SECCON 2019 予選 Writeup

結果は相変わらずの3桁…
SSTIかとおもったら普通に回避して解く感じだったりSQLインジェクションうまくいかなかったり
まだまだ実力が足りてない

misc

Beeeeeeeeeeeeer

base64されまくったシェルがもらえるので気合で復元する問題

書くこと多すぎてつらいので、要約してかくと

  • sudo -u nobody bash -x Beeeeeeeeeer_ec349d2c91cb37b7657bc3d684d0b5c4c8cb06a2でうまく実行する(たしかrootとかadmとかnobodyで実行しろみたいな部分があったはず)
  • 二段階目のシェルでHow many beeps?";みたいなやつでなんか数えるっぽいけどめんどくさいのでコードいじってexport n=$lにして自動的に解決するようにする
  • aesのパスフレーズはなんかとりあえず叩いて挙動確認してたら3で通ってしまった(1から順に叩いて見てたらすぐ終わってしまった(実際はccccになるっぽい))
  • 最後のシェルはなんか下みたいなやつが続いててやばかった(どうやって書いてるのかさっぱりわからなかった…なんていうんだろこれ)
__=$(. 2>&1);__=${__##*.};__=$(. 2>&1);__=${__##*.};${__:$(($[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[$$/$$])):$((___=___^___||++___))}$
{__:$[$[$$/$$]<<$[$$/$$]<<$[$$/$$]]:$((___=___^___||++___))}${__:$(($[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]]+$[($[$$/$$]<<$[$$/$$]<<$[$$/$$]<<$[$$/$$])+$[$$/$$]])):$((___=___^___||++___))} -
- {z..A};${@:$((____=(____^____||++____)+(____^____||++____)))$((____=(____^____||++____)+(____^____||++____))):$((____=____^____||++____))}${@:$((____=(____^____||++____)+(____^____||++____)))$(($((___
_=(____^____||++____)+(____^____||++____)))+$((____=(____^____||++____)+(____^____||++____))))):$((____=____^____||++____))}${@:$((____=____^____||++____))$(($((____=(____^____||++____)+(____^____||++__
__)))*$((____=(____^____||++____)+(____^____||++____)))*$((____=(____^____||++____)+(____^____||++____)))+$((____=____^____||++____)))):$((____=____^____||++____))}
  • ただなんか最後のシェルを-xつけて叩くとpassword is bashとか書いてあるのでbashを入力する
  • 分解して実行していたせいでSECCON{bash}が答えになって、WEBの入力したらincorrect….
  • シェルの中身をもう一度見るとecho SECCON{$S1$n$_____};らしいので$S1$nが空っぽなんだろうと気がついた
  • $nは入力した記憶があったのですぐに3だとわかった
  • $S1は見かけた記憶がなかったので、とりあえず最初のシェルの最後にecho $S1;を書いてごりおしたらhogefugaが出力された
  • あとは$S1$nを適切な位置にいれてあげてcorrectした

flagはSECCON{hogefuga3bash}

crypto

coffee_break

暗号化されたデータとソースコードがもらえるので頑張って復元する問題

暗号化されたデータ

FyRyZNBO2MG6ncd3hEkC/yeYKUseI/CxYoZiIeV2fe/Jmtwx+WbWmU1gtMX9m905

もらえるソース

import sys
from Crypto.Cipher import AES
import base64
import string


def encrypt(key, text):
    s = ''
    for i in range(len(text)):
        s += chr((((ord(text[i]) - 0x20) + (ord(key[i % len(key)]) - 0x20)) % (0x7e - 0x20 + 1)) + 0x20)
    return s

key1 = "SECCON"
key2 = "seccon2019"
text = sys.argv[1]

enc1 = encrypt(key1, text)
cipher = AES.new(key2 + chr(0x00) * (16 - (len(key2) % 16)), AES.MODE_ECB)
p = 16 - (len(enc1) % 16)
enc2 = cipher.encrypt(enc1 + chr(p) * p)
print(base64.b64encode(enc2).decode('ascii'))

