1. 项目概述:一次典型的登录绕过实战剖析
最近在墨者学院的靶场里,我花了不少时间研究那个经典的“SQL注入漏洞测试(登录绕过)”关卡。这其实是一个教科书级别的场景,模拟了无数真实网站后台登录验证的逻辑。简单来说,就是你面对一个登录框,输入用户名和密码,后端程序会拿着这些数据去数据库里查询匹配。如果存在匹配的记录,就让你登录成功。问题就出在这个“查询”上——如果程序没有处理好你输入的内容,攻击者就能在用户名或密码框里“夹带私货”,输入一些特殊的SQL代码片段,从而改变整个查询语句的逻辑,最终实现“无密码登录”甚至“任意用户登录”。这个靶场正是为了让大家亲手体验并理解这种攻击是如何发生的,以及其背后潜藏的惊人破坏力。无论你是刚入门网络安全的新手,还是想巩固Web安全基础的老兵,通过这个靶场的实战,都能把SQL注入登录绕过的原理、手法和防御要点吃得透透的。
2. 核心原理:SQL注入如何“骗过”登录逻辑
要理解登录绕过,首先得明白一个正常的登录验证后台是怎么工作的。我们假设一个最简单的场景,后端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) { // 登录成功 echo "Welcome, " . $username; } else { // 登录失败 echo "Invalid username or password!"; }这段代码的逻辑很直接:从用户提交的表单里拿到username和password,然后拼接成一条SQL查询语句。如果数据库的users表里存在一条记录,其username字段和password字段的值分别等于用户输入的值,那么查询就会返回结果,程序就认为登录凭证正确。
漏洞的根源在于“拼接”。程序把用户输入的数据,原封不动地当作了SQL语句的一部分。如果用户输入的不是一个普通的用户名,而是一段精心构造的字符串呢?比如,在用户名输入框里输入:admin' --。
此时,拼接后的SQL语句就变成了:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '$password'在SQL中,--是单行注释符,它意味着后面的所有内容都会被数据库忽略。于是,这条查询的实际执行部分就变成了:
SELECT * FROM users WHERE username = 'admin'它完全忽略了密码检查!只要数据库里存在用户名为admin的记录,无论密码输入什么(甚至不输入),这条查询都会成功返回结果,从而绕过登录验证。这就是最经典的“单引号闭合+注释绕过”手法。
注意:这里使用的是
--(注意后面有个空格),在某些数据库(如MySQL)中,注释符可能需要空格。也有使用#作为注释符的情况,这取决于数据库类型。
3. 靶场实战:手把手拆解墨者学院登录绕过关卡
理论懂了,我们直接上靶场操作。墨者学院这个环境通常模拟的是一个存在漏洞的登录页面。
3.1 初步探测与漏洞确认
首先,打开靶场提供的登录页面。它的样子可能很普通,就两个输入框:用户名和密码。
第一步:尝试万能账户一个常见的试探是使用一些著名的“万能”Payload。在用户名和密码框都尝试输入:
' or '1'='1' or 1=1 --admin' --
如果输入admin' --后,密码框随意输入或留空,点击登录竟然成功了,直接跳转到了后台,那么漏洞就确凿无疑了。这证明后端查询语句的username字段存在基于字符串的注入点,并且没有过滤单引号和注释符。
第二步:判断注入类型如果上面的简单Payload没成功,我们需要进一步判断。这涉及到区分数字型注入和字符型注入。
- 字符型注入:SQL语句中,用户名被单引号包裹,如
WHERE username = '$input'。我们需要先闭合前引号,再构造Payload。上面用的admin' --就是针对字符型的。 - 数字型注入:如果用户名字段在数据库是数字ID(虽然登录不常见),语句可能像
WHERE id = $input,没有引号。此时Payload可以尝试1 or 1=1。
对于登录绕过,绝大多数是字符型注入,因为用户名通常是字符串。我们可以通过输入一个单引号'来测试。如果页面返回了数据库错误(如“You have an error in your SQL syntax”),这反而是一个好消息,它明确告诉我们存在SQL注入漏洞,并且程序将错误信息直接返回给了用户(即开启了错误回显)。如果页面只是登录失败,没有任何提示,则可能是盲注,需要更复杂的布尔逻辑来判断。
3.2 构造绕过Payload的进阶思路
最简单的注释绕过可能被一些基础的过滤器拦截。我们需要一个Payload武器库。
1. 经典注释绕过:
admin' --admin' #(MySQL中#也是注释符,注意URL中#是锚点,可能需要编码为%23)admin'/*任意内容*/(多行注释,可以绕过对--和#的过滤)
2. 逻辑运算符绕过:利用OR逻辑让查询条件恒真。
' or 1=1 --' or 'a'='a原理是:WHERE username = '' or 1=1 -- ' AND password='...'。由于1=1永远为真,所以整个WHERE条件恒真,查询会返回表中的所有用户(通常是第一条)。如果应用程序取查询结果的第一条记录作为登录用户,你就能以第一个用户的身份登录。
3. 联合查询绕过(需要知道字段数):如果页面在登录成功后会显示用户名等信息,我们可以尝试用UNION SELECT来直接伪造查询结果。 首先需要判断查询的字段数。通过ORDER BY来猜测:
- 输入
admin' order by 1 --,正常。 admin' order by 2 --,正常。admin' order by 3 --,报错。 说明原查询返回2个字段。那么我们可以构造:' union select 'admin', 'hashed_password' --这里我们需要知道密码的哈希值才能完全伪造,但有时程序只检查用户名是否存在,不严格校验密码哈希,用任意值也可能绕过。更常见的是用联合查询来探测数据库信息(如表名、列名),为后续攻击做准备。
4. 报错注入利用:如果页面会显示SQL错误,我们可以利用报错函数来提取信息。例如在MySQL中:admin' and updatexml(1, concat(0x7e, (select database()), 0x7e), 1) --这会在登录失败的错误信息中,暴露出当前数据库的名称。这在登录绕过场景中属于“意外收获”,主要目的是信息收集。
3.3 实操过程与结果分析
在墨者学院的靶场中,我们大概率会使用最简单直接的Payload就能成功。假设我们输入:
- 用户名:
admin' -- - 密码:(任意输入,比如
123)
点击登录。预期的结果是,页面不会提示“密码错误”,而是直接跳转到一个新的页面,可能显示“登录成功,欢迎管理员!”或者直接进入后台管理界面。
背后的数据库查询发生了什么?后端程序原本期待的语句是:
SELECT * FROM users WHERE username = '[用户名]' AND password = '[密码的MD5哈希]'由于我们输入了admin' --,语句被构造为:
SELECT * FROM users WHERE username = 'admin' -- ' AND password = '202cb962ac59075b964b07152d234b70'数据库引擎执行时,看到--就忽略了其后所有的内容。因此它实际执行的是:
SELECT * FROM users WHERE username = 'admin'只要users表里存在用户名为admin的记录,这条查询就会返回该用户的所有信息(包括用户ID、邮箱、哈希密码等)。后端程序检查到查询结果集不为空,便判定为登录成功,并将这条记录(通常是结果集的第一条)赋值给当前会话的用户身份。于是,我们便在没有正确密码的情况下,成功以admin的身份登录了系统。
实操心得:在实际测试中,如果
admin账户不存在,可以尝试常见的其他用户名,如administrator、root、test等。也可以结合or 1=1这类Payload,直接获取数据库中的第一个用户。我曾在一个测试中,用' or 1=1 limit 1 --成功登录,发现是某个普通员工的账户,这也证明了漏洞的危害性——攻击者可能以任意用户身份进入。
4. 深度挖掘:从登录绕开到数据库信息泄露
一次成功的登录绕过远不是终点。对于一个攻击者而言,这仅仅是拿到了进入“大厅”的权限。真正的“宝藏”在后面的数据库里。我们可以利用这个注入点,进行更深层次的信息探测,这通常被称为“基于联合查询的注入信息收集”。
第一步:确定字段数量我们已经知道可以用ORDER BY。在用户名框输入:admin' order by 5 --,如果报错,就降低数字,直到不报错为止。假设order by 3报错,order by 2正常,说明查询结果返回2列。
第二步:确定字段回显位置使用UNION SELECT来让数据库同时执行我们自定义的查询,并将结果展示在页面上。构造Payload:' union select 1,2 --或者对于字符型,需要闭合引号:admin' union select 1,2 --提交后,观察登录结果页面。如果页面上原本显示用户名的地方出现了数字“1”或“2”,或者页面某处显示了这些数字,就说明该位置可以回显我们查询的数据。例如,“欢迎,1”或“您的ID是:2”。这为我们后续输出数据库信息提供了“屏幕”。
第三步:获取数据库信息假设数字“2”在页面上显示了出来。我们就可以把“2”这个位置,替换成我们想查询的数据库函数。
- 查询当前数据库名:
admin' union select 1, database() --页面上可能会显示“欢迎,1”和“您的ID是:mozhe_db”(假设数据库名是mozhe_db)。 - 查询数据库版本和用户:
admin' union select 1, version() --admin' union select 1, user() -- - 查询所有表名(以MySQL为例):
admin' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database() --这可能会爆出一串表名,如users,articles,config。 - 查询特定表(如users)的列名:
admin' union select 1, group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users' --可能会得到id,username,password,email。 - 最终拖库,获取所有用户名和密码:
admin' union select group_concat(username), group_concat(password) from users --这样,页面上可能会一次性显示所有用户的用户名和密码哈希值,造成灾难性的数据泄露。
这个过程清晰地展示了一个简单的登录绕过漏洞,如何像滚雪球一样,演变成整个数据库沦陷的全过程。墨者学院的靶场可能不会让你走完所有步骤,但理解这个链条至关重要。
5. 防御策略:开发人员如何筑起防线
作为开发者,绝不能抱有“我的网站小,没人攻击”的侥幸心理。SQL注入的防御是Web开发的必修课,且手段已经非常成熟。
1. 使用参数化查询(预编译语句)这是根治SQL注入的“银弹”。它的原理是将SQL代码和用户数据分开发送给数据库服务器。数据库先编译SQL语句的结构(一个模板),然后将用户输入的数据仅仅作为“参数”传入,无论参数里包含什么特殊字符,都会被当作纯粹的数据来处理,而不会被解释为SQL代码。
- PHP (PDO):
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password"); $stmt->execute(['username' => $username, 'password' => $hashedPassword]); - Python (SQLAlchemy):
stmt = text("SELECT * FROM users WHERE username = :u AND password = :p") result = conn.execute(stmt, u=username, p=hashed_password)
只要坚持使用参数化查询,就能从根本上杜绝拼接字符串导致的注入。
2. 对输入进行严格的过滤和转义如果因为历史遗留问题必须拼接字符串(强烈不建议),那么必须对用户输入进行转义。例如,在MySQL中可以使用mysqli_real_escape_string()函数。它会将特殊字符(如单引号)前面加上反斜杠,使其失去特殊含义,变成普通字符。
$username = mysqli_real_escape_string($conn, $_POST['username']);但请注意,这不是万无一失的,且依赖于数据库字符集,容易被“宽字节注入”等高级技巧绕过。它应作为参数化查询不可用时的最后手段,而非首选。
3. 遵循最小权限原则为Web应用程序连接数据库的账户分配最小的、必要的权限。例如,这个账户可能只需要对users表有SELECT权限,而不需要DROP、UPDATE、DELETE甚至是SELECT * FROM information_schema的权限。这样即使发生注入,攻击者能造成的破坏也有限,无法获取数据库结构信息或修改数据。
4. 自定义错误处理永远不要将数据库的原始错误信息直接显示给用户。这些信息(如数据库类型、表结构、SQL语句片段)是攻击者的“指路明灯”。应该配置统一的、友好的错误页面,记录详细的错误日志到服务器后台,供管理员排查。
5. 使用Web应用防火墙(WAF)WAF可以作为一道前置防线,根据规则库过滤常见的攻击Payload,如包含union select、sleep(、benchmark(等特征的请求。但它是一种缓解措施,不能替代安全的代码。
6. 定期安全审计与渗透测试对代码进行人工或自动化工具(如SQLMap,但需在授权范围内使用)的扫描,定期进行渗透测试,主动发现潜在漏洞。
6. 常见问题与排查技巧实录
在学习和实战SQL注入登录绕过时,你肯定会遇到各种“坑”。下面是我总结的一些常见问题及解决思路。
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
输入'单引号后,页面直接报“非法字符”或没有任何反应。 | 前端或后端做了基础的输入过滤或验证。 | 1.检查前端:查看网页源代码,看是否有JavaScript过滤函数。可以尝试禁用浏览器JS后提交。2.尝试编码绕过:将单引号'URL编码为%27,空格编码为%20或+。3.尝试双写绕过:如果过滤是删除关键字,尝试ad'min'(过滤掉单引号后变成admin)。4.尝试大小写/混淆:Or、OR、oR、UnIoN等。 |
使用--注释符无效,但输入'会报SQL语法错误。 | 数据库可能是其他类型,如Oracle使用--,Access可能不支持。或者注释符后需要换行。 | 1.尝试其他注释符:#(MySQL),在URL中需写为%23。/*注释内容*/(多行注释,通用性较强)。2.尝试逻辑闭合:不使用注释,而用' or '1'='1来让整个条件永真。 |
联合查询union select被执行了,但页面没有回显数字。 | 页面可能不会直接输出查询结果,或者输出位置不显眼(如页面标题、隐藏标签、响应头)。 | 1.查看页面源代码:数据可能被插入到HTML注释、<input>的value值或JavaScript变量中。2.尝试报错注入:如果页面会显示错误,用updatexml()、extractvalue()或floor(rand())等方式让错误信息包含我们需要的数据。3.尝试时间盲注:如果页面只有“登录成功/失败”两种状态,可以用' and sleep(5) --来判断。如果页面响应延迟了5秒,说明注入存在且可被利用。 |
| 知道是盲注,但手工构造Payload太慢。 | 手工测试布尔型或时间盲注效率极低。 | 在获得授权的前提下,可以使用自动化工具,如SQLMap。命令示例:sqlmap -u "http://target.com/login.php" --data="username=admin&password=123" --level=3 --risk=2 --technique=B。它会自动探测注入点、数据库类型,并提取数据。切记:仅用于授权的测试环境! |
| 成功登录后,不知道下一步该干嘛。 | 对渗透测试流程不熟悉。 | 登录后,应进行权限测试和信息收集:1. 查看后台有哪些功能点(文件上传、用户管理、数据查询)。2. 尝试越权操作(如修改他人信息)。3. 寻找是否存在其他漏洞(如XSS、CSRF)。4. 结合获取的数据库信息,尝试解密密码哈希(如果是弱哈希如MD5),或寻找其他可利用的数据。 |
个人踩坑心得:
- 别忽视“隐形”的回显:有一次测试,
union select 1,2,3后页面一片空白。后来查看源码,发现数字“2”被直接写进了一个隐藏的<div id="userid">里,前端通过JS来显示。所以,永远要习惯性地按F12查看源代码。 - WAF的干扰:遇到一些云WAF,可能会拦截常见的
union、select等关键字。这时需要尝试混淆技术,比如用内联注释/*!50000select*/(MySQL特定语法),或者将关键字拆分为sel+ect,再用concat函数拼接。有时甚至需要研究WAF的规则,寻找其盲区。 - 心态要稳:不是每一个登录框都有SQL注入。如果尝试了多种方法都无效,很可能它就是安全的,或者漏洞不在这个位置。安全测试需要耐心和系统性,切忌钻牛角尖。
SQL注入登录绕过是一个看似简单却内涵丰富的漏洞。通过墨者学院这个靶场,我们不仅学会了一个攻击技巧,更重要的是理解了“用户输入不可信”这一安全核心原则。对于开发者,这意味着必须在代码层面筑牢防线;对于安全研究者,这意味着需要一双能发现细微异常的眼睛。技术本身无善恶,关键在于使用它的人。希望这篇详细的拆解,能帮助你真正掌握这项技能,并将其用在正确的地方——保护你的系统,而非攻击他人。