はじめに
最近はなぜか業務として脆弱性を埋めこんだやられアプリを作ってることが多いです。
やられアプリの性質として、脆弱性が埋めこまれているのですが、これを埋めこんだ後、正常に機能が動作しているか、脆弱性と機能がうまく共存しているかを確かめる必要があります。
脆弱性の再現に関しては社内のツールを使ったり、burpのリピータを使ったり、手動で再現確認していたのですが、yamlテンプレートを使って脆弱性診断を行うことができるnucleiというツールがあったので、これを使ってみることにしました。
burpのリピーターや手動での確認だと自分の経験で目視確認で判断するしかないのですが、nucleiだと判断までしてくれるようになるのでありがたいですね。
なお、nucleiではすでにたくさんのテンプレートが準備されており、これを使った脆弱性スキャンができるのですが、それについてはここでは言及しません。ご了承ください。
nucleiのURL
とりあえずkaliで動かす
手元にkali linuxがあったのでインストールして動かしてみました(自分でインストールする必要があります)。PCにインストールすることやDockerを使うことも可能なようです。
インストール
$ sudo apt install nuclei
$ nuclei --version
[INF] Nuclei Engine Version: v2.9.12
Kaliのnucleiテンプレートの場所
/home/kali/.local/nuclei-templates/
簡単なオプション
# ネットワーク+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で書かれています。
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:
- "<s>test</s>"
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については後述します。
- method: GET
path:
- "{{BaseURL}}/.git/config"
のような指定もできますが、raw
の方がより柔軟な対応が可能です。
{{Hostname}}
は予約されている定数で、命令実行時に入力されるURLから取り出したホスト名、となっていますが http://host:port/
で指定している場合、host:port
とポート番号まで取ってきてくれます。
{{testcode}}
下のpayloadsで指定する変数です。
payloads
ここでリクエストに代入される攻撃コードが入ります。
testcode
リクエストに入力する攻撃ペイロードの名前です。好きな名前を設定可能です。ペイロードはここでは1つしか書いていませんが、複数指定することも可能ですし、辞書ファイルを指定することも可能です。
matchers
この条件に当てはまると脆弱性があるということになります。条件は単純な文字列比較( type: word
)もあるのですが、dsl という式の作成と評価を可能にするライブラリを使いデータを詳しく見ることが可能になっています。ここではhttpボディに入力された文字列と同じ文字列が返ってるかという単純な比較をしています。文字列で引っかけるなら以下のようなことでいいと思いますが。
- type: word
words:
- "{{testcode}}"
テストで使ったテンプレートの書き方
ここからはテストを書くときに必要だったテンプレートの書き方についていくつか書きます。他に必要になった場合は追記するかもしれないです。
ログインしてからテストする
脆弱性診断の仕事をしていると、普通はアカウントをもらい、ログインしてからその先の仕事となることが多いです。やられサイトにしても似たような流れのものを作成しますので、やはりログイン状態を維持したいものです。
Cookieでのセッション管理が多いですが、そういう場合はログインしてセッションIDを取得して、それを使うという一連の流れを記述する必要があります。nuclei以下のようにログインとリクエストを並べて、cookie-reuse: true
と記述することで、ログインを行い、ログインセッションを維持して検査を行うことが可能です。
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
ランダムなデータを登録する
重複させないようなデータを登録するときなどランダムな値を設定したい場合もあります。その際はヘルパー関数が用意されているので以下のようにリクエストに直接埋めこむことで使えます。
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
詳しくはドキュメントを参照してください。
race conditionsの確認
race conditionsを使って変な挙動を起こすことは今でも見かけることがあります。
同じリクエストの場合だとrace: true
にしてrace_count: 10
とすることで同じリクエストをまとめて送信することが可能です。
requests:
- raw:
- |
POST /coupons HTTP/1.1
Host: {{Hostname}}
code=promocode
race: true
race_count: 10
違うリクエストの場合はスレッドを増やして擬似的にリクエストを送信する必要があるようです。
CSRF対策用tokenがあるリクエストを行う
最近はなくてもある程度防いでくれるようにはなりましたが、現在多くのサイトには何かデータベースの内容を改変するようなリクエストを送信する時にCSRF攻撃を防ぐためのtokenと呼ばれる文字列が付与されています。CSRF対策用tokenを受け取って利用するには、以下のようなextractorsというものを使います。ここででは正規表現でtokenを見つけて次のリクエストで使用するようにしています。
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とかもできるのですが、それはまた別の機会があれば書きます。