nucleiでテストを書いてみたのでメモ

# はじめに

最近はなぜか業務として脆弱性を埋めこんだやられアプリを作ってることが多いです。
やられアプリの性質として、脆弱性が埋めこまれているのですが、これを埋めこんだ後、正常に機能が動作しているか、脆弱性と機能がうまく共存しているかを確かめる必要があります。

脆弱性の再現に関しては社内のツールを使ったり、burpのリピータを使ったり、手動で再現確認していたのですが、yamlテンプレートを使って脆弱性診断を行うことができるnucleiというツールがあったので、これを使ってみることにしました。

burpのリピーターや手動での確認だと自分の経験で目視確認で判断するしかないのですが、nucleiだと判断までしてくれるようになるのでありがたいですね。

なお、nucleiではすでにたくさんのテンプレートが準備されており、これを使った脆弱性スキャンができるのですが、それについてはここでは言及しません。ご了承ください。

## nucleiのURL

https://nuclei.projectdiscovery.io/

## とりあえずkaliで動かす

手元にkali linuxがあったのでインストールして動かしてみました（自分でインストールする必要があります）。PCにインストールすることやDockerを使うことも可能なようです。

## インストール
```bash
$ sudo apt install nuclei

$ nuclei --version
[INF] Nuclei Engine Version: v2.9.12
```

## Kaliのnucleiテンプレートの場所

`/home/kali/.local/nuclei-templates/`

## 簡単なオプション

```bash
# ネットワーク+Webのスキャン
$ nuclei -u http://192.168.5.7 

# ネットワークのスキャン
$ nuclei -u 192.168.5.7

# URLリストに対してスキャン
$ nuclei -list urls.txt

# テンプレートを指定してスキャン
$ nuclei -t cves/2021/CVE-2021-41773.yaml -u http://192.168.5.7 -v

# テンプレートの文法を検証する
$ nuclei -t cves/2021/CVE-2021-41773.yaml -u http://192.168.5.7 --validate

# プロキシ経由でスキャン
$ nuclei -t cves/2021/CVE-2021-41773.yaml -u http://192.168.5.7 -v -p http://192.168.5.33:8080

# 出力の指定
$ nuclei -u http://192.168.5.7 -o scan.txt

# markdownへの出力（panicになる）
$ nuclei -u http://192.168.5.7 -me testscan

# 本体のアップデート(flag provided but not defined: -update エラーになる)
$ nuclei -update

# 脆弱性テンプレートのアップデート
$ nuclei -ut
```

-----

# テストを作成する

ここからが本番です。脆弱性をnucleiに再現してもらうためのテンプレートを作成します。
すでに再現手順がわかっている脆弱性なので、テンプレートに再現手順を書き、脆弱性があると判断するための条件を満たせば脆弱性の再現に成功したということになります。

脆弱性を見つけるというnucleiの目的とは少し外れてしまうのかもしれませんが、そこは、まあ、いいじゃないですか。

## シンプルなテンプレート

以下がシンプルな反射型XSS脆弱性を検出するテンプレートになります。yamlで書かれています。

```yaml
id: yarare-xss-reflected

info:
  name: yarare Reflected XSS
  author: yujitounai
  severity: Medium

http:
  - raw:
      - |
        POST /form HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        firstname=test&lastname=test&mail={{testcode}}
    payloads:
      testcode:
        - "
test
"
    matchers:
      - type: dsl
        dsl:
          - "contains((body), '{{testcode}}')"
```

## テンプレートの解説

このテンプレートの説明です。

`id` 必須項目です。認識できる一意の文字列を設定します。IDにはスペースを含めてはいけないということになっています。

`info` 必須項目です。nameはテンプレート名、authorは作者、severityは脆弱性の危険度です。他にtags、description、referenceが設定可能です。自分だけが使うなら適当でいいです。

`http` httpリクエストの始まりです。ここから実行させたいhttpリクエストを書きます。

`raw` httpリクエストのrawデータです。雰囲気で察してください。ブラウザやらburpから生httpリクエストを貼って適当に付けたり消したりします。セッションIDについては後述します。

```yaml
  - method: GET
    path:
      - "{{BaseURL}}/.git/config"
```

のような指定もできますが、`raw`の方がより柔軟な対応が可能です。

