
1. 项目概述从Low到Medium的跨越很多朋友在DVWA靶场里轻松拿下了SQL注入的Low级别信心满满地进入Medium级别结果一上来就懵了。页面看起来和Low级别差不多一个输入框一个提交按钮但无论你怎么输入、、or 11页面要么没反应要么就给你一个不痛不痒的错误提示完全不像Low级别那样直接给你报出数据库错误。这种感觉就像你拿到了一把新锁却发现原来的万能钥匙插不进去锁芯结构似乎变了。这正是Medium级别设计的精妙之处它不再是“门户大开”而是设置了一道简单的过滤机制考验你能否识别并绕过它。这个Medium级别的SQL注入核心变化在于从GET请求变成了POST请求并且对用户输入进行了转义处理。对于刚入门的朋友来说这就像从“明文通信”进入了“加密通信”阶段虽然只是最基础的“加密”转义但足以让很多直接照搬Low级别Payload的攻击失效。我刚开始接触时也在这里卡了很久后来才明白关键在于理解后端如何处理你的输入以及如何构造不会被转义破坏的Payload。本教程将带你一步步拆解这个过程不仅告诉你“怎么做”更重点解释“为什么这么做”让你真正掌握这种基础防御的绕过思路为后续应对更复杂的High级别打下坚实基础。2. 环境准备与初步侦察在开始实操之前确保你的DVWA环境已经就绪。将DVWA的安全级别设置为“Medium”然后导航到“SQL Injection”模块。你会发现页面外观和Low级别几乎一致但这正是陷阱所在。第一步永远是侦察理解你的“对手”发生了什么变化。2.1 理解请求方式的根本转变首先打开浏览器的开发者工具F12切换到“网络”Network选项卡。在输入框里随意输入一个数字比如1然后点击“Submit”。观察网络请求列表你会发现一个关键变化请求方法从GET变成了POST。在Low级别你的输入id1会直接附加在URL后面形如vulnerabilities/sqli/?id1SubmitSubmit。这种GET请求的参数在地址栏清晰可见非常容易修改和测试。而在Medium级别你提交后URL不会变化参数id1和SubmitSubmit被放在了HTTP请求的“主体”Body中通过一个表单Form Data提交。这是第一个需要适应的点你不能再通过简单地修改URL来测试了。注意这种从GET到POST的变化是Web应用安全中非常常见的一种提升安全性的措施。POST请求的数据不会记录在浏览器历史、服务器日志通常、或地址栏中使得一些自动化的、基于URL扫描的攻击工具可能失效。但这绝不意味着POST请求就更安全它只是提高了攻击的门槛。2.2 分析前端的潜在限制接下来查看页面源代码。在Medium级别的页面上右键选择“查看页面源代码”。搜索输入框对应的HTML代码你可能会看到类似这样的内容form nameXSS methodpost ... input typetext nameid ... /form这里没有像High级别那样出现maxlength等限制属性说明前端没有做额外的输入过滤。问题出在后端。这是第二个关键信息攻击面从前端转移到了后端逻辑。2.3 初探后端过滤机制现在让我们进行第一次试探性攻击看看后端到底做了什么。在输入框中尝试经典的探测字符输入一个单引号点击提交。在Low级别这会直接导致SQL语法错误并回显数据库报错信息。但在Medium级别页面很可能只是刷新了一下显示“User ID is MISSING from the database.”之类的固定提示或者原样输出了你输入的。没有报错信息这被称为“盲注”环境的一种表现但在此处更可能是因为你的输入被“处理”了。为了确认我们输入一个故意构造的、能触发正常回显的Payload同时包含特殊字符。尝试输入1 or 11在Low级别这会构成永真条件返回所有用户数据。在Medium级别提交后观察回显。如果返回的结果只显示了ID为1的用户信息而没有返回所有信息那基本可以确定你输入的单引号被转义了。它可能被转换成了\这样注入的SQL就变成了1\ or \1\\1单引号失去了闭合字符串的功能整个or 11被当作一个字符串的一部分而不是SQL逻辑的一部分。3. 核心漏洞原理与绕过思路拆解经过初步侦察我们确认了Medium级别存在过滤。要绕过它我们必须先理解它。3.1 揭秘mysqli_real_escape_string()函数查看DVWA Medium级别的源代码位于/dvwa/vulnerabilities/sqli/source/medium.php你会发现关键的一行$id mysqli_real_escape_string($GLOBALS[___mysqli_ston], $id);这行代码就是Medium级别的“守门员”。mysqli_real_escape_string()是PHP中MySQLi扩展提供的一个函数它的作用是在特定的字符串这里是用户输入的$id中的特殊字符前加上反斜杠(\)进行转义。哪些是特殊字符呢主要包括单引号 ()双引号 ()反斜杠 (\)换行符 (\n)回车符 (\r)NUL字符 (\0)例如你输入1 or 11经过这个函数处理后就变成了1\ or \1\\1。当这个字符串被拼接到SQL查询语句中时SELECT first_name, last_name FROM users WHERE user_id 1\ or \1\\1;数据库引擎会认为1\是一个整体字符串1后面的or \1\\1也是字符串的一部分整个查询的逻辑变成了“查找user_id等于字符串1 or 11的记录”这显然是不存在的因此只返回空或默认的第一个结果。你的注入逻辑被完全破坏了。3.2 构建无需引号的注入Payload既然单引号会被转义那我们的核心绕过思路就是构造不需要使用引号的SQL注入Payload。这是攻克Medium级别的关键。在数字型SQL注入中WHERE子句的条件通常是user_id $id。如果$id是一个数字在SQL中是不需要用引号包裹的。例如WHERE user_id 1。Low级别之所以能用字符型注入的Payload是因为它的代码可能用了WHERE user_id $id将输入用引号包成了字符串。而Medium级别虽然转义了引号但如果我们能确保输入的$id本身就是一个合法的数字或数字表达式那么即使后端代码用了引号包裹$id我们也能利用数字运算来注入。但更常见且合理的情况是Medium级别的代码本身处理的就是数字型输入没有加引号。我们如何验证呢使用经典的数字型注入测试Payload测试永真条件输入1 and 11测试永假条件输入1 and 12如果第一个输入返回了ID为1的正常结果而第二个输入返回空或错误提示ID缺失那么就可以100%确定这是一个数字型注入点且输入未被引号包裹。因为如果被引号包裹1 and 11会被当作整个字符串and 11不会作为SQL逻辑执行两个输入都会返回相同的结果即user_id等于字符串1 and 11或1 and 12的记录都不存在。实测DVWA Medium级别你会发现1 and 11返回正常1 and 12返回空。Bingo我们不仅确认了是数字型注入还发现了一个重要事实mysqli_real_escape_string()对数字和逻辑运算符and,or,, 空格等没有影响它只转义那些特殊的字符。所以and、、空格这些构成逻辑注入的关键元素都可以正常使用。3.3 编码绕过一种备选思路除了上述方法资料中还提到了编码绕过例如十六进制编码。其原理是将敏感的字符串如数据库名dvwa转换成十六进制表示0x64767761。在SQL语句中MySQL可以直接识别十六进制值。例如在需要指定数据库名的地方不使用table_schemadvwa这需要引号而使用table_schema0x64767761。这样即使引号被过滤或转义我们也无需使用它。这种方法的局限性在于你需要提前知道或猜解出目标字符串如数据库名、表名。它通常用于联合查询Union Inject中在已经确定注入点且需要指定查询条件时使用。在初始探测阶段我们更多依赖数字逻辑测试and 11来确认漏洞编码技术是后续信息获取阶段的利器。4. 手工注入实战步步为营获取数据理解了原理我们开始手工注入。整个过程我们将完全使用浏览器和Burp Suite的Repeater模块来完成这能让你更清晰地看到每一次请求和响应。4.1 第一步确认注入点与字段数我们已经用1 and 11和1 and 12确认了数字型注入。接下来需要知道这个SELECT语句一共查询了多少个字段这是使用UNION SELECT进行数据获取的前提。使用ORDER BY子句来猜测字段数。ORDER BY n表示根据第n个字段进行排序如果n超过了实际字段数数据库就会报错。由于Medium级别不显示具体错误我们通过页面是否返回正常结果来判断。输入1 order by 1- 页面正常显示ID为1的用户信息。输入1 order by 2- 页面正常。输入1 order by 3- 页面异常可能显示空白或固定错误提示。这说明原始查询语句只返回2个字段。我们在后续的UNION SELECT时也必须拼接2个字段。4.2 第二步探测回显点UNION SELECT需要前后两个SELECT语句的字段数一致。现在我们知道是2个字段接下来要找出这2个字段在页面中的哪个位置被显示出来即“回显点”。输入Payload1 union select 1,2这个Payload的意思是先执行SELECT ... WHERE user_id 1再联合执行SELECT 1, 2。如果注入成功且页面有回显数字1和2就会出现在原本显示first_name和last_name的位置上。提交后观察页面。你很可能看到原本显示名字的地方变成了数字1和2或者只有其中一个数字出现。这证明了两个信息1)UNION注入成功2) 我们知道了哪个位置对应SELECT语句中的第一个字段哪个对应第二个字段。假设页面显示“你的ID1 你的姓氏2”那么第一个回显点对应first_name第二个回显点对应last_name。4.3 第三步获取数据库信息现在我们可以把回显点中的数字替换成我们想查询的数据库函数了。获取当前数据库名 输入Payload1 union select database(), 2或者1 union select 1, database()具体取决于你的回显点位置。提交后在其中一个回显点位置你应该能看到当前数据库的名称对于DVWA来说通常就是dvwa。获取数据库版本和用户拓展信息 输入Payload1 union select version(), user()这会在两个回显点分别显示MySQL数据库的版本信息和当前执行查询的数据库用户。这些信息在后续判断数据库特性、权限时很有用。4.4 第四步枚举表名知道了数据库名dvwa下一步是查看这个数据库里有哪些表。我们需要查询MySQL的系统数据库information_schema.tables。输入Payload1 union select 1, table_name from information_schema.tables where table_schemadvwa等等这里我们用了引号dvwa在Medium级别这个引号会被转义导致查询失败。这就是需要运用绕过技巧的时候了。方法一使用十六进制编码推荐将字符串dvwa转换为十六进制。可以在线转换也可以用Burp Suite的Decoder模块。dvwa的十六进制是64767761。 构造Payload1 union select 1, table_name from information_schema.tables where table_schema0x64767761注意在SQL中十六进制值前需要加0x作为前缀。提交这个Payload你就能看到dvwa数据库中的所有表通常会看到guestbook和users两个表。我们显然对users表更感兴趣。方法二不使用引号利用已知信息如果知道数据库名就是dvwa且它是默认的也可以尝试直接使用。但为了通用性十六进制编码是最可靠的绕过方式。4.5 第五步枚举列名现在目标锁定users表我们需要知道这个表里有哪些列字段。查询information_schema.columns。首先将users转换为十六进制7573657273。 输入Payload1 union select 1, column_name from information_schema.columns where table_name0x7573657273 and table_schema0x64767761这个Payload更严谨同时指定了表名和所属数据库名避免从其他数据库中查询到同名表。提交后你应该能看到user_id,first_name,last_name,user,password,avatar等列名。其中user和password是我们最关心的敏感信息列。4.6 第六步提取最终数据——用户名与密码最后一步直接从users表中查询user和password列的数据。输入Payload1 union select user, password from users这里我们直接使用了列名并且UNION SELECT后面跟了两个字段user和password正好对应两个回显点。提交后页面上就会以列表形式显示出所有用户的登录名和经过哈希加密的密码通常是MD5。实操心得有时候数据很多一行行显示不直观。可以使用MySQL的group_concat()函数将所有结果拼接在一行内用逗号隔开。例如1 union select group_concat(user), group_concat(password) from users。这样所有用户名会显示在一个回显点所有密码显示在另一个回显点方便复制和处理。5. 工具辅助使用Burp Suite与sqlmap高效利用手工注入能加深理解但在实战或需要快速获取信息时工具是必不可少的。这里介绍如何结合Burp Suite和sqlmap来完成Medium级别的注入。5.1 使用Burp Suite拦截与重放Burp Suite是Web安全测试的瑞士军刀。我们用它来捕获POST请求并方便地修改Payload进行测试。在浏览器中配置代理指向Burp Suite通常是127.0.0.1:8080。打开Burp的代理拦截功能Proxy - Intercept is on。在DVWA页面输入1并提交。Burp会拦截到这个POST请求。将其发送到“Repeater”模块。在Repeater中你可以看到原始的请求体如id1SubmitSubmit。直接在请求体中修改id参数的值例如改为1 and 11然后点击“Send”。右侧就会显示服务器的响应。你可以不断修改Payload并发送无需在浏览器中反复操作极大提升了测试效率。所有手工注入的步骤都可以在Repeater中完成。5.2 使用sqlmap进行自动化注入sqlmap是自动化的SQL注入神器。对于Medium级别由于是POST请求且存在基础过滤需要一些特殊参数。方法一使用--data参数指定POST数据在命令行中运行python sqlmap.py -u http://your-dvwa-ip/dvwa/vulnerabilities/sqli/ --dataid1SubmitSubmit --cookie你的DVWA会话Cookie --level2 --risk2-u: 目标URL。--data: 指定POST请求的数据。--cookie: 这是必须的因为DVWA需要登录态。你可以从浏览器开发者工具的“网络”选项卡中复制任意一个DVWA请求头中的Cookie值。--level2: 提高测试等级sqlmap会尝试测试Cookie等头部。--risk2: 提高风险等级允许使用一些风险更高的测试Payload。运行后sqlmap会识别出注入点并询问你是否要跳过其他类型测试、是否要执行全量测试等。按照提示选择即可。方法二使用-r参数加载请求文件更推荐首先用Burp Suite拦截一个正常的POST请求如id1。在Burp的拦截历史或Repeater中右键点击该请求选择“Save item”将其保存为一个文本文件比如dvwa_post.txt。在命令行中运行python sqlmap.py -r dvwa_post.txt -p id --level2 --risk2-r: 从文件中读取HTTP请求。-p id: 指定需要测试的参数是id。sqlmap会自动处理Cookie等信息。这种方式更简单因为请求文件里包含了完整的HTTP头包括Cookie、Host等sqlmap可以直接使用。让sqlmap自动获取数据 在sqlmap确认注入点后你可以使用一系列参数让它自动获取数据--dbs: 枚举所有数据库。-D dvwa --tables: 枚举dvwa数据库中的所有表。-D dvwa -T users --columns: 枚举users表的所有列。-D dvwa -T users -C user,password --dump: 导出users表中user和password列的数据。sqlmap的强大之处在于它能自动识别和尝试多种绕过技术包括编码因此即使面对Medium级别的转义它通常也能成功注入。6. 常见问题、排查技巧与防御思考6.1 手工注入常见问题排查表问题现象可能原因解决方案输入1‘或1后页面无变化或报错不明显输入被mysqli_real_escape_string()转义确认是否为数字型注入使用1 and 11测试。避免使用会被转义的字符优先使用数字逻辑测试和UNION SELECT。1 order by 2正常1 order by 3也正常无法确定字段数原始查询字段数可能较多或页面响应逻辑不基于数据是否存在尝试更大的order by数字如10, 20。同时观察页面细微差别如响应时间、页面长度或使用基于布尔盲注的order by判断技巧。union select 1,2执行后页面没有显示数字1和2回显点可能不在当前视图或需要触发特定条件尝试将数字替换为更显眼的字符串如union select ‘aaa‘, ‘bbb‘注意编码绕过引号。查看页面源代码搜索aaa或bbb可能数据被隐藏在HTML注释或JS变量中。使用十六进制编码0x64767761后查询失败数据库版本或模式可能不支持直接十六进制比较或语法错误确保十六进制格式正确0x开头无单引号。尝试使用CHAR()函数where table_schemaCHAR(100, 118, 119, 97)其中数字是d,v,w,a的ASCII码。sqlmap无法识别注入点1. Cookie未正确设置2. 测试等级不够3. 存在CSRF令牌等动态参数1. 确保--cookie参数正确。2. 提高--level和--risk值。3. 使用-r参数加载包含完整会话的请求文件。4. 检查是否有anti-CSRF token需用--randomize或--skip参数处理。6.2 从攻击者视角看防御通过攻克Medium级别我们更应该理解开发者应该如何防御SQL注入。预处理语句参数化查询这是最根本、最有效的防御手段。它使用占位符?来预编译SQL语句结构然后将用户输入作为“参数”传入从根本上杜绝了输入数据改变SQL语句结构的可能性。这是High级别采用的主要防御方式。输入验证与过滤Medium级别的转义是一种过滤但它是不完备的。对于明确的数字型输入应在后端进行强类型转换如intval($id)确保输入只能是数字。对于字符串应建立允许字符的白名单。最小权限原则连接数据库的Web应用账号不应拥有DROP,CREATE,UPDATE等高危权限通常只赋予SELECT权限这能极大限制注入攻击造成的破坏。错误信息屏蔽像Medium级别这样不将数据库详细错误信息返回给前端可以增加攻击者判断注入类型和获取信息的难度。Medium级别的设置巧妙地展示了安全是一个持续的过程。仅仅添加一层转义过滤就能阻挡大量基于字符串拼接的自动化攻击脚本。但作为安全研究者或开发者我们必须明白这远非终点。真正的安全需要从架构和编码习惯上着手而理解每一层防御及其绕过方法正是我们构建更坚固防御的起点。手工注入的每一步思考工具使用的每一个参数最终都汇集成对Web应用安全更深层次的认识。当你成功从users表中提取出那串MD5哈希时收获的不仅是一次通关的成就感更是一套应对基础过滤的实战方法论。