Jun 28, 2022

Webサーバへのアタックが多くなってきたのでWAF(naxsi)を導入してみた。

自宅 Web サーバのログを確認しているとインジェクション攻撃など、サーバへのアタックが多くなっているようです。そこで NGINX 用の WAF(naxsi)を導入することにしました。当初、ModSecurity も検討しましたが、今回は Web サーバの規模も小さくシンプルで自分で一つ一つルール設定できそうな naxsi を選択することにしました。

前回のブログから 1 年以上経ってしまいました。この1年でネットワークやコンテナの構成も更新もしましたので、今は以下のような構成となっています。 ネットワーク、サーバ構成

ネットワークの構成は、フロントに TL-SG105E を置いてその配下に IPv6(IPoE)接続は、WSR-3200AX4S、IVP4 固定アドレス用(PPPoE)に EdgerouterX を配置。色々構成変えて試しましたが、今は、この構成で落ち着きました。またネットワークトラフィック監視用に Raspberry Pi を配置しました。ネットワーク機器のログは rsyslog コンテナに集約し、prometheus,victoria,loki 経由 grafana でモニタリングしています。この辺の備忘録はそのうち整理したいと思います。そう思って半年以上放置状態でそろそろ設定した内容忘れそうです。

naxsi のロードモジュール作成

nginx バージョンに合わせて naxsi のロードモジュール作成したいと思います。当初は、モジュールのみコンパイルして適用してみましたが、実行時に core dump を吐いていたので、原因追求はしないで、nginx 本体もコンパイルすることにしました。 16 行以降の.configure のパラメータは、稼働中の nginx 環境からパラメータを取り出して利用しました。47 行以下が本番用の Image ファイルになります。 コンパイルした結果を alpine+nginx に上書きしています。

Dockerfile

環境は、alpine+nginx+naxsi を Docker(Podman)で、nginx-1.20.2、openssl-3.0、naxsi1.3 をコンパイルします。

Dockerfile
FROM alpine AS builder
 
LABEL version="1.0" "akiboi.net"="akiboI"
RUN apk add --update --no-cache git libtool autoconf automake make g++ pcre-dev zlib-dev linux-headers nginx
COPY build/ /etc/nginx/
WORKDIR /src
RUN wget http://nginx.org/download/nginx-1.20.2.tar.gz
RUN tar zxvf nginx-1.20.2.tar.gz
RUN git clone -b openssl-3.0 https://github.com/openssl/openssl openssl-3.0
RUN git clone https://github.com/nbs-system/naxsi.git
RUN cp /src/naxsi/naxsi_config/naxsi_core.rules /etc/nginx && \
  touch /var/log/naxsi.log && \
  chown nginx /var/log/naxsi.log
WORKDIR /src/nginx-1.20.2
RUN ./configure \
  --prefix=/usr/share/nginx \
  --conf-path=/etc/nginx/nginx.conf \
  --http-log-path=/var/log/nginx/access.log \
  --error-log-path=/var/log/nginx/error.log \
  --lock-path=/var/lock/nginx.lock \
  --pid-path=/run/nginx.pid \
  --modules-path=/usr/lib/nginx/modules \
  --http-client-body-temp-path=/var/lib/nginx/body \
  --http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
  --http-proxy-temp-path=/var/lib/nginx/proxy \
  --http-scgi-temp-path=/var/lib/nginx/scgi \
  --http-uwsgi-temp-path=/var/lib/nginx/uwsgi \
  --with-compat \
  --with-debug \
  --with-pcre-jit \
  --with-http_ssl_module \
  --with-http_stub_status_module \
  --with-http_realip_module \
  --with-http_auth_request_module \
  --with-http_v2_module \
  --with-http_dav_module \
  --with-http_slice_module \
  --with-threads \
  --with-http_addition_module \
  --with-http_gunzip_module \
  --with-http_gzip_static_module \
  --with-http_sub_module \
  --with-openssl=../openssl-3.0 \
  --add-dynamic-module=../naxsi/naxsi_src
RUN make && make install
 
FROM alpine
LABEL version="1.0" "akiboi.net"="akiboI"
RUN apk add --update --no-cache nginx
COPY build/ /etc/nginx/
COPY --from=builder /src/nginx-1.20.2/objs/nginx /usr/sbin
COPY --from=builder /src/nginx-1.20.2/objs/ngx_http_naxsi_module.so /usr/lib/nginx/modules
COPY --from=builder /etc/nginx/naxsi_core.rules /etc/nginx
RUN touch /var/log/naxsi.log && \
  chown nginx /var/log/naxsi.log
CMD ["/usr/sbin/nginx", "-g", "daemon off;"]

その他環境ファイル

nginx のその他設定は、build 配下に必要な環境ファイルを作成し deploy しています。Dockerfile の 5 行目、COPY Build/ /etc/nginx/ 各環境に合わせて nginx を設定ましす。なお naxsi を利用するため default.conf を以下のようにしました。 7 行目に backend の Web サーバを設定します。最初は、10 行目の LearningMode を設定してエラーリストの内容をみながらホワイトリストを作成していきます。 11 行目のファイルに許可したいホワイトリストを追加していくことになります。 エラーリストは、コンテナ内の/var/log/naxsi.log に出力します。今回は別のサーバに syslog を転送し、このログを監視するようにしています。 ログの監視は、nginx-waf->rsyslog->promtail->prometheus,loke,victoria->grafana となっています。

