备考CISP-PTE那段时间我几乎把市面上排得上号的SQL注入靶场都刷了一遍DVWA、Pikachu、Sqli-labs、CTFHub技能树一个都没放过。刷得越多越发现真正让新手卡壳的往往不是花哨的union注入、时间盲注反而是看起来最简单的万能密码。 OR 11#这行字符背下来只要十秒可真到了考试环境或实战环境很多人照着敲就是绕不过登录框。问题出在哪基本都出在没搞懂它背后的原理。这篇文章不绕弯子直接把万能密码这个考点拆干净它属于SQL注入的哪一类、为什么能绕过登录、不同数据库之间的写法差异、在CISP-PTE考试环境里怎么用以及失效之后怎么排查。整篇按实战流程推进该给步骤给步骤该给参数给参数。适合正在备考CISP-PTE的学员也适合刚入门SQL注入、想彻底搞懂登录绕过原理的新手。前提先说清楚这里讲的所有测试方法都只能在考试环境、靶场和你有明确授权的系统里使用未经授权的系统一律不要碰。1. 万能密码的本质一段被改写逻辑的SQL1.1 先看一段经典的漏洞代码讨论万能密码之前得先看它攻击的目标长什么样。很多教材里都会出现这么一段登录代码写在一个一门心思只求业务能跑、没考虑安全的背景之下$username $_POST[username]; $password $_POST[password]; $sql SELECT * FROM users WHERE username$username AND password$password; $result mysqli_query($conn, $sql); if (mysqli_num_rows($result) 0) { // 登录成功跳转后台 }这段代码的问题看一眼就明白开发人员把用户提交的用户名和密码通过字符串拼接直接塞进了SQL语句用户输入什么SQL就执行什么输入和代码之间没有任何边界。正常登录时提交usernameadmin、password123456SQL语句是SELECT * FROM users WHERE usernameadmin AND password123456如果用户名一栏输入的是admin OR 11#拼出来的就变成SELECT * FROM users WHERE usernameadmin OR 11# AND password任意内容注意这里的几个细节。单引号把原本username后面的闭合引号给“关掉”了11是一个永远成立的恒真条件后面的#在MySQL里是注释符会把这一行剩下的内容全部忽略。真正提交给数据库执行的语句实际是SELECT * FROM users WHERE usernameadmin OR 11这意味着只要users表里存在任何用户这个查询就会把所有行都返回而代码判断用的是mysqli_num_rows($result) 0只要查到一行就算成功。于是攻击者完全不需要知道密码甚至不需要知道正确的用户名就能把登录框当作不存在。这就是万能密码的本质——它不是某个神秘口令而是通过精心构造的SQL片段让开发者原本写好的判断逻辑彻底失效。理解到这一层就自然明白“万能”其实是有条件的后面会专门讲失效的场景。1.2 万能密码的两个核心组件恒真表达式与注释符从上面的例子里能提炼出万能密码的两个核心组件恒真表达式和注释符。恒真表达式的目标是让WHERE条件在数学上必然为真常见写法有这些1111aa1 LIKE 1注释符的目标是截断后面的查询条件让原本的密码校验直接失效。不同数据库的注释符不一样MySQL#、--两个减号后面必须跟一个空格或控制字符、/* */SQL Server--、/* */Oracle--、/* */PostgreSQL--、/* */这里有一个非常容易踩的坑在MySQL里写admin--是无效的因为--后面必须带空格或者控制字符否则它只是普通字符并不能触发注释但同样的写法放在SQL Server和Oracle里就能正常注释。所以MySQL环境下正确写法是admin--注意后面有个空格或者admin#。很多新手在考试环境里栽跟头就是在SQL Server靶机上用#或者在MySQL靶机上用不带空格的--结果payload“看起来对了”实际完全没生效。打个比方帮助理解你原本的SQL是一句完整的问话输入框里的内容是别人替你在句子里填的词。万能密码做的事情很简单——借用标点符号把这句话拆开塞进去一个永远为真的判断再用注释符把后半句直接划掉。整个操作和你在文档里“引号没闭合导致格式错乱”是同一种逻辑只不过发生在SQL语句里后果从格式问题变成了权限绕过。1.3 不同数据库的万能密码写法差异对照不同数据库不仅注释符不一样字符串拼接、运算符、大小写敏感性也有差异所以同一个payload不可能到处通用。我结合平时刷靶场和考试环境里的经验整理了一张对照表数据库类型常用万能密码说明MySQLadmin##是MySQL专属注释符最省事MySQLadmin----后必须有空格否则不生效MySQL OR 11#连用户名都不用猜SQL Serveradmin--直接注释无需考虑空格问题SQL Server OR 11--通用写法Oracleadmin--语法上与SQL Server风格相似PostgreSQLadmin--与SQL Server一致Access OR 11注释截断不稳定用恒真式更稳妥很多初学者会问现在SQL注入漏洞是不是已经绝迹了答案是没有它依然稳定出现在各类漏洞排行和评估标准的前列。新项目确实普遍用了ORM和参数化查询但存量系统、二次开发、报表模块、搜索功能里依然大量存在拼接SQL。这也是CISP-PTE这类认证考试至今仍然把SQL注入当作必考板块的原因——它不是过时的技术而是防御方一直没能彻底解决的问题。2. CISP-PTE考试里的SQL注入考点形态与万能密码的位置2.1 CISP-PTE的SQL注入题到底长什么样CISP-PTE的实操考试不像CTF那样给你一堆flag让你交它更接近一次迷你版渗透测试。考试环境是一个隔离的网段里面有若干台目标机器和Web应用你需要通过浏览器、Burp Suite、sqlmap这些工具去发现漏洞、利用漏洞、拿到关键数据。SQL注入是其中必考的板块之一常见的出题形态有这么几种一是给你一个后台登录页要求绕过认证拿到后台权限二是给你一个带查询功能的页面要求利用注入提取数据库里的敏感表三是综合题把SQL注入和文件上传、命令执行串起来考察完整利用链。三种形态里登录框注入是最典型的也是万能密码最常发挥作用的场合。需要特别说明的是这类考试的重点不是让你对某种技巧死记硬背而是验收你是否理解漏洞原理、能否在陌生环境里快速定位和利用。所以“背payload”是有用的但远不如“理解payload为什么生效”有用。考试环境里经常有人因为环境细节和平时练的靶场不同就卡住本质就是只记了答案没有理解逻辑。2.2 万能密码在考试里的三个典型场景结合我自己刷题和备考的经验万能密码在CISP-PTE里至少有三个典型使用场景。第一个场景是登录绕过。题目给一个后台入口要求拿到管理员权限或后台页面里的敏感信息。此时万能密码可以直接让你绕过登录省去爆破用户名、猜测弱密码的过程。第二个场景是注入点确认。某些题目里登录框是唯一的交互入口你需要先确认它是否存在SQL注入再考虑union注入提取数据。万能密码在这里扮演的角色是“快速探测”——如果admin#能成功登录说明后端是拼接SQL且数据库类型大概率是MySQL接下来就可以放心换union注入去提取数据。第三个场景是综合利用链路的第一步。有些题目的后台不只有一个登录页后台里藏着文件上传、命令执行之类的功能但都被登录框挡在前面。万能密码绕过登录之后整个攻击面才真正打开。考试环境里经常出现“登录都过不去后面的题根本没法做”的情况所以万能密码往往是综合题的入场券。2.3 从万能密码开始的完整利用链把三个场景串起来看一个典型的考试利用链是这样的先访问目标站点识别出登录框用万能密码绕过认证进入后台在后台发现文件上传点尝试上传webshell拿到执行权限之后再利用系统权限读取数据库配置或服务器文件。这条链路在CTF和授权渗透测试里都很常见在CISP-PTE的考试环境里也属于高频考法。需要注意的是绕过登录只是出发点不是终点。很多题目设计的时候并不会把敏感数据直接放在登录后的首页而是藏在更深的地方需要你继续用SQL注入提取数据库内容或者结合文件上传拿权限。换句话说万能密码解决的是“能不能进去”的问题进去之后“能干什么”考察的是你对整个Web安全体系的理解而不是单点技巧。3. 万能密码核心实操判断、构造、验证一条龙3.1 登录框注入判断五步法在正式使用万能密码之前先介绍一套判断登录框是否存在注入的固定流程。这个方法我在考试和实战里反复用过可以避免大量无效尝试。第一步正常登录观察响应差异。先提交一个不存在的用户名再提交一个已知存在的用户名如果有的话记录页面返回的差异比如“用户名不存在”和“密码错误”两种提示是否分得很清楚。第二步在用户名字段输入admin如果页面出现SQL语法错误、500、或者数据库报错信息基本可以判断存在拼接式SQL注入。第三步做布尔对比。输入admin AND 11再看admin AND 12。两次结果不同说明注入点可控而且你连单引号闭合的位置都摸清了。第四步根据数据库类型选择合适的万能密码做快速验证。第五步用Burp的Repeater抓包确认POST参数和响应包差异留作后续分析的依据。这五步里第二步最容易出现误判。有些开发框架会把SQL错误统一处理成500页面看不到清晰的数据库报错有些则会把输入原样回显。所以不要单看有没有报错要结合响应的长度、状态码、页面内容一起判断。3.2 完整演示从抓包到绕过下面用一个典型环境做完整演示。假设考试网段里有一台目标机器访问后看到一个后台登录页表单字段是username和password提交地址是/login.php。第一步打开Burp Suite配置好代理浏览器访问登录页后随便输入一个测试账号抓取POST请求。请求长这样POST /login.php HTTP/1.1 Host: 192.168.1.10 Content-Type: application/x-www-form-urlencoded usernametestpassword123456第二步先把username改成test看响应。如果页面出现数据库语法错误或者行为明显异常说明参数没做转义、后端用的是字符串拼接这是一个强烈信号。第三步根据数据库类型选择payload。假设之前从响应头或报错信息里看到MySQL特征我在用户名字段输入URL编码后的admin#usernameadmin%23password123456%23是#的URL编码。发送之后如果响应的逻辑分支出现变化比如原本的“密码错误”变成了登录成功跳转说明绕过生效。第四步如果admin#不生效就换成 OR 11#密码随便填username%27OR11%23password123456后端拼接出来的SQL是SELECT * FROM users WHERE username OR 11# AND password123456因为#注释掉了后面的AND password123456而11恒为真查询会把所有用户都查出来按常见的数据表结构来看满足条件的结果里往往第一条就是admin之类的管理账号登录自然就成功了。这里再补充一个细节用Burp直接改包的时候注意URL编码。有些字符在传输过程里会被浏览器自动编码你手动输入时反而可能漏掉编码导致payload变形。我习惯在Burp的Repeater里操作把原始报文改好就发清晰可控比在浏览器地址栏里反复试要可靠得多。3.3 怎么确认自己真的绕过了绕过成功的标志不只是“页面跳了”。考试环境里至少要分三个层面确认结果。第一看响应内容。登录成功通常伴随302跳转并返回Set-Cookie登录失败通常是200页面提示密码错误。用Burp比较两次响应的长度和状态码差异明显说明逻辑分支被改变了。第二看后续访问。带着登录后返回的Cookie去访问后台页面如果返回的是管理功能而不是登录框说明session已经建立成功。第三回头验证注入点。如果环境允许再用 AND 11#和 AND 12#做一次布尔对比确认注入点真实存在。因为只有确认了注入点你后续才能用union注入继续提取数据。很多人绕过了登录就直接交差结果后台里根本没有要拿的flag真正得分点其实在数据库里——这就是前面说的万能密码只是入场券。4. 常见问题与排查技巧实录失效原因与现场排错4.1 万能密码失效的六种常见原因我见过最多的场景不是payload背不出来而是payload扔进去毫无反应。归纳下来失效原因基本逃不出下面六种。第一数据库类型判断错误。在MySQL上用#没问题拿到SQL Server上就完全不识别反过来SQL Server的--在MySQL里可能因为后面没空格而失效。这是最高频的翻车原因。第二代码用了参数化查询。这是最“硬”的原因遇到参数化查询任何万能密码都不会生效因为用户输入被当作数据处理不会改变SQL结构。第三输入被过滤或转义。后端可能调用了类似addslashes的函数把单引号转义成\导致你输入的引号根本没闭合字符串。这种情况需要尝试宽字节绕过、URL双重编码等进阶手段。第四密码字段被单独处理。有些登录逻辑是password md5($_POST[password])之后再拼接密码字段里注入等于注入一段哈希字符串完全没有效果。这时候可以把注入口放在用户名字段。第五登录逻辑不是单条查询判断。比如后端先查用户名拿到用户记录后再单独比对密码那么即使你让第一条查询返回admin第二条密码比对依然会失败。第六WAF或安全组件拦截。考试环境一般不带WAF但某些练习平台会模拟过滤规则拦截or、#、--这类关键字。需要换等价写法比如用||替代or、用/* */替代#或者用URL编码绕过分词。六条里第二条和第五条属于“本来就防住了”你再怎么调payload都没用不如省时间换思路其他四条都有绕过空间就看你对环境的判断准不准。4.2 现场排错固定流程遇到万能密码不生效我给自己定了一套固定流程不慌不忙地走一遍大部分问题都能定位。第一步确认数据库类型。看报错信息的语法格式、看后端技术栈PHP、Java、ASP.NET、看默认环境配置。这一步判断失误后面全是白忙。第二步分别测试用户名和密码两个字段。不要只在一个字段上死磕有些系统的密码字段不安全有些只有用户名字段可直接注入。用Burp分别注入对比两次响应的差异很容易就能看出哪个字段是真正的入口。第三步循序渐进换payload。按照“闭合单引号、恒真表达式、注释符”的顺序先尝试admin--、admin#、admin AND 11逐个排除环境差异。不要一上来就叠一堆复杂payload那会让排查失去参照。第四步考虑过滤。如果所有payload都被原样返回或者被拦截需要观察过滤规则。改大小写混合oR、换成数学等价写法21、用注释包裹关键字O/**/R都是常见的绕过思路。第五步回到响应里找线索。登录失败时页面的提示也会泄底比如“用户名不存在”和“密码错误”分得很清楚说明查询可能分了两步走这时就应该换思路而不是继续堆payload。4.3 万能密码实战速查表把平时积累的payload整理成一张速查表按用途分类方便考试前临时翻看。用途payload示例适用环境MySQL注释绕过admin#MySQLMySQL注释绕过admin--MySQL--后必须有空格恒真式绕过 OR 11#MySQLSQL Server绕过 OR 11--SQL ServerOracle绕过admin--Oracle通用恒真式不用注释 OR 11大多数数据库闭合后通用绕过admin OR 11 --大多数数据库布尔验证注入点admin AND 11大多数数据库注意这张表只是“常见可用”的集合不是“绝对可用”的保证。实战里的SQL语句千变万化表里的payload解决的是教材型环境真遇到特殊写法还是要回到原理去现场构造。5. 站在防御角度重新看万能密码从攻击到防护的思路转换5.1 参数化查询为什么能一劳永逸把攻和防对照着看理解会更立体。万能密码能生效的前提是数据库把用户输入当成了SQL代码的一部分。要根除这个问题最有效的办法是让用户输入永远只可能成为数据不可能成为代码。这个目标就是参数化查询Prepared Statement实现的。以PHP PDO为例$stmt $pdo-prepare(SELECT * FROM users WHERE username :username AND password :password); $stmt-bindParam(:username, $username); $stmt-bindParam(:password, $password); $stmt-execute();在这段代码里SQL语句的结构在prepare阶段就被数据库预先解析好了username和password位置被固定为占位符。后续bind进去的任何内容都会被数据库当作字符串字面量来比对而不是当作SQL语法来执行。换句话说你输入admin OR 11#数据库理解的是“有没有一个用户叫这个名字”而不是“用这个条件查所有用户”。用生活化的方式理解参数化查询像是一张印刷好的问卷空格位置固定你只能在空格里填字不能涂改问卷本身。字符串拼接则是把问卷交给你自由发挥你自然可以画出花来。5.2 过滤和转义为什么治标不治本有人会问既然拼接SQL这么危险那用过滤函数把危险字符都拦掉行不行答案是能缓解但治标不治本。以addslashes为代表的转义函数作用是把单引号、双引号等字符前面加上反斜杠让它们变成普通字符。看起来很合理但在特定编码场景下会被绕过。经典的宽字节注入就是利用GBK编码的漏洞%bf%27经过数据库的编码转换后反斜杠被前面的字节“吃掉”单引号依然成功闭合。这类问题说明转义方案的安全性依赖于“字符处理的每一步都正确”而现实里这种依赖往往靠不住。黑名单过滤同样如此。你拦了or攻击者用||你拦了#攻击者用--你拦了一批关键字攻击者还能用注释符把关键字拆开比如SEL/**/ECT。追着payload打补丁永远慢一截。正确的心态是承认输入不可信、拼接不安全从架构上彻底放弃拼接这条路。5.3 安全编码检查清单最后给一份可以直接抄的检查清单不管是备考后写代码还是平时做开发、做测试都能用得上的那种。第一所有SQL一律使用参数化查询或存储过程业务代码里禁止出现字符串拼接SQL。第二数据库账号遵循最小权限原则应用连接不用高权限账号避免注入演变为拖库或命令执行。第三关闭默认报错回显数据库错误统一记录到日志页面只显示通用错误信息避免注入点被快速识别。第四对管理后台启用额外的访问控制和审计日志一旦用户名字段出现异常字符告警要能捕捉到。第五上线前至少过一次自动化扫描加一次人工测试SQL注入属于上线前就要排干净的问题不能指望上线后靠运维去补。这套清单不复杂但真的能覆盖绝大多数SQL注入事故。我在实际项目里见过太多“加个壳就上线”的下场心里很清楚安全不是加几个规则而是每一行代码都默认不信任输入。最后再分享一个个人习惯。我每次刷完一套靶场题目都会把用到的payload和当时的数据库环境、代码特征记在一起存在自己的知识库里而不是零散地甩进记事本。原因是同一个payload在不同环境下表现真的不一样单一记payload不记环境下次遇到十有八九会翻车。万能密码也好union注入也好备考CISP-PTE的核心从来不是背答案而是把“为什么能通”和“什么条件会失效”的边界摸清楚。边界清楚了换个环境你也能迅速上手。
