☰
CTF Web困难题解题复盘:SQL注入、文件上传与命令执行的绕过思路
2026/10/9 3:12:24 网站建设 项目流程

在PolarD&N上把[web困难]标签下的题翻来覆去刷了两晚之后,我终于意识到自己一直在犯同一个错误:不是漏洞点太难找,而是排查顺序太乱。第一次卡题时我以为自己SQL注入不够熟,后来才发现漏了一个藏在响应头里的提示字段;第二次以为文件上传姿势不对,结果问题出在根本没找到上传文件的实际落盘路径。Web困难题真正折磨人的地方,从来不是某个单一技术的深度,而是多个知识点之间嵌套、绕过、信息的联动。

这篇文章想把我在PolarD&N这类CTF答题平台上遇到的几类高频卡点,以及最终的绕过思路完整复盘一遍。内容以多解法为主,重点放在“当时为什么会被卡住”和“后来是怎么一步步试出来的”这两个问题上。如果你也在刷Web方向的困难题,或者刚接触CTF不确认该从哪里建立排查体系,这篇应该能帮你少走不少弯路。文中涉及的题目场景均为平台靶场环境,所有操作也仅限于竞赛与授权学习范围内,这点大家心里有数就行。

1. 困难题与简单题的分水岭:过滤程度与信息收集深度

1.1 困难题到底在“难”什么——同样是注入,为什么简单题一测就中

PolarD&N这类平台里,简单Web题和困难Web题给人的体感差异非常明显。简单题更像是“手把手带你入门”,漏洞点常常明晃晃地放在某个请求参数里,你提交一个单引号,数据库的报错就会原样吐出来,再配合一条联合查询就能拿到数据。这种题本质上是让新手确认“漏洞确实是存在的”。

困难题完全换了思路。题目仍然是一个注入点,但后端加了多重校验,你输入的单引号可能被转义,关键词可能被正则替换成空字符串,甚至你提交的整个请求体都会被解析两次,第一次先过滤、第二次才进入SQL语句拼接。目标不是让你“发现漏洞”,而是逼你把过滤规则摸清楚、把语法闭合方式调对、把数据提取链路走通。再加上平台题目的参数往往藏在看似无关的Cookie或Header里,如果你只盯着常规参数,可能连注入点都找不到。

所以困难题的核心难点可以归纳为三点:入口更隐蔽、过滤更严格、利用链更长。简单题给你一个点,困难题给你一条线,而你要先自己找出这条线从哪里开始。

1.2 我反复踩坑后建立的排查顺序:响应优先,参数其次

第一晚在PolarD&N上卡题时,我的习惯是拿到题目就直奔URL参数,手工测一圈引号、报错、注释,发现没反应就直接上扫描器,结果什么都扫不出来,白白浪费了一晚上。后来我强迫自己换了一套顺序:先完整看一遍响应,再碰参数。这里的“完整”包括状态码、响应头、Cookie、HTML注释、JS文件、甚至响应里被隐藏的空字段。

我整理了一份常规的排查清单,现在每次刷题都会先过一遍:

  • 响应头里的自定义字段,常见的有X-Hint、X-Flag-Path这类提示性Header,也有的平台会把提示塞在Server字段末尾
  • 页面源码中的HTML注释,出题人很喜欢把“后端过滤函数名”或者“允许的字符集”直接注释在代码里
  • Cookie中的硬编码参数,比如Cookie: role=guest这类与业务逻辑直接相关的字段,困难题常在这里做文章
  • 静态资源文件(JS、CSS)里的接口路径,很多隐藏的后端接口在JS文件里会留下线索

这套排查顺序的价值在于:它强迫你先读懂出题人的“出题思路”,再开始动手测试。难度较高的Web挑战,答题时间往往不充裕,你越早确定过滤规则,越能避免在后面几步反复做无用功。现在我把这一步命名为“前置信息收集”,每次开始做题的头15分钟只做这件事,不碰任何漏洞测试。

2. 第一类高频卡点:SQL注入的过滤绕过与二次注入触发链

2.1 过滤规则探测:用报错当“探针”,别盲目跑工具