/etc/nginx/default.conf
server {
    listen	443 ssl http2;
    listen [::]:443 ssl http2;
 
    location / {
        proxy_set_header Host $host;
        proxy_pass http://xxx.xxx.xxx:xxxx/;
 
        # NAXSIの設定
        #LearningMode;
        include /etc/nginx/naxsi_my.rules;
        SecRulesEnabled;
        DeniedUrl "/50x.html";
 
        CheckRule "$SQL >= 8" BLOCK;
        CheckRule "$RFI >= 8" BLOCK;
        CheckRule "$TRAVERSAL >= 4" BLOCK;
        CheckRule "$EVADE >= 4" BLOCK;
        CheckRule "$XSS >= 8" BLOCK;
 
        # ログの出力先
        error_log /var/log/naxsi.log;
        error_log syslog:server=xxx.xxx.xxx.xxx:xxx,facility=local1,tag=proxy_access,severity=info;
 
    }
 
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        return 418 "<html><head><title>Blocked By NAXSI</title></head><body><div style='text-align: center'><h1>Malicious Request</h1><hr><p>This Request Has Been Blocked By NAXSI.</p></div></body></html>"
        internal;
    }
}

ホワイトリストの作成

Web にアクセスしながらエラーリストを確認していきます。そのエラーリストに合わせてホワイトリストを作成します。小規模な Web サーバなので一つ一つエラーを確認しながらホワイトリストを作成していきました。 エラーリストの詳細は、こちらを参照してください。ルールにマッチした idN をホワイトリスト化していきます。書き方はこちらのサンプルを見ながら設定しました。Web アクセス、エラーリスト確認、ホワイトリスト追加みたいに作業を進めました。一通り確認が終わったら、default.conf の LearningMode をコメントアウトし本番適用です。

Tip

【alpine+nginxのリスタート】 ホワイトリスト作成は、トライアンドエラーでやること多いので、その都度nginxのホワイトリストを読み直す必要があります。ついつい、systemctl restart nginxとやりたいところですが、alpine+nginxしかいれてませんのでnginx -s reloadで環境設定を読み直し確認していきます。・・・ついつい忘れて毎回nginx -hで確認してしまう。

確認作業

Web にアクセスしてエラーが出ないことを確認します。試しにhttps://www.akiboi.net/param=<script>xxxx</script>と実行すると弾いてくれました。

/var/log/naxsi.log;
2022/06/24 13:03:59 [error] 3#3: *888 NAXSI_FMT: ip=xxx.xxx.xxx.xxx&server=www.akiboi.net&uri=/parm=<script>xxxx</script>&vers=1.3&total_processed=250&total_blocked=1&config=block&cscore0=$XSS&score0=16&zone0=URL&id0=1302&var_name0=, client: xxx.xxx.xxx.xxx, server: , request: "GET /parm=%3Cscript%3Exxxx%3C/script%3E HTTP/1.1", host: "www.akiboi.net", referrer: ""

これで一通り WAF(naxsi)の導入が完了しました。rsyslog のログのサンプルを乗せておきます。こちらのログは、naxsi のログを rsyslog に転送して、ここで ip から GeoIP を利用して地域を付加しています。

/var/log/rsyslog/nginx_waf.log
2022-06-25T10:53:20.456163+09:00 web proxy_access: "NA" "North America" "US" "United States" "San Francisco" "37.730800" "-122.383800" 2022/06/25 01:53:20 [error] 95#95: *3548 NAXSI_FMT: ip=xxx.xxx.xxx.xxx&server=xxx.xxx.xxx.xxx&uri=/owa/auth/logon.aspx&vers=1.3&total_processed=158&total_blocked=3&config=block&cscore0=$RFI&score0=8&zone0=ARGS&id0=1101&var_name0=url, client: xxx.xxx.xxx.xxx, server: , request: "GET /owa/auth/logon.aspx?url=https%3a%2f%2f1%2fecp%2f HTTP/1.1", host: "xxx.xxx.xxx.xxx", referrer: ""
2022-06-25T13:14:29.313530+09:00 web proxy_access: "EU" "Europe" "RU" "Russia" "" "55.738600" "37.606800" 2022/06/25 04:14:29 [error] 95#95: *3592 NAXSI_FMT: ip=xxx.xxx.xxx.xxx&server=xxx.xxx.xxx.xxx&uri=/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php&vers=1.3&total_processed=165&total_blocked=4&config=block&cscore0=$SQL&score0=16&cscore1=$XSS&score1=16&zone0=BODY&id0=1001&var_name0=%3C%3F, client: xxx.xxx.xxx.xxx, server: , request: "POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php HTTP/1.1", host: "xxx.xxx.xxx.xxx:443", referrer: ""
2022-06-25T15:37:22.920420+09:00 web proxy_access: "EU" "Europe" "RU" "Russia" "" "55.738600" "37.606800" 2022/06/25 06:37:22 [error] 95#95: *3669 NAXSI_FMT: ip=xxx.xxx.xxx.xxx&server=xxx.xxx.xxx.xxx&uri=/index.php&vers=1.3&total_processed=201&total_blocked=5&config=block&cscore0=$TRAVERSAL&score0=8&zone0=ARGS&id0=1205&var_name0=s, client: xxx.xxx.xxx.xxx, server: , request: "GET /index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=HelloThinkPHP21 HTTP/1.1", host: "xxx.xxx.xxx.xxx:443", referrer: ""
:

OWA インジェクション、PHPUnit の脆弱性、ThinkPHP という中国ローカルのフレームワークの脆弱みたいな感じですかね。 Grafana+loki でログ見ていると色々な脆弱性をついているなぁと感じました。