はじめに
Webアプリを作ったものの、公開するにはセキュリティ面が心もとない…ということでWAFを検討していたところ、 ModSecurityがEoLになるらしく、じゃあどうすれば…?と途方に暮れていました。
そこからいろいろとググってみたところ、ModSecurityとの互換性があるという OWASP Corazaあたりが使えそうということが分かったので、 今回はその導入方法についてまとめます。
必要なもの
- golang-1.20(Goコンパイラ。coraza-caddyのビルドはバージョン1.20以降が必須。)
- Caddyのカスタムビルドツール: caddyserver/xcaddy
- Caddyのcorazaモジュール: corazawaf/coraza-caddy
- Coraza用OWASP CoreRuleset: corazawaf/coraza-coreruleset
- サンプルWebアプリ(なんでもよい): mccutchen/go-httpbin
xcaddyの用意
まずはxcaddy
のバイナリを入手し、パスの通っている適当なディレクトリに置いておきます。
(いつもの感覚でtar xf
するとカレントディレクトリに散らばるので要注意。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% mkdir -p ~/work/xcaddy
% cd ~/work/xcaddy
% curl -L -O https://github.com/caddyserver/xcaddy/releases/download/v0.3.5/xcaddy_0.3.5_linux_amd64.tar.gz
% tar xf xcaddy_0.3.5_linux_amd64.tar.gz
% ls -la
total 4248
drwx------ 2 gloria gloria 4096 Sep 5 18:05 .
drwxr-x--- 18 gloria gloria 4096 Sep 5 18:04 ..
-rw------- 1 gloria gloria 11357 Aug 8 01:22 LICENSE
-rw------- 1 gloria gloria 6833 Aug 8 01:22 README.md
-rwx------ 1 gloria gloria 3010560 Aug 8 01:23 xcaddy
-rw------- 1 gloria gloria 1307631 Sep 5 18:04 xcaddy_0.3.5_linux_amd64.tar.gz
% cp xcaddy ~/.local/bin
% which xcaddy
/home/gloria/.local/bin/xcaddy
coraza-caddyのビルド
次にcoraza-caddyをビルドします。
go
コンパイラが必要なので、ない場合はapt install golang-1.20
しておきましょう。
1
2
3
4
5
6
7
% cd ~/work
% curl -L -O https://github.com/corazawaf/coraza-caddy/archive/refs/tags/v2.0.0-rc.3.tar.gz
% tar xf v2.0.0-rc.3.tar.gz
% cd coraza-caddy-2.0.0-rc.3
% ls
LICENSE TROUBLESHOOTING.md coraza.go e2e ftw go.sum http_test.go logger.go magefile.go testdata
README.md caddy coraza_test.go example go.mod http.go interceptor.go mage.go test.init.config utils.go
go
コンパイラは/usr/lib/go-1.20/bin
に入っているので、そこにPATH
を通してビルドします。
(PATHを通しておかないと、内部でgo
コマンドが実行された際にエラーとなってしまいます。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% PATH="/usr/lib/go-1.20/bin:$PATH" go run mage.go buildCaddy
2023/09/05 18:18:34 [INFO] Resolved relative replacement github.com/corazawaf/coraza-caddy/v2=. to /home/gloria/work/coraza-caddy-2.0.0-rc.3
2023/09/05 18:18:34 [INFO] Temporary folder: /tmp/buildenv_2023-09-05-1818.853923260
2023/09/05 18:18:34 [INFO] Writing main module: /tmp/buildenv_2023-09-05-1818.853923260/main.go
package main
...
...
go: added github.com/tidwall/match v1.1.1
go: added github.com/tidwall/pretty v1.2.1
2023/09/05 18:18:37 [INFO] Build environment ready
2023/09/05 18:18:37 [INFO] Building Caddy
2023/09/05 18:18:37 [INFO] exec (timeout=0s): /usr/lib/go-1.20/bin/go mod tidy -e
2023/09/05 18:18:38 [INFO] exec (timeout=0s): /usr/lib/go-1.20/bin/go build -o /home/gloria/work/coraza-caddy-2.0.0-rc.3/build/caddy -ldflags -w -s -trimpath
2023/09/05 18:18:48 [INFO] Build complete: build/caddy
2023/09/05 18:18:48 [INFO] Cleaning up temporary folder: /tmp/buildenv_2023-09-05-1818.853923260
build
ディレクトリ内にcaddy
のバイナリができていればOKです。
1
2
3
4
5
% ls -l build
total 42440
-rwx------ 1 gloria gloria 43458560 Sep 5 18:18 caddy
% file build/caddy
build/caddy: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=j89ijyzi9HFzGvNYOwym/wTJquPD4Dt_JAqmfwjAt/jDRi_q56UNnAxu7JWvk1/WN9AtgOafr7ercOLvTat, stripped
coraza-caddyをリバースプロキシサーバとして起動
さきほどビルドしたcaddy
を、リバースプロキシサーバとして起動します。
設定ファイル(Caddyfile
)は、coraza-caddy
のexample
ディレクトリ内にあるものをそのまま用います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% ./build/caddy run --config example/Caddyfile --adapter caddyfile
2023/09/05 09:31:23.995 INFO using provided configuration {"config_file": "example/Caddyfile", "config_adapter": "caddyfile"}
2023/09/05 09:31:23.995 WARN Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies {"adapter": "caddyfile", "file": "example/Caddyfile", "line": 2}
2023/09/05 09:31:23.996 INFO admin admin endpoint started {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2023/09/05 09:31:23.996 WARN http.auto_https automatic HTTPS is completely disabled for server {"server_name": "srv0"}
2023/09/05 09:31:23.996 DEBUG http.auto_https adjusted config {"tls": {"automation":{"policies":[{}]}}, "http": {"servers":{"srv0":{"listen":[":8080"],"routes":[{"handle":[{"directives":"\n\t\t\tInclude @coraza.conf-recommended\n\t\t\tInclude @crs-setup.conf.example\n\t\t\tInclude @owasp_crs/*.conf\n\t\t\tSecRuleEngine On\n\t\t\tSecDebugLog /dev/stdout\n\t\t\tSecDebugLogLevel 9\n\t\t SecRule REQUEST_URI \"@streq /admin\" \"id:101,phase:1,t:lowercase,deny,status:403\"\n\t\t\tSecRule REQUEST_BODY \"@rx maliciouspayload\" \"id:102,phase:2,t:lowercase,deny,status:403\"\n\t\t\tSecRule RESPONSE_HEADERS::status \"@rx 406\" \"id:103,phase:3,t:lowercase,deny,status:403\"\n\t\t\tSecResponseBodyAccess On\n\t\t\tSecResponseBodyMimeType application/json\n\t\t\tSecRule RESPONSE_BODY \"@contains responsebodycode\" \"id:104,phase:4,t:lowercase,deny,status:403\"\n\t\t","handler":"waf","include":[],"load_owasp_crs":true},{"handler":"reverse_proxy","upstreams":[{"dial":"localhost:8081"}]}]}],"automatic_https":{"disable":true}}}}}
2023/09/05 09:31:23.996 INFO tls.cache.maintenance started background certificate maintenance {"cache": "0xc000213f80"}
{"level":"debug","ts":1693906284.0556035,"logger":"http.handlers.waf","msg":"Parsing directive","line":"SecRule REQUEST_URI \"@streq /admin\" \"id:101,phase:1,t:lowercase,deny,status:403\""}
{"level":"debug","ts":1693906284.0556402,"logger":"http.handlers.waf","msg":"Parsing directive","line":"SecRule REQUEST_BODY \"@rx maliciouspayload\" \"id:102,phase:2,t:lowercase,deny,status:403\""}
{"level":"debug","ts":1693906284.0556633,"logger":"http.handlers.waf","msg":"Parsing directive","line":"SecRule RESPONSE_HEADERS::status \"@rx 406\" \"id:103,phase:3,t:lowercase,deny,status:403\""}
{"level":"debug","ts":1693906284.055682,"logger":"http.handlers.waf","msg":"Parsing directive","line":"SecResponseBodyAccess On"}
{"level":"debug","ts":1693906284.055694,"logger":"http.handlers.waf","msg":"Parsing directive","line":"SecResponseBodyMimeType application/json"}
{"level":"debug","ts":1693906284.0556972,"logger":"http.handlers.waf","msg":"Parsing directive","line":"SecRule RESPONSE_BODY \"@contains responsebodycode\" \"id:104,phase:4,t:lowercase,deny,status:403\""}
2023/09/05 09:31:24.055 DEBUG http starting server loop {"address": "[::]:8080", "tls": false, "http3": false}
2023/09/05 09:31:24.055 INFO http.log server running {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2023/09/05 09:31:24.055 INFO tls cleaning storage unit {"description": "FileStorage:/home/gloria/.local/share/caddy"}
2023/09/05 09:31:24.055 INFO tls finished cleaning storage units
2023/09/05 09:31:24.056 INFO autosaved config (load with --resume flag) {"file": "/home/gloria/.config/caddy/autosave.json"}
2023/09/05 09:31:24.056 INFO serving initial configuration
coraza.conf-recommended
やcrs-setup.conf.example
、owasp_crs/*.conf
は、
caddyバイナリ内に組み込まれているものが参照される点に注意が必要です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# example/Caddyfile
{
debug
auto_https off
order coraza_waf first
}
:8080 {
coraza_waf {
load_owasp_crs
directives `
Include @coraza.conf-recommended
Include @crs-setup.conf.example
Include @owasp_crs/*.conf
SecRuleEngine On
SecDebugLog /dev/stdout
SecDebugLogLevel 9
SecRule REQUEST_URI "@streq /admin" "id:101,phase:1,t:lowercase,deny,status:403"
SecRule REQUEST_BODY "@rx maliciouspayload" "id:102,phase:2,t:lowercase,deny,status:403"
SecRule RESPONSE_HEADERS::status "@rx 406" "id:103,phase:3,t:lowercase,deny,status:403"
SecResponseBodyAccess On
SecResponseBodyMimeType application/json
SecRule RESPONSE_BODY "@contains responsebodycode" "id:104,phase:4,t:lowercase,deny,status:403"
`
}
reverse_proxy {$HTTPBIN_HOST:localhost}:8081
}
サンプルWebアプリサーバを起動
動作検証に用いるサンプルWebアプリサーバを、てきとうに開いた別の端末で起動します。
これはなんでも構いませんが、今回はCorazaの公式ドキュメントでも用いられているgo-httpbin
を用います。
1
2
3
% /usr/lib/go-1.20/bin/go run github.com/mccutchen/go-httpbin/v2/cmd/go-httpbin@v2.9.0 -port 8081
go: downloading github.com/mccutchen/go-httpbin/v2 v2.9.0
go-httpbin listening on http://0.0.0.0:8081
動作確認
WAFとして正常に機能しているか、あやしいクエリ(id=' OR 1=1 -- '
)を投入して試してみます。
単純なリクエストの場合、特にエラーにならず200 OK
が返ってきます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
% curl -l -i "http://localhost:8080/"
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' camo.githubusercontent.com
Content-Type: text/html; charset=utf-8
Date: Tue, 05 Sep 2023 09:40:35 GMT
Server: Caddy
Transfer-Encoding: chunked
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' value='text/html;charset=utf8'>
<meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
同じパスにSQLインジェクションっぽい文字列をクエリに追加すると、403 Forbidden
が返されます。
1
2
3
4
5
% curl -l -i "http://localhost:8080/?id='%20OR%201%3D1%20--%20"
HTTP/1.1 403 Forbidden
Server: Caddy
Date: Tue, 05 Sep 2023 09:38:50 GMT
Content-Length: 0
リバースプロキシサーバ側のログには、下記のようにSQL Injection Attack Detected
と出ます。
1
2
3
4
5
2023/09/05 09:45:21.062 ERROR http.handlers.waf [client "127.0.0.1"] Coraza: Access denied (phase 2). SQL Injection Attack Detected via libinjection [file "@owasp_crs/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"] [line "5101"] [id "942100"] [rev ""] [msg "SQL Injection Attack Detected via libinjection"] [data "Matched Data: s&1c found within ARGS:id: ' OR 1=1 -- "] [severity "critical"] [ver "OWASP_CRS/4.0.0-rc1"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-sqli"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/248/66"] [tag "PCI/6.5.2"] [hostname ""] [uri "/?id='%20OR%201%3D1%20--%20"] [unique_id "eMtYQWlXbjIxMGER"]
2023/09/05 09:45:21.063 ERROR http.handlers.waf [client "127.0.0.1"] Coraza: Access denied (phase 2). Inbound Anomaly Score Exceeded (Total Score: 5) [file "@owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "6836"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "emergency"] [ver "OWASP_CRS/4.0.0-rc1"] [maturity "0"] [accuracy "0"] [tag "anomaly-evaluation"] [hostname ""] [uri "/?id='%20OR%201%3D1%20--%20"] [unique_id "eMtYQWlXbjIxMGER"]
2023/09/05 09:45:21.063 DEBUG http.log.error interruption triggered {"request": {"remote_ip": "127.0.0.1", "remote_port": "50636", "client_ip": "127.0.0.1", "proto": "HTTP/1.1", "method": "GET", "host": "localhost:8080", "uri": "/?id='%20OR%201%3D1%20--%20", "headers": {"User-Agent": ["curl/7.81.0"], "Accept": ["*/*"]}}, "duration": 0.001005506, "status": 403, "err_id": "eMtYQWlXbjIxMGER", "err_trace": ""}
とりあえず動作することは分かったので、次回はHTTPS対応などを行っていきます。
その他
- テスト用フレームワーク: https://github.com/coreruleset/go-ftw
- ルールテスト用サンドボックス(Web版): https://playground.coraza.io/