SQL注入题在困难难度下,最大的陷阱就是后端过滤。我在做题时发现,很多人习惯直接把参数丢给sqlmap去跑,但对于平台类题目来说,扫描器跑出来的结果往往只有“存在时间盲注”之类的模糊提示,之后你需要手工调整的绕过逻辑,扫描器反而帮不上忙。

更有效的做法是先在文本层面做过滤探测。拿一个典型的请求来看:

GET /challenge/api/user?id=1' HTTP/1.1 Host: target.polarctf.xxx

如果返回页面出现SQL语法错误,说明引号没有被过滤,此时可以继续测试注释符和闭合方式。如果页面空响应,说明引号被转义或过滤,这时需要改变策略,先判断过滤规则再构造payload。我常用的探测序列是:

  • 提交1',观察报错或空响应,判断引号是否被过滤
  • 提交1' --+,观察SQL语句是否被正确闭合,判断注释符是否可用
  • 提交1' and '1'='1和1' and '1'='2,通过布尔差异确认注入点是否“活着”
  • 提交1' or '1'='1,判断空格和or关键字是否被替换

这一步的核心目的是确认“过滤层到底滤掉了什么字符”。我在实际操作中会把所有尝试过的输入、响应状态、页面特征记录下来,避免重复试同一个方向。这样做看起来慢,但总比盲目猜要快得多。

2.2 绕过变形的基本功:先还原语法,再绕过过滤器

当你确认过滤规则后,真正的解题过程就开始了。假设平台后端做了两件事:把空格替换为空字符串,把union关键字替换为空字符串。那么你的思路应该是先把它还原成“后端能接受的、不触发过滤器的等价写法”,而不是直接原样提交union select。

在平台上我常用的一套绕过逻辑是:

  • 空格被过滤:改用内联注释/**/替代空格,或使用Tab、换行符%0a等空白字符
  • union被过滤:改用大小写混合UnIoN、内联注释包裹union/**/select、双写uniunionon
  • 引号被过滤:改用0x...十六进制编码代替字符串字面量,或用char()函数拼接
  • 注释符被过滤:改用#、--+、--%20、/**/轮着试,总有一个能闭合

举个例子,假设过滤了union和空格,一个查库名的经典payload可以写成这样:

-1/**/UnIoN/**/SeLeCt/**/1,database(),3--+

这里-1用来让联合查询的前半部分返回空集,后面的database()就能把当前数据库名直接带出来。注意我把关键字都做了大小写混合,空格全部换成/**/,注释用的是--+。在我实际做的题目里,这套写法至少成功过一次,而且过程中我会用较小的步长逐步变形,避免一次改太多导致无法判断哪一步出了问题。

绕过过滤的核心原则,用一句话概括就是“先让SQL语句在语法上恢复完整,再考虑如何规避过滤器”。如果你直接套变形模板但SQL语法本身是断的,过滤器就算放行,数据库也会给你一个语法错误。

2.3 二次注入:脏数据入库,业务逻辑里才引爆

有种情况比过滤更难缠——后端做了参数化查询,你提交的注入语句根本不会在第一步被拼接到SQL里。但题目又是SQL注入题,那它十有八九是二次注入。

二次注入的触发链是:你提交的恶意数据先被当作普通数据存入数据库,后面某个业务功能在调用这条数据时,没有做任何过滤或参数化处理,直接拼接进了新的SQL语句。这就好比你把一个“坏零件”装进仓库,当时没出事,但你后来在流水线上使用这个零件时,整条生产线就崩了。

我在PolarD&N上遇到过一道题,流程是这样的:注册功能里有个字段会写入用户名,第一步注册时提交admin' --,数据库正常入库。下一步进入“修改个人信息”页面,程序会把当前用户名拼接进更新语句,于是真正执行的内容变成了:

UPDATE users SET email='xxx' WHERE username='admin' --'

单引号把前面的语句闭合了,注释符把后面的内容直接吞掉,实际的WHERE条件变成了只匹配admin,最终实现了对自己账号权限的修改。这类题目的利用要点在于:找到“存储”与“使用”两个位置,并且确认使用位置是否存在直接拼接。想发现二次注入,路径通常是:先正常提交一次脏数据,观察后续页面的回显是否原样输出,回显本身就是二次拼接的信号。

