尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SQL注入绕过登录原理与防御:从拼接逻辑到实战靶场

SQL注入绕过登录原理与防御:从拼接逻辑到实战靶场 第一次在 PortSwigger Academy 上做 SQL 注入绕过登录Login Bypass这个实验的时候我其实有点不以为然。万能密码这东西听起来像十几年前的考古内容总觉得在参数化查询、ORM 普及的今天早就没什么实战价值了。但真正动手完整跑完一遍我才意识到这个实验教科书般的分量——它用最简单的一个登录框把 SQL 注入最核心的本质讲透了当后端把用户输入当成可执行代码拼接时所有的“假设”都会被打破。这篇文章适合正在入门 Web 安全、准备刷靶场、或者想理解“为什么不能直接拼 SQL”的后端开发者。我会把登录绕过背后的 SQL 拼接逻辑、PortSwigger 靶场的实操步骤、常见卡点以及防御侧的写法串一遍尽量让零基础的人也能照着复现整个流程。如果你已经能熟练手打 payload也可以重点看第 3 部分的注释符坑和第 5 部分的代码审计思路。1. 先看明白登录绕过的本质一句话背后的SQL拼接逻辑1.1 登录校验在后端长什么样很多初学者喜欢一上来就试 payload但试来试去不知道原理换个场景就不会用了。这里先展开一下后端代码的常规写法。假设登录接口收到用户名和密码后拼出这样一条 SQLSELECT * FROM users WHERE username 用户输入 AND password 密码输入问题在于如果代码是用字符串直接拼接构造这条 SQL你输入的所有字符都不再是“数据”而是会参与语法解析的“代码”。这句话是整个 SQL 注入的根基。我在实验里的思考方式是先模拟常见的后端写法String query SELECT * FROM users WHERE username username AND password password ; Statement stmt connection.createStatement(); ResultSet rs stmt.executeQuery(query);然后问自己一个关键问题如果 username 的内容是administrator--拼接后的 SQL 会变成什么样SELECT * FROM users WHERE username administrator-- AND password xxx在 SQL 里--是行注释符从它开始到行尾的内容全部被忽略。于是真正被数据库执行的就等价于SELECT * FROM users WHERE username administrator只要 users 表里存在 administrator 这个用户数据库就会把这条记录返回给应用层。应用层拿到结果集发现里面有数据就认为认证通过了。密码这一环从头到尾根本没参与判断。1.2 为什么一条注释就能改变判定结果这里有两层原因叠加在一起缺一不可。第一层典型的认证代码逻辑是“查询到了行就算登录成功”。Java 里通常就是rs.next()返回 true 就放行很少会再去校验查询出来的记录到底和输入凭据相差多少。这个逻辑本身不算错但前提是 SQL 必须按预期执行。第二层字符串拼接让单引号变成了闭合符。你输入的恰好把 SQL 里的第一个单引号补上了让 username 的值域闭合然后--把后面所有内容消音。两者组合WHERE 子句就只剩下“用户名等于 administrator”这一条过滤条件。可以打一个生活化比方后端 SQL 就像门卫的登记逻辑。门卫本来要检查“姓名 通行证”但你在说话时带了一个特殊符号让门卫眼里只剩姓名通行证被自动无视了。这就是 SQL 注入绕过认证的本质——它不是暴力破解也不是伪造 Cookie而是直接篡改了门卫的判断规则。1.3 相比万能密码这个实验更推荐哪种构造网上搜 SQL 注入绕过登录出现最多的 payload 是万能密码 OR 11--。这个确实能用但在这个实验里我更推荐先试administrator--原因到第 3 部分细说。这里先列出两种构造的执行效果对比Payload拼接后的效果返回结果administrator--WHERE 子句只校验用户名精确匹配 administrator只返回目标用户记录 OR 11--WHERE 子句变成永真条件返回所有用户记录返回整个表结果集顺序决定你是谁注意第二种的不确定性如果应用层只用结果集的第一条记录做登录态而表里第一条恰好是普通用户 wiener那登录成功的会话就是 wiener而不是管理员。做实验时既然知道 administrator 这个用户名一定存在用精确匹配显然更稳。2. 靶场准备PortSwigger入口与Burp Suite调试细节2.1 定位实验并理解目标打开 PortSwigger Academy 的 SQL injection 主题找到标题类似SQL injection vulnerability allowing login bypass的实验。PortSwigger 的每个实验都写明了学习目标和验证标准这个实验的核心就是通过 SQL 注入绕过登录以管理员身份进入后台管理页面并删除用户 carlos。很多人做到登录成功就以为结束了实际还没通关。登录只是入口实验的最终目标和真实攻击链路是一致的拿到管理员身份后去后台执行一个敏感操作。这个过程能帮你理解为什么“绕过了登录”往往是更严重攻击的第一步而不是终点。进入实验页面后你会看到一个标准的登录表单通常还有注册入口和我的账户页面。先正常浏览一遍用任意账号密码试一次登录把报错信息记下来。这些响应差异后面都是判断注入是否生效的参照系。2.2 Burp Suite 配置与浏览器代理实际操作时我建议先打开 Burp Suite Community 或者 Professional配置好代理监听。步骤并不复杂打开 Burp进入 Proxy 下的 Options确认监听地址是127.0.0.1:8080。给浏览器配置代理指向127.0.0.1:8080Firefox 可以用 FoxyProxy 这类插件Chrome 可以用 SwitchyOmega。如果实验页面是 HTTPS首次访问时浏览器会提示证书错误需要先访问 Burp 的证书下载页面导入它的 CA 证书到系统信任列表。打开实验到登录页随便输入一组账号密码先不急着提交。这里有个细节很多新手会忽略实验页面的登录表单很可能带有 CSRF token。拦截请求后别去动csrf参数它不影响注入点的测试。我见过有同学顺手把 csrf 改成 payload结果请求直接被服务端拒绝白白排查了很久。2.3 抓包后先做信息收集当你看到类似下面的 POST 请求就说明已经抓到登录请求了POST /login HTTP/1.1 Host: xxx.web-security-academy.net Content-Type: application/x-www-form-urlencoded csrfxxxxxusernametestpasswordtest请求里三个参数真正的注入关注点是username和password。绝大多数类似实验里 username 是注入点因为后端拼接 SQL 时几乎总是把用户名放在 WHERE 子句最前面密码紧随其后。构造administrator--就能把后面的AND password...注释掉。另外建议大家先观察一次正常登录的响应密码错误时返回什么状态码用户名不存在时返回什么状态码页面内容有没有差异。这样后面判断注入成功与否会有参照尤其是遇到盲注场景时这种信息收集习惯能救命。3. 完整实操从提交登录表单到进入管理后台3.1 第一步修改 username 参数并发送在 Burp 拦截到登录请求后把username参数的值改成administrator--也就是在单引号后面加两个连字符。注意--后面最好再补一个空格因为某些数据库要求在注释符后面必须是空白符才能正确识别。Burp 里你可以直接发送原文也可以对参数做 URL 编码administrator%27--%20如果用 Burp 发送后看到 302 跳转基本可以判定绕过成功。如果返回 200 且页面有错误提示通常是 payload 构造有问题接着看 3.2 的坑点。这里有一个实操建议不要试图在浏览器表单里直接输入这个 payload。浏览器可能正常提交但不代表所有字符都能完整到达后端有些前端 JS 还有长度限制和字符过滤。更可控的方式是关闭浏览器的代理拦截或直接在 Burp 的 Repeater 里改请求还能保留每一步的响应记录。3.2 第二步处理注释符和编码的坑注释符是新手最容易卡壳的地方。不同数据库的注释规则有差异我把常用的列出来数据库行注释符注意事项Oracle--直接注释到行尾PostgreSQL--直接注释到行尾SQL Server--直接注释到行尾MySQL--必须在--后跟一个空格MySQL#注释到行尾不需要空格PortSwigger 这个实验的后端用的是 Oracle所以administrator--能直接生效。但如果你在本地搭的是 MySQL 环境比如 DVWA、sqli-labs就必须用administrator#或者写administrator--注意最后的空格。另外一个容易忽略的坑是 URL 编码。#在 URL 里原本表示锚点如果塞进请求体时不编码可能被浏览器或代理截断。我在本地 DVWA 练习时payload 里的#都习惯写成%23避免各种工具层面的干扰。Burp 的 Repeater 会自动处理请求体但你在浏览器插件直接提交时最好手动编码一次。3.3 第三步跟随跳转并验证管理员会话如果 payload 用对了服务端通常会返回 302 跳转把浏览器带到类似/my-account?idadministrator的页面。你可以在 Burp 里勾选 Follow redirection 让它自动跟随也可以把响应里的sessionCookie 复制到手动浏览器里。当页面显示“Your username is: administrator”时说明已经成功以管理员身份登录。这时再访问/admin管理面板找到删除用户 carlos 的按钮点击后实验完成页面会显示通关结果。整个过程里有一个值得留意的点登录成功后应用层判断依据是会话里的用户身份而不是“你是否知道密码”。这正好呼应了第 1 部分的原理——认证逻辑被注入改写后整个信任链就崩了。后续在真实场景里从绕过登录到提权、横向移动往往是一条线打下去的靶场只是把第一环做得很干净。3.4 万能密码和指定用户名到底差在哪实际跑完这个实验我建议你把两种经典 payload 都试一遍体会一下差异Payload拼接后的效果优缺点administrator--精确匹配管理员用户最稳但需要知道用户名 OR 11--列出所有用户取第一条不需要知道用户名但结果依赖表内顺序 OR 11恒真条件绕过全部校验老系统常见但同样不精确admin/*注释到末尾配合内联注释MySQL 专用需小心空格单独强调一下 OR 11--如果结果集第一条恰好是 administrator 就没问题但如果第一条是普通用户 wiener登录成功的会话就是 wiener 的最终进不了管理后台。所以在这个实验里明确知道 administrator 存在就用精确 payload不要赌结果集顺序。4. 绕过手法的变种与同类注入面不止登录框4.1 万能密码的经典变体登录绕过的 payload 远不止一种。我把实战中常见的变体整理出来 OR 11--admin OR 11 OR 11 LIMIT 1-- UNION SELECT 1, admin, passwordadmin-- OR 11#MySQL 场景这些变体背后的逻辑都可以归纳为三类要么用注释符屏蔽后半段条件要么构造恒真表达式让 WHERE 失效要么用 UNION 直接拼一个伪造的用户记录出来。第三类对初学者来说稍微复杂一点因为 UNION 查询的列数必须和原查询一致否则数据库会直接报错。我个人的建议是先别急着背 payload把每个 payload 放进那段 SQL 拼接模板里手写一遍拼接结果看它到底改变了哪些条件。能在纸上推演才算真正掌握了登录绕过而不是碰运气试出来的。4.2 搜索框、接口参数与 JSON 里的注入点很多人有个误区觉得 SQL 注入一定发生在登录框。实际在代码审计中搜索框、排序参数、分页参数、批量查询接口才是更常见的高危入口。举一个典型的例子搜索商品时后端把keyword参数直接拼进 LIKE 查询String sql SELECT * FROM products WHERE name LIKE % keyword %;输入% OR 11--会把整张商品表带出来输入% UNION SELECT username, password FROM users--则可能直接拖库。这种搜索框注入比登录框更隐蔽因为登录框大家都盯着搜索框却常常被忽略。还有 JSON POST 接口。现在很多前端用 axios 传 JSONBurp 抓到的请求体是{username:test,password:test}。测试时同样可以直接改 JSON 字段的值需要注意保留好引号结构比如把 username 改成administrator--后整个 JSON 变成{username:administrator--,password:test}提交后效果和表单参数是一样的。4.3 ORDER BY、UNION 注入与登录绕过的差异登录绕过是 SQL 注入最直观的利用方式但它只是冰山一角。等到刷更多靶场实验你会遇到 ORDER BY 注入、UNION 注入、报错注入、时间盲注等变体。理解登录绕过相当于建立了“输入如何改变 SQL 语义”的思维框架后面这些变体都是在这个框架上叠加更复杂的利用技巧。以 ORDER BY 注入为例排序参数通常出现在列表页或管理后台形如GET /admin/products?sortname HTTP/1.1如果把sort改成1、2、3可以通过响应差异判断列数改成(CASE WHEN 11 THEN name ELSE price END)则可以探测条件。它不会直接帮你登录但能为后续 UNION 注入铺路。新手学习路径上我建议按这个顺序刷 PortSwigger 的 SQL 注入实验检索隐藏数据 → 绕过登录 → UNION 注入 → 盲注。登录绕过的位置恰好卡在入门和进阶之间把它彻底吃透比赶进度刷完所有实验更重要。5. 回到防御侧参数化查询和登录校验的正确写法5.1 从根源阻断拼接型注入无论攻击手法怎么变防御的第一位永远是参数化查询和预编译语句。原理很简单SQL 语句的骨架在预编译阶段就已经确定用户输入只作为参数值传入不会再参与语法解析。再用登录场景举例安全的 Java 写法应该是String query SELECT * FROM users WHERE username ? AND password ?; PreparedStatement pstmt connection.prepareStatement(query); pstmt.setString(1, username); pstmt.setString(2, password); ResultSet rs pstmt.executeQuery();在 Python 里对应的是cursor.execute( SELECT * FROM users WHERE username ? AND password ?, (username, password) )在 MyBatis 里关键是区分#{}和${}!-- 安全 -- SELECT * FROM users WHERE username #{username} AND password #{password} !-- 危险 -- SELECT * FROM users WHERE username ${username} AND password ${password}#{}会生成预编译参数${}是字符串拼接。不少团队用 MyBatis 但还是出了注入漏洞基本都是因为在动态排序、动态表名这些场景里图省事用了${}。记住一个原则能用#{}的地方绝不用${}排序字段这类必须动态拼的地方要做严格白名单校验。5.2 补充防御最小权限、输入校验、登录限制参数化查询解决的是注入的根因但纵深防御依然不能少。我在实际项目里通常会做下面这几层层次手段作用数据库层业务账号不用 DBA 权限按库按表最小授权即使被注入也无法拖全库应用层参数化查询 ORM 审计从代码层消除拼接点输入层用户名白名单字符比如只允许字母数字下划线早期拦截脏字符业务层登录失败次数限制、验证码防撞库和自动化暴力破解网络层WAF 规则缓解已知攻击模式但不可依赖特别提醒一点输入校验只是缓解不是根治。你永远不知道 Unicode 编码、宽字节、二次解码能把脏字符变形成什么样子。所以输入校验可以做但不能替代参数化查询。同样WAF 也经常被绕过绕过方式之一就是把 payload 做大小写混合、注释符插入、URL 编码叠加。真正稳的还是代码层不拼接。5.3 代码审计时怎么快速定位登录注入点最后分享一个实战中很实用的审计思路。拿到一套代码想快速找登录注入点我一般按这个顺序扫全局搜createStatement()、stmt.executeQuery()、String query 这类关键词。搜SELECT * FROM users WHERE username 这种字符串拼接特征。搜 ORM 原生查询里的${}特别是在 XML 映射文件中。重点看request.getParameter()或者框架中RequestParam拿到参数后是否直接落进了 SQL 拼接。真实项目里常出现这样的代码String sql select * from user where username username and password md5(password) ;这段代码有个典型特征密码虽然做了 MD5但用户名完全没处理依然可以注入。这说明一个道理——对单个字段做加密或哈希不等于对整个查询做了防护。只要还有字符串拼接注入点就依然存在。我还在某次审计中遇到过用String.format拼 SQL 的写法比如String.format(SELECT * FROM users WHERE username %s, username)本质上也是拼接。这类问题光靠代码规范很难杜绝最好的办法是引入 ORM 和预编译组件的强制约束比如在代码评审里直接禁止Statement相关 API 出现。把 PortSwigger 这个登录绕过实验完整做下来之后我最大的感受是SQL 注入绕过登录不是一个需要背的“绝招”而是一套完整的推理过程。你输入的每个字符都必须能在脑海里映射到后端那条 SQL 语句上。比如单引号负责闭合注释符负责消音连字符后面的空格负责兼容不同数据库——这些细节不是靠聪明而是靠一遍遍手写拼接结果练出来的肌肉记忆。所以我给新手的建议很直接不要急着上 sqlmap 之类的自动化工具先手工把 PortSwigger 这个实验做到闭眼能写出 payload 的拼接过程。等你能解释每个字符的作用再接触自动化工具和更复杂的盲注那时候你看到的就不是一个字符串而是一段可以被推理、被打破、也被防御的查询逻辑。
返回列表