题目来源(GitHub):RCE-labs 靶场
这里我用的是 CTF+ 平台来进行练习,平台靶场地址:RCE-labs
level_1
题目代码:
<?php
include ("get_flag.php");
/*
# -*- 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靶场 : 一句话木马和代码执行 ---
「代码执行(Code Execution)」 在某个语言中,通过一些方式(通常为函数或者方法调用)执行该语言的任意代码的行为,如PHP中的`eval()`函数或Python中的`exec()`函数。
当漏洞入口点可以执行任意代码时,我们称其为代码执行漏洞 —— 这种漏洞包含了通过语言中对接系统命令的函数来执行系统命令的情况,比如 eval("system('cat /etc/passwd')";); 也被归为代码执行漏洞。
我们平时最常见的一句话木马就用的 eval() 函数,如下所示(一般情况下,为了接收更长的Payload,我们一般对可控参数使用POST传参)
try POST:
a=echo "Hello,World!";
*/
eval($_POST['a']);
highlight_file(__FILE__);
?>没有过滤,直接传参数 a 来读取文件内容就行。

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 <br>";
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 查看函数详情。<br>";
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传入action参数,如果 action=r 的话,就清除session,下次访问会重新随机函数。如果action=submit且POST提交了content的话,就执行 hello_ctf() 函数。
get_fun函数给出一个列表,然后随机给出一个函数, 并给出目标url查看函数用法,最后到hello_ctf()函数,如果这时action的值是sumbit而且POST传参content,content与给出的函数进行拼接执行,回显flag。

我首先传入action=submit,来获取一个随机函数,这里获取到eval函数,再根据eval函数的用法,构造content参数来获取flag。
level_3
题目代码:
<?php
system($_POST['a']);
highlight_file(__FILE__);
?>这段代码直接执行了用户 POST 提交的数据,直接 cat 获取 flag 就行。

level_4
题目代码:
<?php
/*
# -*- 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靶场 : 命令执行 - SHELL 运算符 ---
https://www.runoob.com/linux/linux-shell-basic-operators.html
SHELL 运算符 可以用于控制命令的执行流程,使得你能够根据条件执行不同的命令。
&&(逻辑与运算符): 只有当第一个命令 cmd_1 执行成功(返回值为 0)时,才会执行第二个命令 cmd_2。例: mkdir test && cd test
||(逻辑或运算符): 只有当第一个命令 cmd_1 执行失败(返回值不为 0)时,才会执行第二个命令 cmd_2。例: cd nonexistent_directory || echo "Directory not found"
&(后台运行符): 将命令 cmd_1 放到后台执行,Shell 立即执行 cmd_2,两个命令并行执行。例: sleep 10 & echo "This will run immediately."
;(命令分隔符): 无论前一个命令 cmd_1 是否成功,都会执行下一个命令 cmd_2。例: echo "Hello" ; echo "World"
try GET:
?ip=8.8.8.8
flag is /flag
*/
function hello_server($ip){
system("ping -c 1 $ip");
}
isset($_GET['ip']) ? hello_server($_GET['ip']) : null;
highlight_file(__FILE__);
?>这题只要考察命令分隔符的知识。例如:cmd_1 (分隔符) cmd_2
&&(逻辑与运算符): 只有当第一个命令 cmd_1 执行成功时,才会执行第二个命令 cmd_2
||(逻辑或运算符): 只有当第一个命令 cmd_1 执行失败时,才会执行第二个命令 cmd_2
&(后台运行符): 将命令 cmd_1 放到后台执行,Shell 立即执行 cmd_2,两个命令并行执行
;(命令分隔符): 无论前一个命令 cmd_1 是否成功,都会执行下一个命令 cmd_2