2.4 取数阶段的完整链路示例

当我确认注入点并且绕过了过滤,接下来最关键的一步就是高效取数。对于困难题来说,越早拿到表名和列名,越早能构造出有效payload。我在平台上常用报错注入的方式来取数,因为联合查询要调整列数,在过滤严格的情况下很费时间。

以报错注入为例,MySQL环境下extractvalue和updatexml是最常见的报错函数,它们会把报错信息直接带回页面。比如:

?id=1' AND extractvalue(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()),0x7e))--+

这里的0x7e是波浪号~的十六进制编码,用来标记报错信息的边界,方便把关键内容从冗长的报错里找出来。group_concat可以把多行表名合并成一行,减少请求次数。

我当时取数时被截断问题卡住过:extractvalue的报错输出长度有限制,表一多就会把后面的内容吞掉。后来改用substr配合limit逐段取,虽然请求次数多一点,但数据完整性有保证。这个细节强烈建议你在平台练习时也记下来,报错注入的截断是高频坑点。

取数完成之后,通常就是登录后台、找到flag页面,这一步基本是顺水推舟。SQL注入这部分我最大的体会是:困难题并不要求你发明什么冷门技巧,真正拉开差距的是对过滤规则的敏感度,以及遇到截断、报错、回显异常时能不能稳住心态、逐层拆解。

3. 第二类高频卡点:文件上传与包含漏洞的组合利用

3.1 上传成功只是开始,找到落盘路径才是分水岭

文件上传题在困难Web里属于“看答案觉得简单,自己做就卡住”的类型。我第一次解上传题时,直接把一句话内容塞进一个.php文件上传,结果显示上传成功,但访问路径始终404。后来才发现平台把文件名重命名成了一串随机字符串,而且目录下禁止列目录,你根本无法通过浏览目录找到文件。

这道题的教训是:上传类挑战的第一步不是构造恶意文件,而是先找到上传文件的落盘路径。常规思路有这么几条:

  • 上传一张正常图片,然后通过图片URL拼接../或尝试常见目录(uploads/、images/、files/)逐一访问
  • 观察上传响应包中的Location头或JSON返回字段,很多题目会把真实路径直接回给前端
  • 结合题目提示的源码位置,用伪协议或目录穿越读取上传目录的配置文件

如果你连文件存哪都不知道,后面的利用链根本没机会展开。

3.2 校验绕过的优先级:白名单、黑名单与文件内容头

确定落盘路径之后,上传校验绕过就轮到上场了。大多数平台的上传功能会做三件事:检查扩展名、检查Content-Type、检查文件头。对应绕过优先级我建议按“先试最省事的、再试最折腾的”来排序:

先试修改Content-Type为image/png,这是最小改动。再把扩展名改成大小写混写,比如.PhP、.phtml,后端如果只做小写匹配就会漏掉。接着用双扩展名.jpg.php或者空格截断.php%20,针对不同解析环境和拼接逻辑。

如果这都不行,最后一招是把PHP代码写到图片里,用GIF89a文件头开头,再传一个包含语句作为跳板执行。我在平台上传题里就遇到过:它只检查文件内容头部,我构造了一个GIF头加上短标签写法,就能直接执行。

需要注意,平台环境里“一句话木马”的写法本身很受关键词过滤影响,普通的一句话在困难题的过滤规则下大概率被拦截。竞赛场景里常见的方法是拆分关键词,比如把核心函数用变量拼接、用注释符断开敏感词,这一点下文还会展开。

3.3 包含漏洞与伪协议:拿到源码后解题就赢了一半

上传文件只是第一步,真正拉开差距的是能不能执行。如果上传的文件被解析执行,那直接访问即可。但如果文件名被改成了随机字符串,或者上传目录解析不了PHP,你需要寻找一个“包含点”来把文件包含执行起来。