まずはdecodeする

>>> base64.b64decode("FyRyZNBO2MG6ncd3hEkC/yeYKUseI/CxYoZiIeV2fe/Jmtwx+WbWmU1gtMX9m905")
\x17$rd\xd0N\xd8\xc1\xba\x9d\xc7w\x84I\x02\xff'\x98)K\x1e#\xf0\xb1b\x86b!\xe5v}\xef\xc9\x9a\xdc1\xf9f\xd6\x99M`\xb4\xc5\xfd\x9b\xdd9

次にdecryptする

>>> cipher.decrypt(d_code)
b"'jff~|Ox9'34G9#g52F?489>B%|)173~)%8.'jff~|Q\x05\x05\x05\x05\x05"

\x05は埋めてるだけで邪魔なので削除

"'jff~|Ox9'34G9#g52F?489>B%|)173~)%8.'jff~|Q"

後はdecryptするためのコードかいておわり

import string


def decrypt(key, text):
    s = ''
    for i in range(len(text)):
        for c in list(string.printable):
            if chr((((ord(c) - 0x20) + (ord(key[i % len(key)]) - 0x20)) % (0x7e - 0x20 + 1)) + 0x20) == text[i]:
                s += c
                break
    return s

plaintext = decrypt("SECCON", "'jff~|Ox9'34G9#g52F?489>B%|)173~)%8.'jff~|Q")
print(plaintext)

flagはSECCON{Success_Decryption_Yeah_Yeah_SECCON}

Web

Option-Cmd-U

docker-composeで動いているサービス(nginx+php-fpm)で/flag.phpへアクセスしてフラグを取得する問題

http://ocu.chal.seccon.jp:10000/index.php?action=sourceにアクセスするとソースを取得できるのでまずはソースを見る(必要なとこ以外は消した)
直接nginxのIPへアクセス(名前解決含む)するとだめっぽいので、迂回する方法を考える

<!-- src of this PHP script: /index.php?action=source -->
<!-- the flag is in /flag.php, which permits access only from internal network :-) -->
<!-- this service is running on php-fpm and nginx. see /docker-compose.yml -->
<span style="color: #0000BB"><?php if (isset($_GET['url'])){
    $url = filter_input(INPUT_GET, 'url');
    $parsed_url = parse_url($url);    
    if($parsed_url["scheme"] !== "http"){
        </span><span style="color: #FF8000">// only http: should be allowed. 
        </span><span style="color: #007700">echo </span><span style="color: #DD0000">'URL should start with http!'</span><span style="color: #007700">;
    } else if (gethostbyname(idn_to_ascii($parsed_url["host"], 0, INTL_IDNA_VARIANT_UTS46)) === gethostbyname("nginx")) {
        </span><span style="color: #FF8000">// local access to nginx from php-fpm should be blocked.
        </span><span style="color: #007700">echo </span><span style="color: #DD0000">'Oops, are you a robot or an attacker?'</span><span style="color: #007700">;
    } else {
        </span><span style="color: #FF8000">// file_get_contents needs idn_to_ascii(): https://stackoverflow.com/questions/40663425/
        highlight_string(file_get_contents(idn_to_ascii($url, 0, INTL_IDNA_VARIANT_UTS46),false,stream_context_create(array('http' => array('follow_location' => false,'timeout'=> 2)))));
    }
}
</span><span style="color: #0000BB">?>

http://ocu.chal.seccon.jp:10000/docker-compose.ymlにアクセスして、docker-composeのホスト名を見る

version: '3'

services:
  nginx:
    (...ommitted...)
  php-fpm:
    (...ommitted...

あとは、上のコードに引っかからないように下のようにSSRFしてflagをget
http://nginx:80;ocu.chal.seccon.jp:10000/flag.php

flagはSECCON{what_a_easy_bypass_314208thg0n423g}