Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

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

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応答読み取りを特定し、是正ガイダンスを提供します。

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

人気

すべて見る →

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

すべてのツールを探索

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

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

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) でレスポンスボディ全体を読み取ります。

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 クエリパラメータを受け取ります。

// 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)
// 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
    }
    ...
}
// 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)を通じてシンクに到達し、支払いコールバックは同じシンクに直接到達します。

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)
    ...
}
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コードはそれを使用していません。

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
}
// DefaultUpstreamResponseReadMaxBytes is the default read cap for upstream non-streaming response bodies.
const DefaultUpstreamResponseReadMaxBytes int64 = 128 * 1024 * 1024
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)) を使用。
  • 対照的に、backend/internal/handler/auth_wechat_oauth.go:1149 は依然として生の io.ReadAll(resp.Body) を使用。

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

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

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

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