Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-51416 — WeChat OAuthハンドラー内の特定のCVEを分析し、サービス拒否につながる無制限のHTTP応答読み取りを特定し、是正ガイダンスを提供します。 | Kitploit
ツール/GitHubGitHub/renio-wow/cve-2026-51416
静的分析脆弱性分析コード分析ウェブセキュリティ
GitHubrenio-wow/cve-2026-51416

CVE-2026-51416

WeChat OAuthハンドラー内の特定のCVEを分析し、サービス拒否につながる無制限のHTTP応答読み取りを特定し、是正ガイダンスを提供します。

リポジトリを見る
4ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

1. 概要

  • 脆弱性タイプ: サービス拒否 / 無制限のHTTPレスポンス読み取り ([CWE-400])
  • フラグ付き場所: backend/internal/handler/auth_wechat_oauth.go:1149
  • 脆弱性の説明: WeChat OAuthトークン交換プロセスは、io.ReadAll(resp.Body) を使用して上流のHTTPレスポンスボディ全体をメモリに読み込みますが、バッファリング前に最大サイズ制限を一切適用していません。

2. 分析

ステップ 1: フラグ付きシンクを backend/internal/handler/auth_wechat_oauth.go:1124 で調査

フラグ付き関数と周囲50行以上のコンテキストを確認しました。シンクはトークン交換ヘルパー exchangeWeChatOAuthCode() です。この関数はWeChatへのGETリクエストを構築し、プレーンな http.Client を使用して送信し、その後 io.ReadAll(resp.Body) でレスポンスボディ全体を読み取ります。

root@kitploit:~
func exchangeWeChatOAuthCode(ctx context.Context, cfg wechatOAuthConfig, code string) (*wechatOAuthTokenResponse, error) {
    endpoint, err := url.Parse(wechatOAuthAccessTokenURL)
    ...
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
    ...
    client := &http.Client{Timeout: 30 * time.Second}
    resp, err := client.Do(req)
    ...
    defer func() { _ = resp.Body.Close() }()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, fmt.Errorf("read wechat access token response: %w", err)
    }
    if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
        return nil, fmt.Errorf("wechat access token status=%d", resp.StatusCode)
    }
    ...
}

証拠:

  • backend/internal/handler/auth_wechat_oauth.go:1142 で client := &http.Client{Timeout: 30 * time.Second} を作成。
  • backend/internal/handler/auth_wechat_oauth.go:1149 で body, err := io.ReadAll(resp.Body) を実行。
  • io.LimitReader も ContentLength チェックも、レスポンスボディのバッファリング前のヘルパー関数呼び出しも存在しない。

分析: ここでの唯一の保護は時間制限(Timeout: 30 * time.Second)であり、メモリ制限ではありません。リモートピアはEOFに到達する前に大きなメモリ割り当てを引き起こす可能性があります。次に、このシンクが本番ルートから到達可能かどうかを追跡しました。

ステップ 2: 公開OAuthコールバックルートからのメインリクエストパスを追跡

認証ルート登録とWeChat OAuthハンドラーを確認しました。ルートはJWT保護グループではなく、公開 /auth グループの下に登録されています。コールバックハンドラーは、フラグ付きフローを呼び出す前に、ユーザー指定の code および state クエリパラメータを受け取ります。

root@kitploit:~
// routes
auth := v1.Group("/auth")
auth.Use(servermiddleware.BackendModeAuthGuard(settingService))
...
auth.GET("/oauth/wechat/start", h.Auth.WeChatOAuthStart)
auth.GET("/oauth/wechat/callback", h.Auth.WeChatOAuthCallback)
root@kitploit:~
// callback handler
func (h *AuthHandler) WeChatOAuthCallback(c *gin.Context) {
    ...
    code := strings.TrimSpace(c.Query("code"))
    state := strings.TrimSpace(c.Query("state"))
    if code == "" || state == "" {
        redirectOAuthError(c, frontendCallback, "missing_params", "missing code/state", "")
        return
    }
    ...
    tokenResp, userInfo, err := fetchWeChatOAuthIdentity(c.Request.Context(), cfg, code)
    if err != nil {
        redirectOAuthError(c, frontendCallback, "provider_error", "wechat_identity_fetch_failed", singleLine(err.Error()))
        return
    }
    ...
}
root@kitploit:~
// start handler
func (h *AuthHandler) WeChatOAuthStart(c *gin.Context) {
    ...
    state, err := oauth.GenerateState()
    ...
    wechatSetCookie(c, wechatOAuthStateCookieName, encodeCookieValue(state), wechatOAuthCookieMaxAgeSec, secureCookie)
    ...
    c.Redirect(http.StatusFound, authURL)
}