level_5
题目代码:
<?php
function hello_shell($cmd){
if(preg_match("/flag/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>这道题过滤了 "/flag/",我们简单的绕过一下。比如,可以利用通配符"?","*"等来进行绕过。

level_6
题目代码:
<?php
/*
# -*- 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靶场 : 挑战关 ---
刚才,学了什么来着!?
*/
function hello_shell($cmd){
if(preg_match("/[b-zA-Z_@#%^&*:{}\-\+<>\"|`;\[\]]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>这道题是上一题的加强版,几乎过滤了所有字母和特殊符号,但字母a没被过滤。我们就可以利用通配符结合字母 a 来获取 flag,过滤的字母就用 "?" 来表示。但是执行发现没有回显,考虑到可能是 PATH 的问题,利用 cat 命令的相对路径来执行命令,就得到了 flag。
cat 命令存在于 /usr/bin 目录,利用相对路径即可正确执行命令。

level_7
题目代码:
<?php
/*
# -*- 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靶场 : 命令执行 - 终端特殊字符 ---
在遇到空格被过滤的情况下,通常使用 %09 也就是TAB的URL编码来绕过,在终端环境下 空格 被视为一个命令分隔符,本质上由 $IFS 变量控制,而 $IFS 的默认值是空格、制表符和换行符,所以我们还可以通过直接键入 $IFS 来绕过空格过滤。
*/
function hello_shell($cmd){
if(preg_match("/flag| /", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>这题过滤了 "flag" 字符串,空格,这题的解法有好多种。空格可以用 "%09" 或 "$IFS" 来绕过,flag 字符串可以用 "fl\ag" 或 "fl""ag" 来替代,也可以利用通配符来绕过,例如 "cat$IFS/f*"。

level_8
题目代码:
<?php
/*
# -*- 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靶场 : 命令执行 - 重定向 ---
大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端 —— 这些是命令有回显的基础。
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:
$ command > /dev/null
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
如果希望屏蔽 stdout 和 stderr,可以这样写:
$ command > /dev/null 2>&1
*/
function hello_shell($cmd){
/*>/dev/null 将不会有任何回显,但会回显错误,加上 2>&1 后连错误也会被屏蔽掉*/
system($cmd.">/dev/null 2>&1");
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>正如题目说描述的,/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。
我们可以想办法把 flag 的文件内容写到另一个文件里,然后再去访问文件就好了。
这里就可以利用我们之前提到的通配符了。
payload:
?cmd=cat /flag>/tmp/flag.txt;cat /tmp/flag.txt;level_9
题目代码:
<?php
/*
# -*- 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靶场 : 命令执行 - bash终端的无字母命令执行_八进制转义 ---
题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用
从该关卡开始你会发现我们在Dockerfile中添加了一行改动:
RUN ln -sf /bin/bash /bin/sh
这是由于在PHP中,system是执行sh的,sh通常只是一个软连接,并不是真的有一个shell叫sh。在debian系操作系统中,sh指向dash;在centos系操作系统中,sh指向bash,我们用的底层镜像 php:7.3-fpm-alpine 默认指向的 /bin/busybox ,要验证这一点,你可以对 /bin/sh 使用 ls -l 命令查看,在这个容器中,你会得到下面的回显:
bash-5.1# ls -l /bin/sh
lrwxrwxrwx 1 root root 12 Mar 16 2022 /bin/sh -> /bin/busybox
我们需要用到的特性只有bash才支持,请记住这一点,这也是我们手动修改指向的原因。
在这个关卡主要利用的是在终端中,$'\xxx'可以将八进制ascii码解析为字符,仅基于这个特性,我们可以将传入的命令的每一个字符转换为$'\xxx\xxx\xxx\xxx'的形式,但是注意,这种方式在没有空格的情况下无法执行带参数的命令。
比如"ls -l"也就是$'\154\163\40\55\154' 只能拆分为$'\154\163' 空格 $'\55\154'三部分。
bash-5.1# $'\154\163\40\55\154'
bash: ls -l: command not found
bash-5.1# $'\154\163' $'\55\154'
total 4
-rw-r--r-- 1 www-data www-data 829 Aug 14 19:39 index.php
*/
function hello_shell($cmd){
if(preg_match("/[A-Za-z\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>根据题目提示,已经将 /bin/sh 指向了 /bin/bash 了,简单来说就是,在调用 /bin/sh 时,实际上调用的是 /bin/bash。题目中几乎将所有字母,特殊符号都过滤了,本题实际利用了 bash 终端的特性,在 bash 终端中,可以将八进制 ASCII 码解析为字符,那么接下来就要进行 bash 的八进制绕过了,利用八进制读取 flag。
题目本身提供了 exp.php,也可以利用在线网站:BashFuck
cat:$'\143\141\164',/flag:$'\57\146\154\141\147',中间要加上空格,完整 payload:cmd=$'\143\141\164' $'\57\146\154\141\147'

level_10
题目代码:
<?php
/*
# -*- 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靶场 : 命令执行 - bash终端的无字母命令执行_二进制整数替换 ---
题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用
本关卡的考点为终端中支持 $((2#binary)) 解析二进制数据。
*/
function hello_shell($cmd){
if(preg_match("/[A-Za-z2-9\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>根据题目提示,这一关是要利用二进制数据来在 bash 终端读取 flag。
还是利用和上一关相同的工具。

由于编码后的字符串有 #,# 在 URL 里有特殊意义,所以这里还需要进行 URL 编码。

传入参数,成功得到 flag。

level_11
题目代码:
<?php
/*
# -*- 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靶场 : 命令执行 - bash终端的无字母命令执行_数字1的特殊变量替换 ---
题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用
本关卡的考点为终端中支持 $((2#binary)) 解析二进制数据 + 我们用 ${##} 来替换 1
*/
function hello_shell($cmd){
if(preg_match("/[A-Za-z1-9\"%*+,-.\/:;=>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_POST['cmd']) ? hello_shell($_POST['cmd']) : null;
highlight_file(__FILE__);
?>题目提示:本关卡的考点为终端中支持 $((2#binary)) 解析二进制数据 + 我们用 ${##} 来替换 1。要求不出现1,利用 ${##} 来替代 1。
还是一样先对字符串进行编码。

同样,参数值里面也有 #,所以同样需要 URL 编码。

编码完成后进行传参。要注意,这里是 POST 传参。

level_12(环境有问题)
但根据推断,大概是要求 0 和 1 都不能出现的情况!
level_13
bash 终端的无字母命令执行 _ 特殊扩展替换任意数字
题目代码:
<?php
/*
# -*- 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靶场 : 命令执行 - bash终端的无字母命令执行_特殊扩展替换任意数字 ---
题目已经拥有成熟脚本:https://github.com/ProbiusOfficial/bashFuck
你也可以使用在线生成:https://probiusofficial.github.io/bashFuck/
题目本身也提供一个/exp.php方便你使用
本关卡的考点为 $(()) + 取反 构造任意数字
echo $(()) -> 0
echo $((~$(()))) -> -1
echo $(($((~$(())))$((~$(()))))) -> -2
*/
function hello_shell($cmd){
if(preg_match("/[A-Za-z0-9\"%*+,-.\/:;>?@[\]^`|]/", $cmd)){
die("WAF!");
}
system($cmd);
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
highlight_file(__FILE__);
?>题目中说明了无字母,数字要替换。步骤同上。

然后 URL 编码,传参。

获取 flag。

说明
本文为前 13 关的教程,后面剩下的 14 关在下篇文章!