{"record":{"id":"054948ff08e8d98b","repo":"shadow1ng/fscan","slug":"webscan-request-body-read-failed","errorCode":"webscan_request_body_read_failed","errorMessage":"webscan_request_body_read_failed: %w","messagePattern":"webscan_request_body_read_failed: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"webscan/lib/Eval.go","lineNumber":461,"sourceCode":"\t\t\ti--\n\t\t}\n\n\t\t// 右移已使用的位，更新计数器\n\t\tcache >>= letterIdxBits\n\t\tremain--\n\t}\n\n\treturn string(randBytes)\n}\n\n// DoRequest 执行 HTTP 请求\n// session 为 nil 时回退到全局 state（兼容 CEL runtime 等无 session 场景）\nfunc DoRequest(req *http.Request, redirect bool, session *common.ScanSession) (*Response, error) {\n\t// 处理请求头\n\tif req.Body != nil && req.Body != http.NoBody {\n\t\tbody, err := io.ReadAll(req.Body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"%s: %w\", i18n.GetText(\"webscan_request_body_read_failed\"), err)\n\t\t}\n\t\t_ = req.Body.Close()\n\t\treq.Body = io.NopCloser(bytes.NewReader(body))\n\t\treq.GetBody = func() (io.ReadCloser, error) {\n\t\t\treturn io.NopCloser(bytes.NewReader(body)), nil\n\t\t}\n\t\treq.ContentLength = int64(len(body))\n\n\t\t// 设置 Content-Length\n\t\treq.Header.Set(\"Content-Length\", strconv.FormatInt(req.ContentLength, 10))\n\n\t\t// 如果未指定 Content-Type，设置默认值\n\t\tif req.Header.Get(\"Content-Type\") == \"\" {\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t}\n\t}\n\n\t// 执行请求","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/shadow1ng/fscan/blob/95cc12e753bf43de7004e5aef42a9ffba3934303/webscan/lib/Eval.go#L443-L479","documentation":"DoRequest buffers the outgoing request body with io.ReadAll so it can be replayed (redirects, GMTLS fallback). If reading req.Body fails the error is wrapped as webscan_request_body_read_failed. In practice this fires for bodies that are neither in-memory nor replayable — e.g. streaming readers, network-backed bodies, or a body already consumed/closed.","triggerScenarios":"DoRequest(req, redirect, session) where req.Body is non-nil and non-NoBody but ReadAll errors: network/piped body dropped mid-read, body previously closed, custom ReadCloser returning an error, or a content-length mismatched truncated stream.","commonSituations":"CEL exploit POCs sending file uploads or generated streams, reusing an *http.Request whose body was already drained by a previous DoRequest call, proxy or TLS interception cutting the stream mid-read.","solutions":["Supply the body as an in-memory type: bytes.NewReader/io.NopCloser(bytes.Buffer)","Don't reuse the same *http.Request for multiple DoRequest calls — rebuild it or rely on the Set GetBody replay the library installs","Check the wrapped err for the underlying read failure (connection reset, unexpected EOF)","For file uploads, read the file into memory before constructing the request"],"exampleFix":"// before (unclosable streaming body)\nreq.Body = pipeReader\n// after\nb, _ := io.ReadAll(pipeReader)\nreq.Body = io.NopCloser(bytes.NewReader(b))","handlingStrategy":"fallback","validationCode":"if req.GetBody == nil && req.Body != nil && req.Body != http.NoBody {\n    // body may not be replayable; buffer it yourself first\n}","typeGuard":null,"tryCatchPattern":"resp, err := DoRequest(req, false, session)\nif err != nil {\n    if strings.Contains(err.Error(), i18n.GetText(\"webscan_request_body_read_failed\")) {\n        // rebuild request with in-memory body and retry once\n    }\n    return err\n}","preventionTips":["Build request bodies from bytes.Buffer / strings.Reader","Never reuse a drained *http.Request across DoRequest calls","Read streaming uploads into memory before constructing the request"],"tags":["go","http","request-body","webscan"],"backgroundTag":"network-request-failed","analyzedSha":"95cc12e753bf43de7004e5aef42a9ffba3934303","analyzedAt":"2026-09-06T17:07:30.094Z","contentChangedAt":"2026-09-06T17:07:30.094Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}