`{{Hostname}}`は予約されている定数で、命令実行時に入力されるURLから取り出したホスト名、となっていますが `http://host:port/` で指定している場合、`host:port` とポート番号まで取ってきてくれます。

`{{testcode}}` 下のpayloadsで指定する変数です。

`payloads` ここでリクエストに代入される攻撃コードが入ります。

`testcode` リクエストに入力する攻撃ペイロードの名前です。好きな名前を設定可能です。ペイロードはここでは1つしか書いていませんが、複数指定することも可能ですし、辞書ファイルを指定することも可能です。

`matchers` この条件に当てはまると脆弱性があるということになります。条件は単純な文字列比較( `type: word` )もあるのですが、dsl という式の作成と評価を可能にするライブラリを使いデータを詳しく見ることが可能になっています。ここではhttpボディに入力された文字列と同じ文字列が返ってるかという単純な比較をしています。文字列で引っかけるなら以下のようなことでいいと思いますが。

```yaml
      - type: word
        words:
          - "{{testcode}}"
```

# テストで使ったテンプレートの書き方

ここからはテストを書くときに必要だったテンプレートの書き方についていくつか書きます。他に必要になった場合は追記するかもしれないです。

## ログインしてからテストする

脆弱性診断の仕事をしていると、普通はアカウントをもらい、ログインしてからその先の仕事となることが多いです。やられサイトにしても似たような流れのものを作成しますので、やはりログイン状態を維持したいものです。

Cookieでのセッション管理が多いですが、そういう場合はログインしてセッションIDを取得して、それを使うという一連の流れを記述する必要があります。nuclei以下のようにログインとリクエストを並べて、`cookie-reuse: true` と記述することで、ログインを行い、ログインセッションを維持して検査を行うことが可能です。

```yaml
http:
  - raw:
      - |
        POST /login HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        username=test%40example.jp&password=pass
      - |
        GET /1 HTTP/1.1
        Host: {{Hostname}}
      - |
        GET /2 HTTP/1.1
        Host: {{Hostname}}
    cookie-reuse: true
```
## ランダムなデータを登録する

重複させないようなデータを登録するときなどランダムな値を設定したい場合もあります。その際はヘルパー関数が用意されているので以下のようにリクエストに直接埋めこむことで使えます。

```yaml
http:
  - raw:
      - |
        POST /register/submit HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        username={{rand_text_alphanumeric(10,"ab12")}}@example.jp&password=pass

```

詳しくはドキュメントを参照してください。

https://docs.nuclei.sh/template-example/helper-functions

## race conditionsの確認

race conditionsを使って変な挙動を起こすことは今でも見かけることがあります。
同じリクエストの場合だと`race: true`にして`race_count: 10`とすることで同じリクエストをまとめて送信することが可能です。

```yaml
requests:
  - raw:
      - |
        POST /coupons HTTP/1.1
        Host: {{Hostname}}

        code=promocode        

    race: true
    race_count: 10
```

違うリクエストの場合はスレッドを増やして擬似的にリクエストを送信する必要があるようです。

https://docs.nuclei.sh/template-example/http/http-race-conditions

## CSRF対策用tokenがあるリクエストを行う

最近はなくてもある程度防いでくれるようにはなりましたが、現在多くのサイトには何かデータベースの内容を改変するようなリクエストを送信する時にCSRF攻撃を防ぐためのtokenと呼ばれる文字列が付与されています。CSRF対策用tokenを受け取って利用するには、以下のようなextractorsというものを使います。ここででは正規表現でtokenを見つけて次のリクエストで使用するようにしています。

```yaml
http:
  - raw:
      - |
        GET /form HTTP/1.1
        Host: {{Hostname}}

      - |
        POST /form/submit HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/x-www-form-urlencoded

        firstname=%E3%82%B9%E3%83%88{{testcode}}&lastname=%E3%83%86&mail=bogus%40bogus.jp&consultation=1&specific=test&image=&csrfToken={{csrf}}

    extractors:
      - type: regex
        name: csrf
        regex:
          - 'name="csrfToken" value="(.+?)">'
        internal: true
        group: 1
        part: body
```

このような感じで無事にnucleiを使ってWebアプリケーションの脆弱性を確認するテストを行うことができました。もし何か修正した際にデグレして脆弱性が発現しなくなることを見つけやすくなると思います。

脆弱性を探すにはFuzzingとかもできるのですが、それはまた別の機会があれば書きます。