包含漏洞最常见的利用方式是本地文件包含和伪协议。伪协议里php://filter用来读源码,php://input和data://用来在无法上传可执行文件时直接注入代码。我在一次平台题里的做法是先读取index.php源码,摸清后端过滤逻辑,再决定上传什么样的文件:

<?php if (isset($_GET['page'])) { include $_GET['page']; } ?>

利用这种包含点,可以这样读源码:

?page=php://filter/convert.base64-encode/resource=index.php

返回的内容是一长串Base64,解码后直接看到整个后端逻辑。通常情况下,源码里面会直接写清楚“上传目录在哪”“过滤了什么”“哪个文件是flag首页”,这些信息比你在外部盲猜高效得多。

我的经验是:遇到读源码的机会,绝不跳过去直接尝试上传利用。宁可多花三分钟解码Base64、逐行梳理代码,也要先把输出传给题目看代码在做什么——这往往是整道题最终能否解出的分水岭。

3.4 一句话入口到命令执行的竞赛边界

拿到执行权限后,普通的一句话可以连接管理工具,但在CTF平台里我们往往只需要一个命令执行的结果。PHP一句话最经典的写法是:

<?php @eval($_POST['x']);?>

但这在困难题的过滤面前通常活不下来。平台常见过滤是直接把eval、assert、$_POST这些词替换为空。竞赛中我用的常规做法是把关键词分段拼接:

<?php $a='eva'.'l';$a($_POST['x']);?>

或者利用短标签和编码组合出等价写法。这些在平台题解中属于很常规的操作,重点是你要知道目标过滤的是“字符串”而不是“执行结果”,所以只要能绕过字符串匹配,执行本身不受影响。

不过我建议把利用停在“拿到flag”这个边界上。CTF的意义是理解漏洞原理和攻防逻辑,不是对真实系统做越权操作。平台题目的入口、数据、flag都是设计好的,在这个范围内解题,本身也是对Web安全的一种尊重。

4. 第三类高频卡点:命令执行过滤与无回显判断

4.1 命令拼接过滤的常见姿势:空格、关键字与管道符

命令执行题和SQL注入在解题思路上很像,都是“输入被拼接到后端命令里”,只是拼接目标是系统命令。困难题在这里的常见过滤有三个方向:过滤空格、过滤危险字符、过滤关键命令词。

空格过滤是最容易碰到的。如果后端执行的是Linux命令,空格被过滤时,${IFS}就是最常用的替代方案。比如:

cat${IFS}/flag

${IFS}在Shell里是默认分隔符,可以代替空格,而且这个写法在被正则替换空格之后仍然有效。关键字过滤一般是把cat、flag、/这些词直接替换掉,对应的绕过思路是通配符劈开关键词:

cat /fl[a]g

方括号里的内容对Shell来说还是flag,但正则匹配时fl[a]g不是flag,所以过滤器会放过它。还有的题目会把/过滤掉,那就考虑用$(printf)构造路径,或者直接用未过滤的pwd、ls拼接相对路径。

管道符在命令执行里很关键。有些题目过滤了;和&&,但没过滤|,这时候可以用管道把命令串联,比如ls | cat。如果加引号过滤,还可以用反引号包裹子命令,把命令执行结果作为另一个命令的参数。这些技巧不依赖特定工具,纯手工就能试出来,很适合在平台练习时反复打磨。

4.2 回显消失了怎么办:延迟、错误与状态码三种判读

困难题最讨厌的地方往往不是过滤多,而是“没有回显”。命令执行了,但页面没有任何输出,你根本不知道命令成没成功。我在平台上摸索出一套无回显场景下的判读思路,核心是利用三种信号来“感知”命令是否执行了。

第一种是延迟信号。执行ping 127.0.0.1这类耗时命令,如果能构造sleep 5并观察到响应时间明显变长,说明命令确实执行了。这个思路在时间盲注里也通用。第二种是错误信号。故意执行一个不存在的命令,观察页面是否报错、状态码是否变化,有些平台会把错误信息也过滤掉,那就看404还是500,能区分命令解析阶段的差异。第三种是外带信号。由于平台靶场限制,我不建议向外发请求,而是在可控范围内配合DNS或日志记录,但这在纯平台题目里一般用不太上。

