GoAheadにおける3つのTLSバグ
Three TLS Bugs in GoAhead (foxio.io)
要約
GoAhead組み込みWebサーバーで発見された3つのTLSバグは、IoT、ファイアウォール、軍事用デバイスなど、何億もの機器に影響を与える可能性があります。これらのバグにより、失効したクライアント証明書の受け入れ、暗号化されたセッションの復旧、チェーン深度制限のバイパスが可能になります。影響はTLSバックエンド(OpenSSL、LibreSSL、Mbed TLS)と設定によって異なりますが、GoAheadバージョン6.1.1で修正されています。
全文翻訳
Choose Your Backend: Three TLS Bugs in GoAhead
TLDR
GoAheadは、Palo Alto Networks、Comcast、D-Link、HP、Canon、Honeywell、GE、Oracle、Siemens、Rockwell、Digi、Lockheed Martin、Boeingなどの製品を含む、数億ものIoT、ファイアウォール、OT、軍事用デバイスで使用されています。
GoAheadのTLS統合における3つの独立したバグにより、失効したクライアント証明書が受け入れられたり、傍受者によって暗号化されたセッションが復旧されたり、設定されたチェーン深度制限が強制されなかったりする可能性があります。影響は、TLSライブラリとデバイスに組み込まれた検証設定によって異なります。
-
OpenSSL互換バックエンド - クリティカル、CVSS 9.1 - CRLはロードされるものの強制されず、失効したクライアント証明書がサイレントに受け入れられます。GOAHEAD-SA-2026-0006。
-
LibreSSLレガシー検証器 - ハイ、CVSS 7.4 - クライアント証明書が必要だが発行者チェックが無効になっている場合、過剰な深度のクライアントチェーンが受け入れられます。これにより、チェーン深度制限がバイパスされます。GOAHEAD-SA-2026-0004。
-
Mbed TLS - ミディアム、CVSS 5.9 - バッファ長のタイポにより、2048ビットのDHモジュラスが1バイトに削減されます。クライアントが破損したパラメータを受け入れる場合、TLS 1.2 DHE_RSAセッションは復号可能です。GOAHEAD-SA-2026-0005。
| TLSビルド | GoAheadバージョン | 必要な設定 | 修正または緩和策 |
|---|---|---|---|
| OpenSSL互換バックエンド | >= 3.4.5, <= 6.1.0 | mTLSが有効でCRLが設定されている。発行者検証が有効でも、失効したクライアント証明書は引き続き受け入れられます。 | 6.1.1以降にアップグレードしてください。 |
| Mbed TLSバックエンド | >= 4.0.2, <= 6.1.0 | TLS 1.2 DHE_RSAが、破損したDHパラメータを受け入れるクライアントとネゴシエートされる。 | 6.1.1以降にアップグレードするか、有限体DHEスイートを無効にするか、OpenSSLバックエンドを使用してください。 |
| LibreSSLレガシー検証器 — 3.2.x または 3.3.2–3.3.6 | <= 6.1.0 | VERIFY_PEER=1 かつ VERIFY_ISSUER=0。過剰な深度のクライアントチェーンが受け入れられます。 | VERIFY_ISSUER=1 を信頼されたCAバンドルと共に有効にするか、OpenSSL / LibreSSL 4.x を使用してください。 |
Background
GoAheadは、ルーター、スイッチ、プリンター、ゲートウェイ、産業用制御などのデバイス管理に使用される組み込みWebサーバーであり、数億ものデバイスに展開されています。
以下の各発見事項は、ビルドと設定に応じて、それらのデプロイメントの特定のサブセットに影響します。
GoAheadは、特定の構成に関してコンパイル時のアプローチを採用しています。
-
ME_GOAHEAD_SSL_VERIFY_PEER=1はクライアント証明書を要求します。 -
ME_GOAHEAD_SSL_AUTHORITYは信頼されたCAのPEMバンドルを指します。 -
ME_GOAHEAD_SSL_VERIFY_ISSUER=1は、そのバンドルに対して発行者の信頼を確立するために証明書チェーンを要求します。 -
ME_GOAHEAD_SSL_REVOKEはPEMエンコードされた証明書失効リスト(CRL)を指します。 -
OpenSSLバックエンドはチェーン深度制限を10に設定します(
VERIFY_DEPTH)。
OpenSSL互換ビルド
[画像]
GoAheadのOpenSSLバックエンドは、設定されたCRLを証明書ストアにロードしますが、失効チェックを有効にすることはありません。したがって、発行者検証が有効になっていても、失効したクライアント証明書はmTLSハンドシェイクを完了できます。
CRLはsslOpen()でロードされます。
int sslOpen(void *data, int size, int *flags)
{
// ...
if (goahead->ssl_crl != NULL) {
X509_STORE_load_locations(certStore, goahead->ssl_crl, NULL);
}
// ...
}
OpenSSLはCRLチェックを有効にするためにX509_V_FLAG_CRL_CHECKを必要とします。ファイルをストアにロードしても、そのフラグは設定されません。CRLまたはOCSPチェックが有効になっていない場合、check_revocation()は失効ステータスをチェックする前に成功を返します。
int check_revocation(X509_STORE_CTX *ctx)
{
// ...
if (ctx->untrusted == NULL) {
return 1; /* success */
}
// ...
}
GoAheadはどちらのCRLフラグも設定しません。OpenSSLはロードされたリストをチェックせずに成功を返します。アプリケーションのコールバックは、default:ブランチを通じてエラー23(X509_V_ERR_CERT_REVOKED)を拒否しますが、検証器はそのエラーを生成しません。
GoAhead 6.1.0でOpenSSL 3.6.2を使用した場合、発行者検証が有効な状態で、以前は信頼されていたが失効したCA署名のクライアント証明書がmTLSハンドシェイクを完了しました。検証コールバックはチェーン全体でエラー0を報告しました。
verify_callback: depth=0, err=0, subject=/CN=client.example.com
verify_callback: depth=1, err=0, subject=/CN=Root CA
X509_STORE_load_locations()は成功するため、起動時にCRL強制が無効であるという警告は生成されません。
影響を受けるもの: CRLが設定されているOpenSSL互換バックエンドを使用するGoAhead 3.4.5から6.1.0。セキュリティへの影響は、VERIFY_PEER=1、信頼されたCAバンドル、VERIFY_ISSUER=1の場合に最も明確です。以前は信頼されていたが失効したIDがアクセスを維持します。発行者チェックが無効な場合、CRLの欠陥は残りますが、未知の発行者は既にポリシーによって受け入れられています。
修正: 6.1.1では、CRLをロードした後にチェックが行われます。
int sslOpen(void *data, int size, int *flags)
{
// ...
if (goahead->ssl_crl != NULL) {
X509_STORE_load_locations(certStore, goahead->ssl_crl, NULL);
X509_STORE_set_flags(certStore, X509_V_FLAG_CRL_CHECK);
}
// ...
}
Mbed TLSビルド
[画像]
GoAheadのmbed TLSバックエンドは、mbedtls_ssl_conf_dh_param_bin()を通じてRFC 3526 2048ビットDHグループを設定します。その関数に渡されるモジュラス長は、ジェネレータバッファから取得されます。
int mbedtls_ssl_conf_dh_param_bin(mbedtls_ssl_config *conf, const unsigned char *dhm_p, size_t dhm_p_len, const unsigned char *dhm_g, size_t dhm_g_len)
{
// ...
int ret = mbedtls_ssl_set_dh_param(ssl, dhm_p, dhm_p_len, dhm_g, dhm_g_len);
// ...
}
// In GoAhead's code:
int mbedtls_ssl_set_dh_param(mbedtls_ssl_context *ssl, const unsigned char *p, size_t p_len, const unsigned char *g, size_t g_len)
{
// ...
// p_len and g_len are both set to sizeof(dhm_g) which is 1
// ...
}
サイズ引数は、mbed TLSに各バッファから読み取るバイト数を指示します。呼び出しでは、dhm_gは1バイト長にすぎませんが、両方に対してsizeof(dhm_g)が使用されます。dhm_pの長さはsizeof(dhm_p)、つまり256バイトであるべきです。
その結果、mbed TLSはdhm_pの最初の1バイトのみを読み取ります:0xFF(255)。残りの255バイトは破棄されます。この値は受け入れられ、TLS 1.2 DHE_RSAハンドシェイク中にクライアントに送信されます。
ServerKeyExchangeメッセージでは、00 01 ffは1バイトのDHパラメータ(値255)を示します。完全な256バイトの値を含むべきです。
破損したパラメータにより、可能な共有秘密の値は8つだけになります:16進数で01、02、04、08、10、20、40、80です。傍受者は各候補を試して、キャプチャされたトラフィックを正常に復号できるかどうかをチェックすることで、正しい値を確認できます。
添付のPoCでは、5回の試行後に共有秘密を再現することに成功しました。これはクライアントの秘密と一致し、同じTLSセッションキーがHTTP交換を復号するために導出できることを確認しました。
パッシブな傍受者は、記録されたハンドシェイクと暗号化されたアプリケーションレコードを使用して、完全にオフバンドでキャプチャされたセッションを復号できます。
エクスプロイトには、破損したDHパラメータを受け入れるクライアントが必要です。テストされたOpenSSLクライアントは、DH_check_params_ex: modulus too smallでこれを拒否しました。カスタムまたは組み込み実装で最小サイズチェックがない、寛容なクライアントは、復号可能なセッションを完了できます。ECDHEセッションとTLS 1.3は、この設定された有限体グループの影響を受けません。
影響を受けるもの: TLS 1.2 DHE_RSAスイートを使用し、クライアントがパラメータを受け入れるmbed TLSバックエンドを使用するGoAhead 4.0.2から6.1.0。2018年にバイナリパラメータAPIへの移行中に誤って入力され、2024年4月のTLSリファクタリングでも存続しました。Mbed TLS 4.xはTLS_DHE_*スイートを削除したため、このDHEパスは影響を受けません。
環境のJA4Sログでこれらのスイートのいずれかを確認した場合、影響を受けている可能性があります(例:t120100_009f_1ca028f07214)。
実行したいサンプルクエリ(Splunkなどで)は次のようになります。
index=your_index sourcetype=your_sourcetype JA4S_TLS_Fingerprint="*009f*" OR JA4S_TLS_Fingerprint="*001f*" OR JA4S_TLS_Fingerprint="*002f*" OR JA4S_TLS_Fingerprint="*003f*" OR JA4S_TLS_Fingerprint="*004f*" OR JA4S_TLS_Fingerprint="*005f*" OR JA4S_TLS_Fingerprint="*006f*" OR JA4S_TLS_Fingerprint="*007f*" OR JA4S_TLS_Fingerprint="*008f*" OR JA4S_TLS_Fingerprint="*009f*" OR JA4S_TLS_Fingerprint="*00af*" OR JA4S_TLS_Fingerprint="*00bf*" OR JA4S_TLS_Fingerprint="*00cf*" OR JA4S_TLS_Fingerprint="*00df*" OR JA4S_TLS_Fingerprint="*00ef*" OR JA4S_TLS_Fingerprint="*00ff*"
上記のカーブで0014を置き換えます。
修正: 6.1.1でタイポが修正されました。
LibreSSLビルド
[画像]
本質的に、攻撃者はGoAheadの制限を超える証明書チェーンを提示し、影響を受けるビルドでmTLSハンドシェイクを完了させることができます。LibreSSLは深度エラーの代わりに発行者エラーを報告します。GoAheadは、発行者チェックが無効な場合にその発行者エラーを受け入れます。
GoAhead 6.1.0は、SSL_CTX_set_verify_depth(sslctx, VERIFY_DEPTH)を使用してチェーン深度制限を10に設定します。その検証コールバックも深度をチェックします。
static int verify_callback(X509_STORE_CTX *ctx)
{
// ...
if (ok && depth > ctx->ctx->verify_depth) {
// ...
if (ctx->error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
// ...
return 0; /* accepted */
}
// ...
return 0; /* accepted */
}
// ...
}
このチェックは、okがtrueで、報告された深度が制限を超え、かつエラーが既に存在しない場合にのみ深度エラーを割り当てます。その後、コールバックはエラーを分類します。
エラー20(UNABLE_TO_GET_ISSUER_CERT_LOCALLY)は、発行者チェックが無効な場合に許容されます。エラー22(CERT_CHAIN_TOO_LONG)は拒否されます。
LibreSSL 3.2.xおよび3.3.2–3.3.6は、ピア検証のデフォルトでレガシーX.509検証器を使用します。そのチェーン構築ループにはFIXMEコメントが含まれています(これがGoAheadを調査するきっかけとなりました)。
// FIXME: This loop is not entirely correct. It is not checking the depth of the
// certificate chain. The maximum depth of the chain is not checked.
// The loop stops building the chain without recording the depth error.
ループは深度エラーを記録せずにチェーンの構築を停止します。この失敗は後にエラー20としてGoAheadに到達します。VERIFY_ISSUER=0の場合、コールバックはそれを許容します。
GoAhead 6.1.0をLibreSSL 3.3.6にリンクした場合、攻撃者が制御する自己署名CAにルートされた17証明書のチェーンが、次のコールバックトレースを生成しました。
verify_callback: depth=16, err=20, subject=/CN=attacker-ca
verify_callback: depth=17, err=20, subject=/CN=attacker-root
ハンドシェイクは完了し、クライアント証明書の要件の背後にあるリソースはHTTP 200を返しました。LibreSSL 4.3.1に対してビルドされた同じGoAheadソースは、深度10でエラー22を受け取り、チェーンを拒否しました。
VERIFY_ISSUER=0の場合、通常の自己署名証明書は既に許可されています。過剰な深度のチェーンは、検証器が許容される発行者エラーを報告するため、個別のチェーン深度制限をバイパスします。信頼されたクライアントCAを要求するデプロイメントはそれを拒否します。
実証された設定: LibreSSLレガシー検証器、ME_GOAHEAD_SSL_VERIFY_PEER=1、およびME_GOAHEAD_SSL_VERIFY_ISSUER=0を使用したGoAhead 6.1.0。発行者信頼を有効にするとエラー20が拒否されます。OpenSSL、BoringSSL、AWS-LC、およびLibreSSL 4.xビルドは、このレガシー検証器トリガーの影響を受けません。
Conclusion
3つの発見事項すべてが、GoAheadとそのTLSライブラリとの統合に影響します。CRLの強制は検証フラグに依存し、DHパラメータは提供されたバッファ長に依存し、チェーン深度の強制は検証エラーの処理方法に依存します。これらの欠陥のいずれも、影響を受けるハンドシェイクの完了を妨げません。
バックエンドとビルド構成によって、どの欠陥が適用されるかが決まります。LibreSSLビルドは、OpenSSL互換バックエンドのCRL欠陥とレガシー検証器の深度問題の両方を継承する可能性があります。mbed TLSバックエンドは個別の検証パスを使用しますが、クライアントがパラメータを受け入れる場合のDH切り詰めには影響を受けます。
フリート内の公開されているサーバーで影響を受けるものを検出するには、さまざまなバックエンドに対応するJA4Scan-TLSフィンガープリントを以下に示します。
| Server (GoAhead 6.1.x) | JA4Scan-TLS |
| OpenSSL 3.6.2 | 03100h0s00sn_1fe27f6025fb_d06dc4648d2f_sr00ss537461 |
| LibreSSL 3.3.6 | 03100h0s00s0_9d52559f55bc_c64486648d2f_sr00sc24603c |
| LibreSSL 4.3.1 (depth bypass unaffected) | 03100h0r00s0_9d52559f55bc_c64486206461_sr00scc98ba1 |
| mbedTLS 3.6.6 | 23100h0s01s0_d7d3025021f4_0475c5cede0d_sr0ss02265cf |
JA4Scanのブログ投稿全文はこちらをお読みください。
https://foxio.io/blog/introducing-ja4scan-active-server-fingerprinting-for-tls-and-quic
POC
再現コードはhttps://github.com/FoxIO-LLC/blog_poc/tree/main/chooseyourbackendで入手可能です。
Timeline
-
2026年4月22日 - LibreSSLレポート
-
2026年4月27日 - MbedTLSレポート
-
2026年4月30日 - OpenSSLレポート
-
2026年8月2日 - GoAhead 6.1.1リリース
-
2026年10月12日 - アドバイザリ公開
-
2026年10月26日 - ブログ公開
References
GOAHEAD-SA-2026-0004 / CVE-2026-115884: mTLSチェーン深度バイパス
GOAHEAD-SA-2026-0005: MbedTLS DHパラメータ切り詰め
GOAHEAD-SA-2026-0006 / CVE-2026-115881: OpenSSL CRLチェック無効
OpenSSL [X509_STORE_set_flags(3)](https://docs.openssl.org/3.6/man3/X509_STORE_add_cert/)
RFC 3526 - MODP Diffie-Hellmanグループ