1. 项目概述从“万能口令”看SQL注入攻防的本质“万能口令”这个词在安全测试的圈子里尤其是在Web渗透测试的入门阶段几乎是一个绕不开的经典案例。它听起来很神秘仿佛掌握了某种“后门钥匙”但实际上它的核心原理就是SQL注入。这个项目标题“墨者SQL过滤字符后手工绕过漏洞测试(万能口令)”非常精准地概括了一次完整的、进阶的渗透测试演练在一个已经部署了基础防护过滤了某些危险字符的Web应用上如何通过手工方式一步步分析、构造并最终绕过这些过滤实现所谓的“万能口令”登录从而验证漏洞的存在。简单来说这就是一次针对登录框的字符型SQL注入实战。很多初学者在接触SQL注入时往往直接从工具比如sqlmap开始虽然高效但容易知其然不知其所以然。手工绕过的过程恰恰是理解SQL注入原理、数据库交互逻辑以及开发者防御思路的最佳途径。通过这个项目你将不仅仅学会如何“黑”掉一个靶场如Pikachu更能深刻理解为什么简单的字符过滤会被绕过以及从防御者的角度什么样的编码和过滤才是相对安全的。这个过程适合所有对Web安全感兴趣的朋友无论是刚入门的安全爱好者、准备面试的安全岗位求职者还是希望提升自己代码安全意识的开发人员。接下来我将以一个经验丰富的渗透测试员的视角带你完整复现这次手工绕过之旅并深入剖析每一个技术细节背后的“为什么”。2. 环境搭建与目标分析为什么选择Pikachu在开始任何安全测试之前一个可控、合法的测试环境是首要前提。我们绝不能在没有授权的情况下对任何线上系统进行测试。因此使用像Pikachu这样的漏洞靶场是最佳选择。2.1 靶场选择Pikachu的优势解析Pikachu是一个集成了多种Web漏洞的PHP靶场它的优势在于环境集成度高通常提供一键安装的Docker镜像或集成环境包如PHPStudy省去了繁琐的Apache、PHP、MySQL配置过程。漏洞场景典型其SQL注入模块精心设计了多种类型数字型、字符型、搜索型、XX型、Insert/Update注入、Header注入等非常适合系统性学习。防御机制可调很多关卡提供了“开启/关闭”安全防护的选项方便我们对比学习绕过技巧。这正是我们项目“过滤字符后手工绕过”所需要的。相比之下DVWADamn Vulnerable Web Application虽然也很经典但其防护等级调整更偏向于全局设置而Pikachu在某些具体漏洞点上对过滤机制的展示更为直观。实操步骤从GitHub或相关安全网站下载Pikachu的源码包。如果你本地有PHPMySQL环境如XAMPP, PHPStudy将其解压到Web服务器根目录如www或htdocs。访问http://localhost/pikachu/进行安装根据提示创建数据库并初始化数据。确保主要功能模块特别是“SQL-Inject”下的“字符型注入(get)”和“搜索型注入”等页面可以正常访问。注意强烈建议在虚拟机如VMware, VirtualBox中搭建此环境与主机隔离避免因配置不当影响主机网络或安全。2.2 目标锁定登录框与万能口令我们本次的核心目标是“SQL-Inject”下的“字符型注入(get)”或类似的登录场景。这类场景的前端代码通常如下form actionlogin.php methodGET input typetext nameusername input typepassword namepassword input typesubmit value登录 /form后端PHP代码简化版可能是$uname $_GET[username]; $passwd $_GET[password]; $sql SELECT * FROM users WHERE username$uname AND password$passwd; $result mysqli_query($conn, $sql); if(mysqli_num_rows($result) 0){ // 登录成功 }这里的核心问题在于用户输入的$uname和$passwd被直接拼接进了SQL语句。如果用户输入admin --作为用户名密码任意那么拼接后的SQL语句就变成了SELECT * FROM users WHERE usernameadmin -- AND passwordanything--在SQL中表示单行注释其后的所有内容都会被数据库忽略。于是这条语句等价于SELECT * FROM users WHERE usernameadmin只要admin用户存在登录就会成功。这就是最原始的“万能口令”原理。但我们的项目标题提到了“过滤字符后”这意味着靶场可能对输入进行了一些处理例如过滤了单引号、注释符--或#、or/and等关键字。我们的任务就是分析它过滤了什么以及如何绕过这些过滤。3. 手工注入侦察与过滤探测像侦探一样分析直接上工具是莽夫手工探测才是艺术。第一步永远是信息收集与侦察。3.1 判断注入点类型与基础测试访问靶场的登录页面我们首先进行最基础的测试判断是否存在注入以及注入类型。正常输入测试输入一个大概率不存在的用户名和密码如test/test123。预期结果是登录失败。这是为了建立“正常失败”的基线。数字型注入试探输入1或1 and 11等。但在登录框场景参数通常被引号包裹所以字符型更常见。字符型注入试探关键步骤输入用户名admin注意这个单引号输入任意密码。观察结果情况A无过滤页面返回了数据库错误信息如You have an error in your SQL syntax...。这直接证实了注入漏洞的存在并且单引号没有被过滤。情况B有过滤页面可能显示“用户名不存在”或“登录失败”但没有SQL语法错误。这说明我们的单引号可能被前端或后端处理掉了例如被删除、转义或替换。在我们的项目场景中我们假设遇到了情况B靶场对输入进行了一些过滤。3.2 系统性过滤字符探测当单引号被过滤时我们需要系统性地探测哪些字符或关键字被处理了。这是一个“黑盒测试”过程。探测注释符尝试输入admin--、admin#。观察是登录失败还是出现了不同的错误可能注释符被过滤导致语法错误。探测逻辑运算符尝试输入admin or 11。这是一个经典的Payload如果or或被过滤这个Payload会失效。探测空格SQL语句中空格很重要。有些过滤器会过滤空格。尝试用注释符内联注释/**/代替空格admin/**/or/**/1/**/1。探测关键词大小写变形过滤器可能是大小写敏感的。尝试Or、OR、oR、And、ANd等。探测编码与双重编码尝试URL编码特殊字符。例如单引号的URL编码是%27。输入admin%27or%271%27%271。如果后端在过滤后再次解码那么%27会被还原为从而绕过基于字符串匹配的过滤。探测过滤逻辑是“替换”还是“删除”这是一个重要技巧。输入admin。如果过滤器是删除单引号那么处理后变成admin可能导致登录成功如果admin用户存在。如果过滤器是替换成空或其他字符结果则不同。实操记录示例假设我们输入admin or 11#返回失败但输入admin or 12#也返回同样的失败信息而不是“用户名不存在”这可能说明or或#被整体过滤或导致了语法错误。我们转而测试admin and 11和admin and 12如果前者成功后者失败则说明and和可用但or或#可能被禁。通过一系列有目的的测试我们可以逐步摸清后端过滤器的“脾气”。4. 构造绕过Payload思维与技巧的博弈在探测清楚过滤规则后我们就可以开始精心构造绕过Payload了。假设我们通过探测发现这个靶场的过滤规则是删除输入中的单引号、双减号--和井号#。4.1 绕过单引号过滤利用原始字符串与数字比较既然单引号被删除我们就不能用它来闭合字符串。但登录SQL语句是username$uname。如果我们输入的单引号被删除比如输入admin变成admin语句就成了usernameadmin这反而是合法的。但这并不能实现注入。我们需要换一种思路让注入的Payload本身不依赖单引号或者让单引号的功能被其他方式替代。技巧一利用数字型注入点如果存在虽然登录框多是字符型但有时开发者会将数字ID作为用户名校验的一部分极罕见。如果真是数字型Payload直接是1 or 11无需单引号。但本例我们按字符型处理。技巧二闭合引号后注释掉剩余部分但注释符也被过滤了怎么办这是核心挑战。如果--和#都被删除我们无法用它们注释掉后面的 AND password...。那么我们必须让后面的语句也语法正确。经典Payload构造用户名admin or 11 密码任意经过过滤器删除单引号后输入变为用户名admin or 11 密码任意拼接成的SQL语句为SELECT * FROM users WHERE usernameadmin or 11 AND password任意这显然不行因为admin or 11被当作一个完整的字符串去查询了。我们需要让过滤器删除单引号后剩下的字符能组成新的、有效的语法。这非常困难因为单引号是字符串边界的关键。因此更合理的假设是过滤器可能不是简单删除而是转义。例如PHP的addslashes()或mysqli_real_escape_string()函数会在单引号前加反斜杠\这样它就成了字符串的一部分而不是边界符。4.2 假设过滤为转义宽字节注入等进阶技巧如果过滤是转义单引号-\那么输入admin会变成admin\SQL语句为usernameadmin\。这时如果数据库字符集是GBK、BIG5等双字节编码就可能存在宽字节注入漏洞。原理在GBK编码中反斜杠\的编码是%5c。如果我们输入一个字符其高字节与%5c能组成一个合法的GBK字符那么数据库在解析时就会“吞掉”反斜杠。 例如输入admin%bf%bf是一个GBK字符的高字节。经过转义变成admin%bf\即admin%bf%5c%27。在GBK编码下%bf%5c可能对应一个合法的中文字符如“縗”那么剩下的%27单引号就独立出来成功闭合了前面的引号从而引发注入。Payload示例GBK环境用户名admin%bf or 11# 密码任意假设#未被过滤由于靶场环境多变Pikachu的字符型注入关卡可能专门演示了这种过滤。你需要根据实际错误回显来判断。如果页面提示了数据库错误并且错误信息中包含了\和那么很可能是转义场景。4.3 万能口令的最终形态在理解了过滤机制后我们可以构造出真正意义上的“万能口令”。假设我们面对的是一个转义了单引号但未过滤or和且注释符#可用的环境这是很多初级防御的写照。那么一个经典的绕过Payload是用户名admin or 11# 密码留空或任意后端处理流程获取username “admin or 11#”。转义单引号“admin\ or 11#”。拼接SQLSELECT * FROM users WHERE usernameadmin\ or 11# AND password...数据库执行由于#注释了后面所有内容实际执行的语句是SELECT * FROM users WHERE usernameadmin\ or 11这里admin\是一个整体字符串不等于admin所以usernameadmin\条件为假。但后面有or 11这是一个永真条件。因此整个WHERE条件为真。查询返回结果通常是users表的第一行数据登录成功。这样无论密码是什么甚至不管是否存在admin用户只要or 11条件成立就能登录成功。这就是“万能口令”的威力。5. 手工注入深入信息获取与联合查询绕过登录只是第一步。作为一个完整的漏洞测试我们的目标往往是获取数据库中的敏感信息如其他用户名、密码哈希、手机号等。这就需要用到联合查询UNION SELECT。5.1 确定字段数UNION查询的前提是前后SELECT语句的字段数必须相同。我们通过ORDER BY子句来猜测。 在登录成功后或有错误回显的页面我们尝试在用户名框注入admin order by 1# admin order by 2# admin order by 3# ...直到页面返回错误如Unknown column 5 in order clause说明字段数超出。假设order by 4出错order by 3正常那么当前查询的字段数就是3。5.2 联合查询获取信息知道字段数是3后我们可以构造UNION查询将我们想要的数据“合并”到原查询结果中显示出来。通常登录成功后页面会显示用户名等信息这就是我们的回显点。Payload示例用户名admin union select 1,2,3# 密码任意提交后观察页面。原本显示用户名的地方可能变成了数字2或3。这说明这个位置对应SELECT语句中的第2或第3个字段可以用来回显数据。获取数据库信息假设数字2的位置可以回显。用户名admin union select 1, database(), 3#页面可能会显示当前数据库名比如pikachu。用户名admin union select 1, user(), 3#显示当前数据库用户。用户名admin union select 1, version(), 3#显示数据库版本。5.3 获取表名、列名与数据这需要查询数据库的元数据information_schema。以MySQL为例用户名admin union select 1, group_concat(table_name), 3 from information_schema.tables where table_schemadatabase()#这会将当前数据库pikachu下的所有表名连接起来显示例如httpinfo,member,message,users,xss...。我们找到用户相关的表比如users。接着查询users表的列名用户名admin union select 1, group_concat(column_name), 3 from information_schema.columns where table_schemadatabase() and table_nameusers#可能会得到id,username,password,level。最后拖取数据用户名admin union select 1, concat(username, :, password), 3 from users#这样页面上就可能直接显示出admin:xxxxxx这样的用户名和密码哈希值。实操心得在实际测试中页面可能没有明显的回显点盲注。这时就需要采用时间盲注或布尔盲注的技巧通过页面响应时间或内容的细微差异来推断数据。例如admin and if(ascii(substr(database(),1,1))100, sleep(5), 1)#通过观察页面是否延迟5秒响应来判断条件真假。6. 防御编码分析与绕过总结完成攻击不是终点理解防御并思考如何改进才是安全工作的核心。题目中“过滤字符”是一种非常初级的防御手段我们来看看它为什么失败以及更好的做法是什么。6.1 常见错误防御方式黑名单过滤简单地删除或替换、--、#、or、and、select、union等关键词。绕过方法双写selselectect、大小写混合SeLeCt、用等价符号||代替or代替and、编码、注释分割sel/**/ect。转义特殊字符使用addslashes()、mysql_real_escape_string()。绕过方法宽字节注入GBK等环境、二次注入转义后的数据被取出再次使用时不加转义。魔法引号Magic Quotes已废弃的PHP配置自动转义GPCGet/Post/Cookie数据问题同2。6.2 根本解决方案参数化查询预编译语句这是目前公认最有效的防御SQL注入的方法。它将SQL语句的结构模板与数据参数分开发送至数据库服务器数据库会先编译语句结构再将参数代入。此时即使用户输入中包含SQL元字符也只会被当作普通字符串数据来处理无法改变语句结构。PHPMySQLi示例$stmt $conn-prepare(SELECT * FROM users WHERE username ? AND password ?); $stmt-bind_param(ss, $username, $password); // “ss”表示两个字符串参数 $stmt-execute(); $result $stmt-get_result();PHPPDO示例$stmt $conn-prepare(SELECT * FROM users WHERE username :uname AND password :passwd); $stmt-execute(array(:uname $username, :passwd $password));使用参数化查询后即使用户输入admin or 11#数据库也会严格查找用户名为admin or 11#的记录而不会将其解析为SQL指令。6.3 其他辅助防御措施最小权限原则数据库连接账户不应使用root应仅授予应用所需的最小权限如只有SELECT、INSERT权限无DROP、FILE等。输入验证与白名单对于已知固定格式的输入如手机号、邮箱使用正则表达式进行严格匹配白名单而非仅仅过滤危险字符黑名单。输出编码防止二次注入在将数据从数据库取出显示或用于其他查询时仍需进行适当的处理。Web应用防火墙WAF在应用层前部署WAF可以拦截大量已知的攻击Payload作为一道额外的防线。7. 从手工到自动化Sqlmap的辅助验证手工注入能让我们透彻理解原理但在效率和信息收集全面性上工具仍有不可替代的优势。Sqlmap就是这样一个神器。在手工确认存在注入点后可以用Sqlmap进行快速、全面的利用。基本使用步骤抓包使用Burp Suite或浏览器开发者工具抓取登录请求的数据包。将请求内容包括Cookie、Headers保存到一个文本文件中比如login.txt。运行Sqlmapsqlmap -r login.txt --batch --dbs-r从文件加载HTTP请求。--batch以非交互模式运行自动选择默认选项。--dbs枚举数据库。获取当前数据库sqlmap -r login.txt --batch --current-db枚举表sqlmap -r login.txt --batch -D pikachu --tables枚举列并拖取数据sqlmap -r login.txt --batch -D pikachu -T users --columns sqlmap -r login.txt --batch -D pikachu -T users -C username,password --dumpSqlmap的高级技巧与注意事项绕过WAFSqlmap提供了--tamper参数可以使用脚本对Payload进行混淆、编码以绕过简单的WAF过滤。例如--tamperspace2comment将空格替换为注释。风险提示Sqlmap功能强大但切勿用于未授权的测试。即使在授权测试中--dump这样的操作也可能拖取大量数据需谨慎评估影响。结果分析Sqlmap输出的信息非常详细包括注入类型、Payload、数据库指纹等是学习不同数据库MySQL, SQL Server, PostgreSQL等注入差异的绝佳材料。手工注入与工具使用并不矛盾而是相辅相成。手工打下基础理解原理工具提升效率查漏补缺。通过这个“墨者”项目的完整演练你应该对SQL注入特别是过滤字符后的手工绕过有了从理论到实践的深刻认识。安全之路始于理解漏洞终于修复漏洞。