
वीचैट 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))
साक्ष्य:
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 पथ इन सुरक्षाओं को पूरी तरह से दरकिनार कर देता है। फिर यह निर्धारित करने के लिए दूरस्थ एंडपॉइंट की जाँच की गई कि क्या यह निश्चित है और क्या यह निष्कर्ष बदलता है।
स्थिर परिभाषाओं और आसन्न उपयोगकर्ता-जानकारी प्राप्ति कोड की समीक्षा की गई। OAuth टोकन एंडपॉइंट WeChat के लिए हार्डकोडेड है और उपयोगकर्ता-आपूर्ति नहीं है, लेकिन प्रतिक्रिया निकाय सामग्री अभी भी दूरस्थ नेटवर्क डेटा है और इसे बिना किसी आकार सीमा के पढ़ा जाता है। उपयोगकर्ता-जानकारी अनुरोध के लिए उसी फ़ाइल में एक दूसरा अप्रतिबंधित अपस्ट्रीम रीड मौजूद है।
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
}
}
साक्ष्य:
backend/internal/handler/auth_wechat_oauth.go:52-54 अपस्ट्रीम एंडपॉइंट्स को WeChat URL पर हार्डकोड करता है।backend/internal/handler/auth_wechat_oauth.go:1197 fetchWeChatUserInfo() के अंदर एक दूसरा अप्रतिबंधित io.ReadAll(resp.Body) करता है।backend/internal/server/middleware/backend_mode_guard.go:38-55 स्पष्ट रूप से /auth/oauth/wechat/callback और /auth/oauth/wechat/payment/callback को अनुमति देता है, भले ही बैकएंड मोड सक्षम हो।विश्लेषण: निश्चित WeChat होस्टनाम एंडपॉइंट चयन पर हमलावर के नियंत्रण को कम करता है, लेकिन यह कोई प्रतिक्रिया आकार सीमा नहीं जोड़ता है। कमजोरी बनी रहती है क्योंकि एप्लिकेशन बिना किसी ऊपरी सीमा के मनमाने दूरस्थ प्रतिक्रिया बाइट्स को मेमोरी में बफर करता है। यह उत्पादन हैंडलर कोड है, परीक्षण, डेमो, या मृत कोड नहीं।
exchangeWeChatOAuthCode() एक आउटबाउंड HTTP अनुरोध जारी करता है और फिर backend/internal/handler/auth_wechat_oauth.go:1142-1149 पर बिना किसी आकार गार्ड के io.ReadAll(resp.Body) को कॉल करता है।backend/internal/server/routes/auth.go:73 और backend/internal/server/routes/auth.go:80 सार्वजनिक WeChat प्रारंभ/कॉलबैक मार्गों को पंजीकृत करते हैं, और backend/internal/handler/auth_wechat_oauth.go:206 कॉलबैक प्रवाह को fetchWeChatOAuthIdentity() के माध्यम से मार्ग देता है।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 पर io.LimitReader का उपयोग करती हैं, लेकिन चिह्नित फ़ंक्शन इनमें से किसी का उपयोग नहीं करता है।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 पर कॉलबैक पथों की अनुमति देता है।सत्य भेद्यता
मुख्य साक्ष्य:
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 कोड बस ऐसा नहीं करता है।resp.Body को io.LimitReader(..., maxBytes+1) के साथ लपेटें और सीमा से अधिक निकायों को अस्वीकार करें।backend/internal/handler/auth_wechat_oauth.go:1197 पर आसन्न उपयोगकर्ता-जानकारी रीड पर समान सुधार लागू करें, जिसमें समान अप्रतिबंधित पैटर्न है।backend/internal/service/upstream_response_limit.go:26-40 से मौजूदा सीमित-रीड पैटर्न का पुन: उपयोग करना पसंद करें ताकि OAuth प्रवाह शेष कोडबेस के अपस्ट्रीम प्रतिक्रियाओं को संभालने के तरीके के अनुरूप हो।backend/internal/handler/auth_wechat_oauth.go:206) और भुगतान कॉलबैक (backend/internal/handler/auth_wechat_oauth.go:438)।