备考CISP-PTE那段时间,我几乎把市面上排得上号的SQL注入靶场都刷了一遍,DVWA、Pikachu、Sqli-labs、CTFHub技能树一个都没放过。刷得越多越发现,真正让新手卡壳的往往不是花哨的union注入、时间盲注,反而是看起来最简单的万能密码。' OR 1=1#这行字符,背下来只要十秒,可真到了考试环境或实战环境,很多人照着敲就是绕不过登录框。问题出在哪?基本都出在没搞懂它背后的原理。
这篇文章不绕弯子,直接把万能密码这个考点拆干净:它属于SQL注入的哪一类、为什么能绕过登录、不同数据库之间的写法差异、在CISP-PTE考试环境里怎么用,以及失效之后怎么排查。整篇按实战流程推进,该给步骤给步骤,该给参数给参数。适合正在备考CISP-PTE的学员,也适合刚入门SQL注入、想彻底搞懂登录绕过原理的新手。前提先说清楚:这里讲的所有测试方法,都只能在考试环境、靶场和你有明确授权的系统里使用,未经授权的系统一律不要碰。
1. 万能密码的本质:一段被改写逻辑的SQL
1.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就执行什么,输入和代码之间没有任何边界。正常登录时提交username=admin、password=123456,SQL语句是:
SELECT * FROM users WHERE username='admin' AND password='123456'如果用户名一栏输入的是admin' OR 1=1#,拼出来的就变成:
SELECT * FROM users WHERE username='admin' OR 1=1#' AND password='任意内容'注意这里的几个细节。单引号把原本username后面的闭合引号给“关掉”了,1=1是一个永远成立的恒真条件,后面的#在MySQL里是注释符,会把这一行剩下的内容全部忽略。真正提交给数据库执行的语句实际是:
SELECT * FROM users WHERE username='admin' OR 1=1这意味着只要users表里存在任何用户,这个查询就会把所有行都返回,而代码判断用的是mysqli_num_rows($result) > 0,只要查到一行就算成功。于是攻击者完全不需要知道密码,甚至不需要知道正确的用户名,就能把登录框当作不存在。
这就是万能密码的本质——它不是某个神秘口令,而是通过精心构造的SQL片段,让开发者原本写好的判断逻辑彻底失效。理解到这一层,就自然明白“万能”其实是有条件的,后面会专门讲失效的场景。
1.2 万能密码的两个核心组件:恒真表达式与注释符
从上面的例子里能提炼出万能密码的两个核心组件:恒真表达式和注释符。恒真表达式的目标是让WHERE条件在数学上必然为真,常见写法有这些:
1=1'1'='1'a'='a'1 LIKE 1
注释符的目标是截断后面的查询条件,让原本的密码校验直接失效。不同数据库的注释符不一样:
- MySQL:
#、--(两个减号后面必须跟一个空格或控制字符)、/* */ - SQL Server:
--、/* */ - Oracle:
--、/* */ - PostgreSQL:
--、/* */
这里有一个非常容易踩的坑:在MySQL里写admin'--是无效的,因为--后面必须带空格或者控制字符,否则它只是普通字符,并不能触发注释;但同样的写法放在SQL Server和Oracle里就能正常注释。所以MySQL环境下正确写法是admin'--(注意后面有个空格)或者admin'#。很多新手在考试环境里栽跟头,就是在SQL Server靶机上用#,或者在MySQL靶机上用不带空格的--,结果payload“看起来对了”,实际完全没生效。
打个比方帮助理解:你原本的SQL是一句完整的问话,输入框里的内容是别人替你在句子里填的词。万能密码做的事情很简单——借用标点符号把这句话拆开,塞进去一个永远为真的判断,再用注释符把后半句直接划掉。整个操作和你在文档里“引号没闭合导致格式错乱”是同一种逻辑,只不过发生在SQL语句里,后果从格式问题变成了权限绕过。
1.3 不同数据库的万能密码写法差异对照
不同数据库不仅注释符不一样,字符串拼接、运算符、大小写敏感性也有差异,所以同一个payload不可能到处通用。我结合平时刷靶场和考试环境里的经验,整理了一张对照表:
| 数据库类型 | 常用万能密码 | 说明 |
|---|---|---|
| MySQL | admin'# | #是MySQL专属注释符,最省事 |
| MySQL | admin'-- | --后必须有空格,否则不生效 |
| MySQL | ' OR 1=1# | 连用户名都不用猜 |
| SQL Server | admin'-- | 直接注释,无需考虑空格问题 |
| SQL Server | ' OR 1=1-- | 通用写法 |
| Oracle | admin'-- | 语法上与SQL Server风格相似 |
| PostgreSQL | admin'-- | 与SQL Server一致 |
| Access | ' OR '1'='1 | 注释截断不稳定,用恒真式更稳妥 |
很多初学者会问:现在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 '1'='1,再看admin' AND '1'='2。两次结果不同,说明注入点可控,而且你连单引号闭合的位置都摸清了。
第四步,根据数据库类型选择合适的万能密码做快速验证。
第五步,用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 username=test&password=123456第二步,先把username改成test',看响应。如果页面出现数据库语法错误或者行为明显异常,说明参数没做转义、后端用的是字符串拼接,这是一个强烈信号。
第三步,根据数据库类型选择payload。假设之前从响应头或报错信息里看到MySQL特征,我在用户名字段输入URL编码后的admin'#:
username=admin'%23&password=123456%23是#的URL编码。发送之后如果响应的逻辑分支出现变化,比如原本的“密码错误”变成了登录成功跳转,说明绕过生效。
第四步,如果admin'#不生效,就换成' OR 1=1#,密码随便填:
username=%27+OR+1=1%23&password=123456后端拼接出来的SQL是:
SELECT * FROM users WHERE username='' OR 1=1#' AND password='123456'因为#注释掉了后面的AND password='123456',而1=1恒为真,查询会把所有用户都查出来,按常见的数据表结构来看,满足条件的结果里往往第一条就是admin之类的管理账号,登录自然就成功了。
这里再补充一个细节:用Burp直接改包的时候,注意URL编码。有些字符在传输过程里会被浏览器自动编码,你手动输入时反而可能漏掉编码导致payload变形。我习惯在Burp的Repeater里操作,把原始报文改好就发,清晰可控,比在浏览器地址栏里反复试要可靠得多。
3.3 怎么确认自己真的绕过了
绕过成功的标志不只是“页面跳了”。考试环境里,至少要分三个层面确认结果。
第一,看响应内容。登录成功通常伴随302跳转并返回Set-Cookie;登录失败通常是200页面提示密码错误。用Burp比较两次响应的长度和状态码,差异明显说明逻辑分支被改变了。
第二,看后续访问。带着登录后返回的Cookie去访问后台页面,如果返回的是管理功能而不是登录框,说明session已经建立成功。
第三,回头验证注入点。如果环境允许,再用' AND 1=1#和' AND 1=2#做一次布尔对比,确认注入点真实存在。因为只有确认了注入点,你后续才能用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 '1'='1,逐个排除环境差异。不要一上来就叠一堆复杂payload,那会让排查失去参照。
第四步,考虑过滤。如果所有payload都被原样返回或者被拦截,需要观察过滤规则。改大小写混合(oR)、换成数学等价写法(2>1)、用注释包裹关键字(O/**/R)都是常见的绕过思路。
第五步,回到响应里找线索。登录失败时页面的提示也会泄底,比如“用户名不存在”和“密码错误”分得很清楚,说明查询可能分了两步走,这时就应该换思路,而不是继续堆payload。
4.3 万能密码实战速查表
把平时积累的payload整理成一张速查表,按用途分类,方便考试前临时翻看。
| 用途 | payload示例 | 适用环境 |
|---|---|---|
| MySQL注释绕过 | admin'# | MySQL |
| MySQL注释绕过 | admin'-- | MySQL(--后必须有空格) |
| 恒真式绕过 | ' OR 1=1# | MySQL |
| SQL Server绕过 | ' OR 1=1-- | SQL Server |
| Oracle绕过 | admin'-- | Oracle |
| 通用恒真式(不用注释) | ' OR '1'='1 | 大多数数据库 |
| 闭合后通用绕过 | admin' OR '1'='1' -- | 大多数数据库 |
| 布尔验证注入点 | admin' AND '1'='1 | 大多数数据库 |
注意,这张表只是“常见可用”的集合,不是“绝对可用”的保证。实战里的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 1=1#,数据库理解的是“有没有一个用户叫这个名字”,而不是“用这个条件查所有用户”。
用生活化的方式理解:参数化查询像是一张印刷好的问卷,空格位置固定,你只能在空格里填字,不能涂改问卷本身。字符串拼接则是把问卷交给你自由发挥,你自然可以画出花来。
5.2 过滤和转义为什么治标不治本
有人会问,既然拼接SQL这么危险,那用过滤函数把危险字符都拦掉行不行?答案是:能缓解,但治标不治本。
以addslashes为代表的转义函数,作用是把单引号、双引号等字符前面加上反斜杠,让它们变成普通字符。看起来很合理,但在特定编码场景下会被绕过。经典的宽字节注入就是利用GBK编码的漏洞:%bf%27经过数据库的编码转换后,反斜杠被前面的字节“吃掉”,单引号依然成功闭合。这类问题说明,转义方案的安全性依赖于“字符处理的每一步都正确”,而现实里这种依赖往往靠不住。
黑名单过滤同样如此。你拦了or,攻击者用||;你拦了#,攻击者用--;你拦了一批关键字,攻击者还能用注释符把关键字拆开,比如SEL/**/ECT。追着payload打补丁,永远慢一截。正确的心态是承认输入不可信、拼接不安全,从架构上彻底放弃拼接这条路。
5.3 安全编码检查清单
最后给一份可以直接抄的检查清单,不管是备考后写代码,还是平时做开发、做测试,都能用得上的那种。
第一,所有SQL一律使用参数化查询或存储过程,业务代码里禁止出现字符串拼接SQL。第二,数据库账号遵循最小权限原则,应用连接不用高权限账号,避免注入演变为拖库或命令执行。第三,关闭默认报错回显,数据库错误统一记录到日志,页面只显示通用错误信息,避免注入点被快速识别。第四,对管理后台启用额外的访问控制和审计日志,一旦用户名字段出现异常字符,告警要能捕捉到。第五,上线前至少过一次自动化扫描加一次人工测试,SQL注入属于上线前就要排干净的问题,不能指望上线后靠运维去补。
这套清单不复杂,但真的能覆盖绝大多数SQL注入事故。我在实际项目里见过太多“加个壳就上线”的下场,心里很清楚:安全不是加几个规则,而是每一行代码都默认不信任输入。
最后再分享一个个人习惯。我每次刷完一套靶场题目,都会把用到的payload和当时的数据库环境、代码特征记在一起,存在自己的知识库里,而不是零散地甩进记事本。原因是同一个payload在不同环境下表现真的不一样,单一记payload不记环境,下次遇到十有八九会翻车。万能密码也好,union注入也好,备考CISP-PTE的核心从来不是背答案,而是把“为什么能通”和“什么条件会失效”的边界摸清楚。边界清楚了,换个环境你也能迅速上手。