証拠:

  • backend/internal/server/routes/auth.go:27-28 はこれらのルートを公開 /auth ルーターグループに配置。
  • backend/internal/server/routes/auth.go:73 は auth.GET("/oauth/wechat/start", h.Auth.WeChatOAuthStart) を登録。
  • backend/internal/server/routes/auth.go:80 は auth.GET("/oauth/wechat/callback", h.Auth.WeChatOAuthCallback) を登録。
  • backend/internal/handler/auth_wechat_oauth.go:160-162 はHTTPリクエストから code と state を読み取る。
  • backend/internal/handler/auth_wechat_oauth.go:206 は fetchWeChatOAuthIdentity(c.Request.Context(), cfg, code) を呼び出す。
  • backend/internal/handler/auth_wechat_oauth.go:105-147 は、状態クッキーを設定してブラウザをOAuthフローにリダイレクトする通常の前処理ステップを示す。

分析: これは公開GETコールバックから到達可能な本番コードです。状態クッキーはOAuth-CSRF保護を提供しますが、トークン交換後に下流に到着するHTTPレスポンスのサイズを制限するものではありません。次に、内部呼び出しチェーンとシンクのすべての本番呼び出し元を追跡しました。

ステップ 3: exchangeWeChatOAuthCode() へのすべての本番呼び出し元を追跡

ハンドラーとヘルパー関数間の内部呼び出しチェーンを追跡しました。メインのコールバックはヘルパー(fetchWeChatOAuthIdentity)を通じてシンクに到達し、支払いコールバックは同じシンクに直接到達します。

root@kitploit:~
func fetchWeChatOAuthIdentity(ctx context.Context, cfg wechatOAuthConfig, code string) (*wechatOAuthTokenResponse, *wechatOAuthUserInfoResponse, error) {
    tokenResp, err := exchangeWeChatOAuthCode(ctx, cfg, code)
    if err != nil {
        return nil, nil, err
    }
    userInfo, err := fetchWeChatUserInfo(ctx, tokenResp)
    ...
}
root@kitploit:~
cfg, err := h.getWeChatOAuthConfig(c.Request.Context(), "mp", c)
...
cfg.redirectURI = h.resolveWeChatPaymentOAuthCallbackURL(c.Request.Context(), c)
tokenResp, err := exchangeWeChatOAuthCode(c.Request.Context(), cfg, code)
if err != nil {
    redirectOAuthError(c, frontendCallback, "token_exchange_failed", "failed to exchange oauth code", err.Error())
    return
}

証拠:

  • backend/internal/handler/auth_wechat_oauth.go:1112-1117 は fetchWeChatOAuthIdentity() が exchangeWeChatOAuthCode() を呼び出し、次に fetchWeChatUserInfo() を呼び出すことを示す。
  • backend/internal/handler/auth_wechat_oauth.go:206 はメインのOAuthコールバックが fetchWeChatOAuthIdentity() を呼び出す場所。
  • backend/internal/handler/auth_wechat_oauth.go:438 は支払いOAuthコールバックが exchangeWeChatOAuthCode() を直接呼び出す場所。

分析: フラグ付きシンクはデッドコードではありません。プロジェクト全体の呼び出し元検索により、同じヘルパーへの2つの本番エントリパス(標準のWeChatログイン/バインドコールバックとWeChat支払いコールバック)が特定されました。次に、このレスポンスパスに存在するサニタイザー、バリデーター、またはフレームワークレベルのサイズガードを確認しました。

