1. CISP-PTE里那道让人印象深刻的"万能密码"题
考过CISP-PTE的朋友应该都有体会,实操题里SQL注入占了很大的比重,而且难度是从前往后递增的。前面几道通常是让你判断注入类型、绕过WAF、盲注拿数据,练熟了基本都能过。但是到了SQL注入系列的中后段,会突然冒出一个登录框——没有提示、没有报错、看起来就是一个再普通不过的用户名密码提交页面。我第一次做到这里的时候,第一反应是"这题是不是考弱口令":拿admin、123456、test之类试了一圈,页面纹丝不动,甚至连个验证码错误提示都没有。
后来冷静下来,把Burp Suite挂上代理,看了一眼提交的请求包,才意识到这里大概率考的是万能密码。所谓万能密码,不是字典里某个固定的密码,而是利用SQL语句拼接漏洞,把登录校验逻辑给"化掉"。它不是让你破解某个真实的密码,而是通过注入一段SQL代码,让后端认为你已经通过了认证。这类题目在CISP-PTE的考试环境中会直接映射到一个具体的flag,你需要通过登录成功之后的界面或者响应包拿到关键信息。
这道题之所以经典,在于它考察的不只是"会不会背几个payload",而是你能不能从请求包、响应包、闭合方式、数据库类型这些细节里,推出正确的构造方法。很多人卡在这一题,不是因为不知道万能密码这回事,而是因为构造出来的语句和题目环境不匹配。这一篇我就把从探测到提交flag的完整过程、背后原理和踩坑经验一次性讲透。
1.1 为什么这道题会成为实操中的分水岭
CISP-PTE的考纲里,Web渗透方向的SQL注入是最核心的得分点。而"万能密码"这个知识点,表面上看只是一个登录绕过的技巧,实际上它串联了SQL注入里的几个关键概念:注入点识别、字符串闭合、注释符、布尔逻辑、以及后端语言的差异。题目环境往往还会做简单的过滤,比如拦截空格、拦截关键字、拦截注释符,这又额外考察了绕过能力。
我在备考和带人备考的过程里见过不少例子:有人前面几道题做得很快,到万能密码这道题就卡一两个小时。原因往往是他们习惯性地用SQLMap去跑,或者直接套用网上的payload,却没有认真看请求包的特征。CISP-PTE的题目环境是可控的,但恰恰因为可控,反而更需要你从响应差异里自己推断出正确的payload。这道题做完之后,你对SQL注入的"手感"会明显不一样——因为你不再只是机械地试payload,而是能理解为什么某个payload能成功,为什么另一个不行。
另外,这个考点在实际工作中也高度实用。很多老旧系统的登录接口到现在依然是直接拼接字符串,登录框就是攻击面。能快速判断并利用万能密码,往往比费劲去爆数据库拿密码哈希要快得多,甚至在真实渗透测试中直接决定了你能不能拿到系统权限。
1.2 万能密码的本质:让登录逻辑失效
聊万能密码之前,先明确一个概念:它绕过的不是"密码",而是"认证逻辑"。后端在处理登录请求时,通常会有这样一段逻辑:从数据库里找出用户名匹配的记录,然后比对密码字段是否一致。如果用户名和密码都对,就放行登录。万能密码的攻击思路,就是想方设法让SQL语句里的WHERE条件恒为真,或者让密码比对部分被注释掉,这样无论你输入什么密码,查询结果都会返回有效记录。
举例来说,常见的payload:
'or'1'='1'--把它放在用户名位置,实际执行的SQL会变成类似这样:
SELECT * FROM users WHERE username='' or '1'='1'--' AND password='xxx'这里--是注释符,后面的密码校验逻辑被注释掉了。前面的'1'='1'永远成立,整条WHERE条件也就恒真。数据库会返回表中至少一条用户记录,后端一看查询结果不为空,就判定登录成功。整个过程里,你并没有输入任何真实密码,这就是"万能"两个字的含义。
理解了这一层逻辑之后,你会发现万能密码的构造是有章可循的,不是靠死记硬背。后面我会从一段典型的问题代码开始,把原理逐步展开。
2. 核心原理拆解:万能密码为什么能"万能"
2.1 从一句拼接查询看认证逻辑漏洞
要彻底搞懂万能密码,先要回到最原始的代码层面。下面这段PHP代码,几乎是所有漏洞靶场和部分老系统里都能见到的典型写法:
<?php $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) { // 登录成功 } else { // 登录失败 } ?>注意看SQL语句,用户输入的用户名和密码被直接拼接到了字符串里,外面只包了一层单引号。这里的问题就在于:用户名输入的内容会原封不动地成为SQL语句的一部分。如果用户名里包含SQL关键字和引号,它就能改变整个语句的结构。
假设我输入的用户名是:
admin' or 1=1 --拼接后的SQL是:
SELECT * FROM users WHERE username = 'admin' or 1=1 --' AND password = 'xxx'数据库解析这条语句时,会先看WHERE子句:username = 'admin' or 1=1。后面的--是SQL注释符,数据库会忽略注释符后面的所有内容。所以这条语句实际执行的是:查询所有用户名等于admin、或者1=1的记录。因为1=1是恒真的,数据库会返回整张表的内容。mysqli_num_rows($result)的结果必然大于0,登录校验自然就通过。
你可能会问:如果用户表里第一行不是admin怎么办?没关系,or 1=1会让整张表的所有行都满足条件,查询结果不会为空。只要表里有任何一条记录,登录就成功。这就是万能密码的底层逻辑。
2.2 三种常见形态的原理对照
万能密码的payload看起来五花八门,但归纳起来主要有三类,每一类的原理都略有不同。搞清楚它们之间的区别,你在实际做题时就能灵活变通。
第一种是恒真条件型。这类payload的核心是在WHERE子句中构造一个永真的表达式,比如:
' or 1=1 --' or '1'='1' --' or 'a'='a'#原理就是把原有的查询条件变成"条件A OR 条件B",其中条件B永真。整个OR表达式的结果必为真。
第二种是注释截断型。这类payload的要点是利用注释符直接吃掉密码校验部分。典型例子:
admin' --如果用户名传入的是admin' --,拼接后的SQL变成:
SELECT * FROM users WHERE username = 'admin' --' AND password = 'xxx'--后面的内容被注释掉,查询条件只剩username = 'admin'。如果表里恰好有admin用户,查询结果就非空,登录成功。这种方式不需要构造恒真表达式,但前提是你知道一个有效的用户名。对admin这个常见账号来说,几乎一试一个准。
第三种是空值恒真型。这类payload利用的是某些数据库里空字符串和数值比较时的特性,比如:
' or 1=1#' or 2>1--原理和第一种类似,只是表达式形式不同。实际使用中,具体选哪类,取决于后端代码的过滤规则和注释符支持情况。
我把这三类整理成一张快速对照表:
| 类型 | 典型payload | 核心原理 | 适用场景 |
|---|---|---|---|
| 恒真条件型 | ' or 1=1 -- | 构造永真OR条件 | 后端子句为经典AND拼接 |
| 注释截断型 | admin' -- | 注释掉密码校验 | 已知有效用户名 |
| 空值恒真型 | ' or 1=1# | 构造恒真条件+注释 | MySQL环境,#可用时 |
2.3 一个参数多个点:别忽略用户名和密码两个注入点
很多人在做题时习惯性地只把payload放在用户名位置,却忽略了密码位置也是一个可注入的点。实际题目的登录代码如果用的是不同的拼接方式,密码位置同样存在注入可能。
比如有些代码是这样的:
$sql = "SELECT * FROM users WHERE username = '$username'";它先用用户名查一次,不管查没查到,后面再用密码去匹配。这种情况你把payload放在密码位置也能起作用。
还有的情况是用户名和密码都拼接,但过滤规则只过滤了GET参数,没过滤POST参数,或者只过滤了用户名,没过滤密码。做题时两手准备永远是必要的:先在用户名处试payload,不行就把同样的思路搬到密码处。
另外,万能密码的构造中还经常用到注释符。MySQL里常见的是--(后面要跟一个空格)和#。SQL Server和Oracle里--不需要额外空格。如果环境是PostgreSQL,--同样有效。这个细节非常关键——我见过不少人用MySQL的#去打SQL Server环境,结果什么都没发生。
3. 实操全流程:从探测到拿Flag
3.1 第一步:识别登录框的语言与数据库特征
进入CISP-PTE的这道题之后,不要急着往输入框里填payload。先打开Burp Suite,把浏览器代理挂上,随便提交一组测试账号(比如admin/admin123),观察HTTP请求包和响应包。
这一步的目标有三个:第一,确认提交方式是GET还是POST;第二,看有没有隐藏参数;第三,从响应头、报错信息、Cookie这些地方判断后端语言和数据库类型。
常见特征包括:Set-Cookie里带着特定的会话标识、响应头里出现X-Powered-By: PHP/7.x、错误页面出现MySQL相关的提示文字。如果页面在输入单引号后抛出数据库语法错误,那基本上可以确认是MySQL。如果没有任何回显,那就要用布尔盲注的思路来判断。
我实测过的CISP-PTE模拟环境里,这道题通常是PHP+MySQL的组合,请求方式是POST,参数就是username和password。但这不意味着每次都是同一套,我后面会专门讲怎么应对变体。
3.2 第二步:闭合判断与引号确认
在提交万能密码之前,先做闭合测试。这个动作可以帮你确认后端到底是怎么拼接SQL的。
在用户名框输入一个单引号',提交,观察响应。分三种情况:
第一种,页面报错,提示类似You have an error in your SQL syntax,这说明单引号被直接拼进了SQL语句,输入存在字符串型注入。
第二种,页面返回"用户名或密码错误",没有报错。这可能是错误信息被隐藏了,也可能是参数被过滤了。此时可以用浏览器开发者工具看网络请求的具体响应码和响应体,或者换一种方式继续判断。
第三种,页面跳转到登录成功界面。这种属于"撞大运",说明单引号产生了注释效果,但概率很小,不能作为判断依据。
确定存在注入后,再测一遍闭合方式。这里有个小技巧:输入admin'试试,如果报错信息和之前单引号不同,说明后端确实把输入拼了进去。然后再输入admin' --,如果页面不再报错误信息,而是变成"登录失败",那基本确认闭合方式是单引号,注释符也生效了。
3.3 第三步:万能密码构造与登录验证
闭合方式确认之后,就可以正式构造万能密码了。我会按优先级依次尝试以下payload,每试一次都观察响应变化:
第一个是注释截断型:
admin' --把这个放进用户名框,密码框随便填一个值。如果登录成功,说明后端没过滤关键字、而且表里存在admin用户。这个payload最简洁,成功率也高。注意--后面要有一个空格,否则某些数据库环境会解析失败。
第二个是恒真条件型:
' or 1=1 --如果上一个失败,这个需要重点尝试。很多题目环境会故意让你在"绕过密码校验"和"直接查询非空用户"之间选一条路,而恒真条件型是普适性最强的。
第三个是带闭合符的完整版本:
1' or '1'='1' --这个payload在拼接后会产生username='1' or '1'='1'的恒真条件,效果和第二种类似,但多了一个前缀1,可以有效应对某些代码里对输入做了截断或类型转换的情况。
第四个是MySQL的#注释版本:
' or 1=1#如果--在这个环境里不生效,可以试试#。
我个人的经验是:不要一次性把payload都堆上去试,而是从最简到复杂逐个观察。每换一个payload都刷新一次页面,避免浏览器缓存带来的干扰。如果某个payload让页面跳转到了登录后界面,立刻查看响应包里的内容,flag往往就在登录成功后的页面里。
3.4 第四步:拿Flag与提交
题目要求在登录成功后找到flag并提交。拿到flag之后,先确认它的格式。CISP-PTE的flag通常是flag{...}的字符串,可能在登录成功页面的HTML源码里,也可能在响应头里,还有可能有跨页面跳转,需要跟着重定向过程才能看到。
我遇到过一次比较坑的情况:登录成功后页面跳转到了home.php,我直接看响应包只看到一个302跳转,就以为登录没成功。后来用Burp的"Follow redirection"功能把跳转打开,才在home.php的响应体里看到flag。所以拿到任何阶段性的结果,都要习惯性地右键看完整响应包,不要只盯着浏览器页面。
如果登录成功却没有flag,还有一种可能:flag藏在Cookie里或者需要通过访问某个特定文件才能获取。这时候可以用Burp的站点结构功能看看登录后的目录里有没有其他文件。
4. 常见问题与排查技巧实录
4.1 万能密码"失效"的三个常见原因
练习和实战中,我见过太多人栽在同一个坑里。第一个常见原因是注释符用错。前面提到过,MySQL支持--和#,SQL Server和Oracle支持--但不认#。很多人在本地MySQL靶场里用顺手了#,换到考试环境登录失败,就以为payload不对。遇到这种情况,先确认后端数据库类型,再决定用哪种注释符。
第二个常见原因是--后面没加空格。MySQL的--注释符要求在注释内容之前有一个空格或控制字符。admin'--和admin'--是两种完全不同的情况,前者在某些模式下会直接报语法错误,后者才能正确注释。
第三个常见原因是后端对引号和关键字做了替换或过滤。有些题目环境会在拼接之前把'替换成空字符串,或者把or、and、--等关键字拦截掉。这时候万能密码不能硬套,需要结合绕过手法。比如用/**/替代空格、用||替代or、用1=1的二次编码形式做尝试。
我整理了一个快速排查表:
| 现象 | 可能原因 | 排查建议 |
|---|---|---|
| 登录失败,无报错 | 注释符不兼容 | 更换为#或--再试 |
| 报SQL语法错误 | --后缺空格 | 改为--(带空格) |
| 输入单引号后报错消失 | 输入被过滤 | 尝试URL编码或双写绕过 |
输入' or 1=1后仍失败 | 闭合方式不是单引号 | 尝试双引号或数字型闭合 |
4.2 过滤了空格和关键字怎么办
考试环境和真实靶场里,为了增加难度,常常会加上过滤规则。最常见的过滤手段是删除空格、删除or/and关键字、删除注释符。这时候万能密码的构造就要跟着改变。
空格被过滤时,可以用替代字符。MySQL里可以用注释符包裹空格,常见写法/**/,比如:
'/**/or/**/1=1/**/--也可以用制表符、换行符等空白字符替代,不过现实题目的过滤器未必能识别全部,得现场测试。
or关键字被过滤时,可以用等价写法。MySQL里||可以作为OR逻辑运算符使用,所以:
' || 1=1 --这个payload在部分环境里能成功规避关键字拦截。
还有一种思路是十六进制编码绕过。如果后端做了简单的关键字黑名单,尝试把1=1写成1%3d1的URL编码形式,或者把关键字拆开,比如o%72。不过这类操作得谨慎,编码方式必须匹配后端实际接收的参数格式。
4.3 万能密码之外的延伸:同一考点的变体
万能密码这个考点在真实世界中还有一个延伸方向,就是"逻辑漏洞中的登录绕过"。有些系统根本不用SQL注入,而是纯粹因为逻辑判断顺序出了问题。比如代码先把用户名查出来放数组里,再单独比对密码,但如果查询结果为空,数组是空数组,某些语言的"空数组为假"特性和"非空判断"写反了,就会导致明明没有正确密码也能登进去。
CISP-PTE考试中不排除在这类题目里变体考察。我的建议是:在做登录框相关题目时,不要只局限于SQL注入payload,还要顺手观察登录请求的逻辑流程。如果SQL注入的几个常规payload都不管用,试着用一组不存在的用户名加任意密码,观察响应是否有异常。很多事情是打开思路之后,答案自己蹦出来的。
另外,有些版本的登录框用的是参数化查询,SQL注入无效。但代码里如果同时存在AND/OR乱用的情况,仍然可能出现逻辑绕过。这时候万能在的已经不是密码,而是你对代码逻辑的理解力。
5. 一点个人体会
CISP-PTE的SQL注入系列题目,到万能密码这里其实是在逼你跳出"逐条试payload"的舒适区。你不再只是把SQLMap跑出来的结果抄上去,而是必须理解语句结构、闭合方式、注释符和数据库差异。这种能力在真实渗透测试里非常值钱:很多目标系统的登录框就是用了十几年前的拼接写法,你用admin' --三秒钟登进去,远比花一个多小时爆密码哈希有意义。
我在练习这道题时最大的收获,就是养成了每次拿到登录框都先看请求包、先判断闭合、再试payload的习惯。这个流程一开始觉得繁琐,但练熟之后,效率反而比直接甩SQLMap高得多——因为SQLMap在面对一些特殊过滤规则时,启动慢、误报多,手测反而更快更准。
如果你正在准备CISP-PTE,我的建议是不要只在题库里刷万能密码的题目,把DVWA、Pikachu、SQLi-LABS里所有和登录绕过相关的关卡都亲手做一遍。重点不是记住payload,而是感受不同环境对payload的约束:这个环境为什么注释符只认#?那个环境为什么||能替代or?搞清楚这些之后,考试里再遇到什么变体你都不会慌。
最后分享一个小技巧:每次登录成功之后,不要急着走,先把Burp的HTTP History里这一整个HTTP事务从第一行看到最后一行。很多时候flag藏的位置会让你意外——可能在重定向跳转的Location头里,可能在一个不起眼的Set-Cookie字段中,也可能在登录成功后的JS脚本里。把这一条流程完整看清,你就真正吃透了这道题。