ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SQL注入入门:极客大挑战LoveSQL题解与万能密码原理

SQL注入入门:极客大挑战LoveSQL题解与万能密码原理 做了几年Web方向的东西要说哪个题目最像“SQL注入第一课”我脑子里第一个蹦出来的就是[极客大挑战 2019]LoveSQL。这道题在很多CTF平台上都能见到尤其是BUUCTF几乎成了新人入门SQL注入的默认练习场。题目本身不复杂核心就是“万能密码登录”加“联合查询注入”但你别小看它——登录绕过、注释符闭合、字段数判断、information_schema查库、回显位利用这些SQL注入最基础也最关键的环节你都能在这道题里完整走一遍。这篇文章我会从题目拆解开始把万能密码的原理、手工注入的每一步、以及我踩过的坑和排查思路全部写清楚适合刚接触Web安全、想系统入门SQL注入的朋友。全文所有操作我默认你是在靶场或CTF题目环境中进行别拿去对别人的线上系统做测试这是底线。1. 题目拆解LoveSQL到底在考什么1.1 一个登录框能有多少戏这道题的页面形态非常简单就是一个登录框用户名字段加密码字段长得和日常网站没有任何区别。但你结合题目名字LoveSQL就能猜到这题的核心就是让你对登录逻辑做SQL注入。登录框为什么是SQL注入最常见的入口因为很多后端代码会直接把用户输入拼进SQL语句。比如用户名输入“admin”密码输入“123456”后端可能会拼出这样一条查询SELECT * FROM users WHERE usernameadmin AND password123456;问题在于如果用户名或密码没有经过任何过滤、转义那你在输入框里写的任何内容都会被当作SQL语法的一部分去执行。这就是注入点的来源。LoveSQL考的不是暴力破解而是让你通过“闭合引号”和“改变查询逻辑”要么绕过后端校验直接拿到登录态要么通过联合查询读出整个数据库的内容。对于新手来说这道题最友好的地方在于它几乎没有做什么过滤也不存在盲注那种让你一次只能猜一个字符的痛苦。只要你会拼SQL就能把库名、表名、字段名、数据一行行读出来反馈非常直观。1.2 考点清单与难度定位这道题的定位非常清晰入门级、基础型、适合第一次正经做SQL注入的人。它覆盖的考点主要包括字符型注入的判断与单引号闭合MySQL注释符的使用#、--空格order by 确定字段数量union select 联合查询获取回显information_schema 元数据库的查询方法group_concat 聚合函数批量获取数据万能密码登录绕过的基本原理如果你之前只在DVWA的low级别里点过几个按钮或者在Pikachu靶场里跟着教程走了一遍那LoveSQL正好可以帮你把这些零散的知识点串成一条线。它不像盲注那么磨人也不像堆叠注入那样需要额外条件属于那种“一理通百理明”的经典题。1.3 复现环境的准备思路虽然题目本身就在平台上可以访问但我建议你本地也搭一套靶场环境来配合练习。常见的几套包括DVWA、Pikachu、SQLi-Labs它们都有和LoveSQL非常相似的注入关卡。我的习惯是准备三样东西一套本地或线上的CTF靶场题目LoveSQL本身也行浏览器开发者工具F12看请求和响应Burp Suite用来抓包、改包、重放请求Burp Suite对做注入题太重要了。你直接在浏览器地址栏里测试经常会遇到URL编码、空格处理之类的问题而在Burp的Repeater里你可以直接看到原始请求包改起来也方便。新手不用一上来就学什么高级用法只要会抓包、会重放就足够对付这类题目。2. 万能密码登录原理和实操2.1 登录逻辑背后的SQL拼接万能密码是很多人接触SQL注入的第一印象“用户名输入admin or 11#密码瞎打就进去了。”听起来很神奇但你一旦理解了后端的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) { // 登录成功 } else { // 登录失败 }问题就出在 $username 和 $password 是直接把用户输入的内容原样拼进了SQL字符串。你在用户名输入admin or 11#那最终拼接出来的SQL就是SELECT * FROM users WHERE usernameadmin or 11# AND password任意内容;仔细看这条语句。单引号先闭合了username字段前面的引号然后 or 11 让条件恒真# 号则把后面 AND password任意内容 全部注释掉。所以这条SQL实际执行的是SELECT * FROM users WHERE usernameadmin or 11;因为 11 永远为真整个WHERE条件恒真数据库会返回表中所有用户记录。mysqli_num_rows 只要大于0后端就认为登录成功了。这个思路就是万能密码的底层逻辑——不是真的有什么万能密码而是让查询条件恒为真。2.2 三条万能密码的构造思路理解了原理之后万能密码可以有很多种写法。我整理几个最常用的用户名密码最终SQL效果admin or 11#任意注释掉密码判断条件恒真 or 11#任意查询所有用户条件恒真admin -- x任意-- 后面的内容被注释条件恒真第一条最经典admin本来就是一个常见用户名用 or 11 让条件恒真。第二条更“万能”因为连用户名都随便填关键是单引号能闭合前面。第三条用了另一个注释符 --注意MySQL里 -- 后面必须跟一个空格或者控制字符我写成了 -- x 来保证安全。实际操作的时候你可能会遇到一个问题如果用户名输入 or 11#密码留空最终SQL变成SELECT * FROM users WHERE username or 11# AND password;这里 # 依然会把后面的内容注释掉。所以登录成功与否实际上只取决于WHERE条件能不能被构造成恒真。这是这一类题目的核心逻辑。2.3 这里容易踩的三个坑万能密码听着简单但实际操作中我见过太多新手在这里卡住基本上就是下面几个原因第一个坑是注释符在URL里被编码。如果你直接在浏览器地址栏或者某些表单里提交 admin or 11# # 在URL中本来就是片段标识符会导致请求被截断传到后端的实际内容只到#之前。解决办法是把 # 编码成 %23也就是提交 admin or 11%23 。第二个坑是搞错注释符要求。MySQL里 -- 注释符后面需要有一个空格很多人直接写 admin -- 结果--后面没空格注释不生效SQL语法就一直报错。题做得多了你就会发现大家在CTF里喜欢用 -- 来代表 -- 加空格因为 号在URL里会被解析成空格。第三个坑是后端如果做了转义或者过滤万能密码会失效。比如把单引号转义成 或者检查输入是否包含 or/and那原来的payload就不灵了。LoveSQL这道题没有这么强的过滤但你在做DVWA或其他靶场时很可能就会遇到类似限制到时候需要换用注释符变种、大小写绕过、内联注释等技巧。3. 从万能密码到联合查询一步步读库3.1 判断注入点与闭合方式LoveSQL这道题的价值不止于登录绕过。很多新人登录进去之后就觉得“完事了”其实还差得远。这道题的完整通关思路是先通过注入拿到数据库里存储的用户名和密码甚至拿到整个库的数据。第一步是确认注入点。在用户名输入框里输入一个英文单引号例如1如果页面返回SQL语法错误比如类似 You have an error in your SQL syntax 的信息那就说明输入被直接拼进了SQL语句而且单引号没有被过滤。这正是我们想要的字符型注入点。然后输入1 #如果页面恢复正常说明 # 成功注释了后面的内容也进一步确认了闭合方式就是单引号。走到这里基本可以确定后端SQL是这种结构SELECT * FROM users WHERE username输入内容 ......;3.2 用 order by 摸清字段数量联合查询的前提是你得知道目标表里有多少个字段。最常用的方法就是 order by 逐渐增加数字观察什么时候会报错。具体操作是在用户名里输入1 order by 1# 1 order by 2# 1 order by 3# 1 order by 4#如果 order by 3 时页面正常order by 4 时报错那就说明当前查询的字段数是 3。报错信息通常会提示类似 Unknown column 4 in order clause意思是你排序的字段4根本不存在。为什么 order by 能测出字段数因为 order by 后面跟的数字本质上是按查询结果的第几列排序。如果查询结果只有3列你让它按第4列排序数据库自然就报错了。这是SQL注入里最经典的探测手法几乎所有联合查询注入都会用到。提示注意每条payload结尾的 #它用来注释掉SQL语句末尾可能存在的其他条件。如果你用的是URL方式提交记得把 # 写成 %23。3.3 union select 确定回显位置知道了有3个字段接下来就要确定哪一列的数据会显示在页面上。我们用联合查询来测试1 and 12 union select 1,2,3#这条payload的逻辑分两部分。前半部分 1 and 12 肯定是假因为 1 不等于 2所以前面那条查询不会返回任何数据。后半部分 union select 1,2,3 会把我们自己构造的一行结果显示出来。页面上如果能看到数字 1、2、3那这些位置就是可以利用的回显位。为什么要用 and 12因为 union 默认会合并两个查询的结果。如果前面那条查询本身就有结果那么页面上显示的是前面的内容我们自己构造的123可能就看不到了。让它查不到任何数据union 后面的内容自然就“顶上来”了。实际操作时页面上可能出现“1”“2”“3”这三个数字也可能只出现部分。比如只看到2和3那说明只有第二列和第三列有回显后面拼接payload时就要把查询的内容放在第2位或第3位。3.4 获取当前数据库名有了回显位置就可以开始读数据了。先从最简单的开始获取当前数据库名。把回显位第二列替换成 database() 函数1 and 12 union select 1,database(),3#登录成功后的页面上回显位置2就会显示当前数据库名。以这道题常见的环境为例你很可能看到类似 geek 这样的输出。database() 是MySQL内置函数直接返回当前默认数据库的名称不需要任何参数特别适合用来验证自己是不是真的拿到了查询结果。这一步在我看来是整个流程的转折点一旦数据库名出来了后面就可以按部就班去查表、查字段、查数据思路就不会再乱了。3.5 从库名到表名、字段名、数据的完整链路拿到了数据库名接下来就是SQL注入最经典的套娃流程查表名、查字段名、查数据。查当前库的所有表名构造如下payload1 and 12 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()#information_schema 是MySQL自带的元数据库tables 表里记录了所有数据库的表信息。group_concat 会把查询到的多行结果拼接成一行输出方便我们在页面上一次看全。这里我指定了 where table_schemadatabase()意思是只查当前数据库的表避免把所有库的表都列出来结果太长看不完。假设查出来有两个表其中一个叫 users。接着查 users 表的字段名1 and 12 union select 1,group_concat(column_name),3 from information_schema.columns where table_schemadatabase() and table_nameusers#这里用到了 information_schema.columns它记录了所有表的字段信息。我通过 table_schema 和 table_name 两个条件把范围限制到当前库的 users 表再用 group_concat 把字段名拼起来显示。假设字段名有 id、username、password那最后一步就是直接查数据1 and 12 union select 1,group_concat(username,0x3a,password),3 from geek.users#这里我把库名 geek 直接写进去了0x3a 是冒号的十六进制表示用来在输出内容里加个分隔符否则用户名和密码挤在一起很难分辨。如果你嫌0x3a难记也可以用 concat(username,:,password)效果是一样的。当你看到页面上列出类似 admin:xxxxxx 这样的记录时这道题的核心内容已经彻底打通了。记住这些查出来的数据很多题目里的flag就藏在密码字段里或者需要你用这些账号密码继续登录后台。3.6 如果用SQLMap会更快手工注入流程走完一遍之后如果你还想验证自己的判断或者图省事可以考虑用SQLMap来跑一下。基础命令是这样sqlmap -u http://目标地址/login.php --data username1password1 --batch--data 参数指定POST请求体--batch 让它自动选择默认选项不要停下来问你各种问题。SQLMap会自动帮我们检测注入类型、数据库类型、库名表名字段名最后还能直接脱库。但我不建议新手上手第一遍就直接用SQLMap。工具出结果很快可你对每一步背后的原理完全没有感知后面一旦遇到工具绕不过去的过滤、盲注场景就会非常被动。我的建议是手工把LoveSQL整体打通一遍再用SQLMap跑一次对照两种方式的输出结果你会有一种“原来工具就是在自动化做我刚才手动做的这些事”的顿悟感。4. 排错速查半小时内解决常见卡点4.1 常见报错与含义速查表做这道题的过程中新手最常见的报错和异常就那么几种。我整理了一个速查表遇到问题直接对号入座。现象可能原因解决办法Unknown column 4 in order clause字段数小于4最多只有3列把order by数字改小You have an error in your SQL syntax单引号没闭合好或注释符写错检查引号和注释符 # 或 -- 空格页面正常但看不到union的结果前面查询有数据union结果被盖住加 and 12 让前面查询无结果只看到部分数字比如只有2和3部分列不在页面模板中回显把要查的内容放到有回显的列里回显内容里有引号语句报错数据里的引号破坏了外层SQL用0x3a、hex编码或concat拼接万能密码提交后没反应# 在URL被截断改用%23或--注释这些报错不是坏事。SQL注入调试很多时候就是靠报错信息来反推后端语句结构的。你想如果后端不报错你反而不知道自己的payload到底执行到哪一步了。4.2 URL编码与Burp调试技巧在浏览器地址栏直接测试最麻烦的就是特殊字符的处理。# 会被当成URL片段标识符#后面的内容根本不会发到服务器。空格会被浏览器处理成 %20有些时候也需要自己编码成 。所以我的习惯是在Burp Suite的Repeater里测。分三步走浏览器打开题目页面把代理指向Burp登录一次抓到请求包在Repeater里修改 username 和 password 参数点击Send重放看Repeater右侧的响应内容确认页面是否显示了预期数据Repeater的好处是你可以直接看到原始请求手动控制编码和内容。比如你输入username1 order by 3%23password任意在Repeater里%23不会像浏览器那样被截断你能原样发给服务器。响应内容也直接在面板里展示找数据比在浏览器里翻来翻去舒服得多。如果你没有Burp也可以先用浏览器的F12开发者工具在Network面板里找到登录请求右键重新发送改参数再发送。效果类似只是不如Burp顺手。4.3 为什么我的万能密码打不开这个问题我几乎每带一个新人都会遇到一次。明明照着文章写了 admin or 11#怎么就是进不去我一步步排查的思路是这样的。先看闭合对不对后端如果不用单引号包字段而是直接拼接数字型参数那你的单引号反而会破坏SQL语法。但LoveSQL这种登录题用户名密码基本都是字符型单引号闭合是没问题的。再看注释符有没有被处理。如果你在浏览器里测试确认 # 有没有被编码成 %23如果你用 -- 确认后面有没有空格。这两个注释符细节是万能密码失败的Top 2原因。还要看是不是把请求方法搞错了。有些登录框是GET提交有些是POST如果你在Burp里手动改包时把参数位置放错了后端自然接收不到。用原始请求包就最稳浏览器怎么发的你就怎么改。如果以上都没问题那就要怀疑登录逻辑是不是根本不查数据库。有些题目所谓的“登录成功”只是判断用户名是否等于某个写死的值那样的话SQL注入可能不在用户名这里得换思路。但LoveSQL作为基础题一般不会在这种地方为难你。5. 做完这道题以后下一步怎么练5.1 从LoveSQL到DVWA、Pikachu、SQLi-Labs做完LoveSQL你等于把SQL注入最基础的一条主线跑通了。但要真正形成肌肉记忆还需要在更多环境里重复练习。我推荐的路线是这样的先去DVWA把SQL Injection模块的low级别做一遍你会发现在DVWA里测字段数、union查询流程和LoveSQL几乎一模一样唯一区别是DVWA有个安全等级设置网上很多教程写的是low级别。把low做完之后可以试试Medium和High感受一下后端过滤对payload的影响。接着练Pikachu靶场。Pikachu里的SQL注入模块分得比较细数字型注入、字符型注入、搜索型注入、XX型注入都有单独关卡你可以对照着感受不同类型注入的SQL结构差异。最后是SQLi-Labs。这个靶场有几十关前20关基本就是把SQL注入的各种闭合方式轮着考你一遍单引号、双引号、括号、数字型、字符串型全都有。如果你能把SQLi-Labs前20关都独立做出来你再回头看LoveSQL真的就是三分钟以内的事。5.2 给新手的三个学习技巧第一个技巧每条SQL语句都在本地数据库跑一遍。用phpMyAdmin或者命令行连上MySQL把最终拼接出来的SQL自己手写执行亲眼看看每条语句返回了什么结果。我在这一点上受益非常大SQL注入说白了就是研究SQL在不同拼接方式下的执行结果你越熟悉SQL本身注入起来越有底气。第二个技巧动手之前先闭上眼把最终SQL写出来。提交一个payload之前先在纸上把它会拼接成什么样写出来确认没有语法错误再提交。这个过程一开始有点慢但练多了你就能秒判断一条payload能不能成。第三个技巧不要拿到题就先上SQLMap。我知道SQLMap确实酷炫一条命令能帮你跑完很多事。但工具跑完你依然不知道回显位是什么、为什么这个payload能用。这些知识恰恰是后续做布尔盲注、时间盲注、堆叠注入的基础。等到你手工能把基本题做到滚瓜烂熟再用工具提高效率不迟。5.3 一点过来人的体会我在做[极客大挑战 2019]LoveSQL的时候和你们一样卡在最基础的地方。当时我完全不懂为什么 # 在地址栏里总是“消失”后来才知道是URL编码的问题。搞明白之后这条题对我来说就彻底“通”了整个SQL注入的框架也一下子立住了。这种最基础的题目永远值得反复做。不要嫌它简单到后面你打盲注、打过滤绕过的时候会发现所有复杂技巧的根基都是LoveSQL里这些最朴素的payload换了个壳子。建议你动手做题的时候把每一步的payload和页面反馈记成笔记后面你回头看时一定会感谢自己这笔账记得清楚。最后再分享一个小习惯每次做完一道注入题我都会把题目类型、闭合方式、payload、注释符用法整理成一个速查表。攒了二三十道题之后你的速查表就是一份最贴合自己的SQL注入字典以后遇到新题直接查表组合就行比临时翻书快得多。
返回列表