ステップ 4: コードベースのサイズ制限と既存のセキュリティパターンを確認

レスポンスボディサイズ制限パターンについてコードベースを検索し、他の場所で上流読み取りに既に使用されている共有ヘルパーを確認しました。そのヘルパーは io.LimitReader(..., maxBytes+1) を使用し、制限を超えた場合に明示的にエラーを返しますが、WeChat OAuthコードはそれを使用していません。

root@kitploit:~
func readUpstreamResponseBodyLimited(reader io.Reader, maxBytes int64) ([]byte, error) {
    ...
    body, err := io.ReadAll(io.LimitReader(reader, maxBytes+1))
    if err != nil {
        return nil, err
    }
    if int64(len(body)) > maxBytes {
        return nil, fmt.Errorf("%w: limit=%d", ErrUpstreamResponseBodyTooLarge, maxBytes)
    }
    return body, nil
}
root@kitploit:~
// DefaultUpstreamResponseReadMaxBytes is the default read cap for upstream non-streaming response bodies.
const DefaultUpstreamResponseReadMaxBytes int64 = 128 * 1024 * 1024
root@kitploit:~
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
...
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 5<<20))

証拠:

  • backend/internal/service/upstream_response_limit.go:26-40 は io.LimitReader(reader, maxBytes+1) を使用する readUpstreamResponseBodyLimited() を定義。
  • backend/internal/service/upstream_response_limit.go:49-61 は共有の境界付き読み取りラッパーとして ReadUpstreamResponseBody() を定義。
  • backend/internal/config/config.go:55-58 は DefaultUpstreamResponseReadMaxBytes を定義。
  • backend/internal/service/crs_sync_service.go:1166 は上流レスポンスの読み取りに io.ReadAll(io.LimitReader(resp.Body, 1<<20)) を使用。
  • backend/internal/service/crs_sync_service.go:1201 は別の上流レスポンスに io.ReadAll(io.LimitReader(resp.Body, 5<<20)) を使用。

分析: プロジェクトは上流レスポンスボディサイズを制限する必要性を既に認識しており、アドホック実装と共有実装の両方が使用されています。フラグ付きWeChat OAuthパスはこれらの保護を完全にバイパスしています。次に、リモートエンドポイントが固定されているかどうか、およびそれが結論を変えるかどうかを確認しました。

ステップ 5: 信頼境界と隣接コードコンテキストを検証

定数定義と隣接するユーザー情報取得コードを確認しました。OAuthトークンエンドポイントはWeChatにハードコードされており、ユーザー指定ではありませんが、レスポンスボディの内容は依然としてリモートネットワークデータであり、サイズ制限なしで読み取られます。同じファイル内にユーザー情報リクエスト用の2つ目の無制限の上流読み取りが存在します。

root@kitploit:~
var (
    wechatOAuthAccessTokenURL = "https://api.weixin.qq.com/sns/oauth2/access_token"
    wechatOAuthUserInfoURL    = "https://api.weixin.qq.com/sns/userinfo"
)
root@kitploit:~
body, err := io.ReadAll(resp.Body)
if err != nil {
    return nil, fmt.Errorf("read wechat userinfo response: %w", err)
}
root@kitploit:~
for _, suffix := range []string{
    "/auth/oauth/linuxdo/callback",
    "/auth/oauth/wechat/callback",
    "/auth/oauth/wechat/payment/callback",
    ...
} {
    if strings.HasSuffix(path, suffix) {
        return true
    }
}

証拠:

  • backend/internal/handler/auth_wechat_oauth.go:52-54 は上流エンドポイントをWeChat URLにハードコード。
  • backend/internal/handler/auth_wechat_oauth.go:1197 は fetchWeChatUserInfo() 内で2つ目の無制限の io.ReadAll(resp.Body) を実行。
  • backend/internal/server/middleware/backend_mode_guard.go:38-55 はバックエンドモードが有効な場合でも /auth/oauth/wechat/callback と /auth/oauth/wechat/payment/callback を明示的に許可。