无回显判断的核心是“把你的命令结果转成可观察的信号”,延迟是最稳定的一种,因为它不需要任何输出通道,只需要你能掐住响应时间。

4.3 命令执行之后的“定式操作”与联想扩展

命令执行一旦确认,接下来要做的事就比较固定了。我总结了一套“拿到命令执行后”的定式流程,基本适用于大多数平台题:

先确认权限和上下文,执行whoami和pwd。再看目录结构,执行ls -la,优先找当前目录和根目录下的可疑文件。接着读关键文件,/flag、flag.txt、/var/www/html/index.php这些位置逐一尝试。最后看配置文件,数据库密码、后台路径、隐藏接口经常藏在配置里。

如果命令执行是在一个Web应用的参数里,我还会回到第1章说过的“前置信息收集”里,把源码文件和接口路径再认认真真过一遍。有时候flag不在当前命令执行能读到的位置,而是在另一个内部接口里,需要你通过命令执行的结果反向推断出接口地址,再从Web侧打过去。这种跨侧别联动的思路,恰恰是困难题和简单题最大的区别。

5. 刷题配套练习与节奏:从终端模拟器到综合平台

5.1 我常用的练习链路:模拟器、靶场、综合平台三者各自练什么

在PolarD&N刷题之前的那段时间,我其实走了不少弯路:一开始直接上手困难题,结果连基础命令都敲不顺,碰到命令执行题经常卡在Shell语法上。后来我把练习拆成了三层:模拟器层、靶场层、综合平台层。

模拟器层主要用Hacker Terminal这类终端模拟器练命令熟练度。对Web方向的选手来说,模拟器的价值不在于练渗透,而在于练“肌肉记忆”——ls、cat、grep、find、管道、重定向这些基础命令,必须做到看到题目就能条件反射地写出来,而不是现场查手册。命令执行题有时间窗口,你在终端上犹豫10秒,可能整个思路就断了。

靶场层用专门的Web靶场练单一漏洞类型的套路化操作。比如想练SQL注入,就在靶场里一个个地做报错注入、布尔盲注、时间盲注,把每种手法的payload写到能默写为止。想练文件上传,就把白名单、黑名单、解析漏洞、包含漏洞都过一遍。

综合平台层才是PolarD&N这类CTF平台。这里练的是“把前面两层学的知识组装起来解决问题的能力”。题目不会告诉你“这道题考的是二次注入”,你需要自己从入口、响应、源码里判断出来。三层链路下来,你的刷题效率会高很多。

5.2 一个可执行的日常刷题流程与排错习惯

如果你正好处在从简单题迈向困难题的阶段,我建议按下面的节奏来安排日常练习。每天先花15分钟做“前置信息收集”的专项训练,随便打开一道平台题,只做响应分析,不碰漏洞测试,逼自己在有限时间内找出所有可能的提示线索。然后花60分钟做一道完整题目,从信息收集到漏洞利用,全程不留死角。

如果卡了半小时没进展,我会强制自己停下来,把目前已知的信息列一遍。然后对照下面几个问题重新检查:有没有漏看响应头?有没有过滤规则没摸清?有没有考虑二次触发链?有没有可能题目根本不考当前方向而是考另一个?

我曾经在一道题上卡了一整天,最后发现是漏看了源码里的一个注释,提示说“后端只允许白名单字符”,然后我用编码把payload改了一遍,十分钟就通了。从那以后我一直很重视录像和日志。建议你也养成记录的习惯,不需要有多么规整的笔记格式,只要每次请求和响应留存下来,排查问题的时候能回溯即可。

最后再分享一个小经验:我在PolarD&N上刷题最大的收获,不是解掉了多少道困难题,而是练出了“一次只改一个变量”的排错习惯。无论是SQL注入的payload,还是命令执行的拼接,每次只改动一个字符或一个语法,然后观察响应差异,这个方法看着笨,但在复杂过滤规则面前却是最可靠、最能积累经验的方式。希望这篇复盘能给你的刷题之路提供一些有用的参考。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询