
वीचैट OAuth हैंडलर में एक विशिष्ट CVE का विश्लेषण करता है, जो अनबाउंड HTTP प्रतिक्रिया रीड्स की पहचान करता है जो सेवा अस्वीकृति (denial of service) की ओर ले जाती हैं, साथ ही उपचार मार्गदर्शन के साथ।
backend/internal/handler/auth_wechat_oauth.go:1149io.ReadAll(resp.Body) का उपयोग करती है।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 तक पहुँचने से पहले बड़ी मेमोरी आवंटन को ट्रिगर कर सकता है। फिर उत्पादन मार्गों से इस सिंक की पहुँच का पता लगाया गया।
प्रमाणीकरण मार्ग पंजीकरण और WeChat OAuth हैंडलर की समीक्षा की गई। मार्ग सार्वजनिक /auth समूह के अंतर्गत पंजीकृत है, JWT-संरक्षित समूह के अंतर्गत नहीं। कॉलबैक हैंडलर चिह्नित प्रवाह को लागू करने से पहले उपयोगकर्ता-आपूर्ति किए गए 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 प्रतिक्रिया के आकार को सीमित नहीं करती है। फिर आंतरिक कॉल श्रृंखला और सिंक के सभी उत्पादन कॉलर का अनुसरण किया गया।
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() को कॉल करता है।विश्लेषण: चिह्नित सिंक मृत कोड नहीं है। एक परियोजना-व्यापी कॉलर खोज उसी सहायक में दो उत्पादन प्रवेश पथों को हल करती है: मानक WeChat लॉगिन/बाइंड कॉलबैक और WeChat भुगतान कॉलबैक। फिर इस प्रतिक्रिया पथ पर मौजूद किसी भी सैनिटाइज़र, वैलिडेटर, या फ्रेमवर्क-स्तरीय आकार गार्ड की जाँच की गई।
प्रतिक्रिया निकाय आकार-सीमित पैटर्न के लिए कोडबेस खोजा गया, और अन्यत्र साझा सहायक की समीक्षा की गई जो पहले से ही सीमित अपस्ट्रीम रीड के लिए उपयोग किया जाता है। वह सहायक 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))