分析: 固定されたWeChatホスト名は攻撃者のエンドポイント選択の制御を減らしますが、レスポンスサイズ制限を追加するものではありません。アプリケーションが任意のリモートレスポンスバイトを上限なしでメモリにバッファリングするため、脆弱性は残ります。これは本番ハンドラーコードであり、テスト、デモ、またはデッドコードではありません。

判定パス

  • Q1: コードはリモート提供データの無制限読み取りを実行しますか? → はい。 exchangeWeChatOAuthCode() は送信HTTPリクエストを発行し、backend/internal/handler/auth_wechat_oauth.go:1142-1149 でサイズガードなしに io.ReadAll(resp.Body) を呼び出します。
  • Q2: シンクは実際の本番リクエストパスから到達可能ですか? → はい。 backend/internal/server/routes/auth.go:73 と backend/internal/server/routes/auth.go:80 は公開WeChat開始/コールバックルートを登録し、backend/internal/handler/auth_wechat_oauth.go:206 はコールバックフローを fetchWeChatOAuthIdentity() 経由でルーティングします。
  • Q3: 読み取り前に最大レスポンスボディサイズを強制するサニタイザー、バリデーター、またはフレームワーク/ヘルパー関数はありますか? → いいえ。 共有制限ヘルパーは backend/internal/service/upstream_response_limit.go:26-40 に存在し、他のサービスは backend/internal/service/crs_sync_service.go:1166 と backend/internal/service/crs_sync_service.go:1201 で を使用していますが、フラグ付き関数はそれらのいずれも使用していません。

3. 結論

真の脆弱性

主要な証拠:

  • backend/internal/handler/auth_wechat_oauth.go:1149 は、タイムアウトのみを設定した後(サイズ上限なし)に body, err := io.ReadAll(resp.Body) を使用して上流トークンレスポンス全体を読み取ります。
  • backend/internal/server/routes/auth.go:73-82 は /oauth/wechat/callback と /oauth/wechat/payment/callback を公開し、両方のルートは backend/internal/handler/auth_wechat_oauth.go:206 と backend/internal/handler/auth_wechat_oauth.go:438 を介してフラグ付きヘルパーに到達します。
  • backend/internal/service/upstream_response_limit.go:26-40 と backend/internal/service/crs_sync_service.go:1166 は、コードベースが境界付き上流読み取りに既に io.LimitReader を使用していることを示しています — WeChat OAuthコードは単にそれを使用していないだけです。

4. 修正方法

  • WeChatトークンレスポンスのバッファリング前に明示的な最大レスポンスボディサイズを追加します — 例えば、resp.Body を io.LimitReader(..., maxBytes+1) でラップし、制限を超えるボディを拒否します。
  • 同一の無制限パターンを持つ隣接するユーザー情報読み取り(backend/internal/handler/auth_wechat_oauth.go:1197)にも同じ修正を適用します。
  • backend/internal/service/upstream_response_limit.go:26-40 の既存の境界付き読み取りパターンを再利用して、OAuthフローがコードベースの他の部分の上流レスポンス処理と一貫性を持つようにします。
  • 修正後、両方の本番呼び出しパスを再検証します: メインのOAuthコールバック(backend/internal/handler/auth_wechat_oauth.go:206)と支払いコールバック(backend/internal/handler/auth_wechat_oauth.go:438)。
ツールをダウンロード
  • 対照的に、backend/internal/handler/auth_wechat_oauth.go:1149 は依然として生の io.ReadAll(resp.Body) を使用。
  • io.LimitReader
  • Q4: コードはデッド、テスト専用、または本番から除外されていますか? → いいえ。 コードは本番ハンドラーファイル backend/internal/handler/auth_wechat_oauth.go に存在し、backend/internal/server/routes/auth.go:73-82 で登録され、バックエンドモードミドルウェアは依然として backend/internal/server/middleware/backend_mode_guard.go:38-55 でコールバックパスを許可しています。
  • → 真陽性