shadow1ng/fscan · error
webscan_request_body_read_failed
webscan_request_body_read_failed
Error message
webscan_request_body_read_failed: %w
What it means
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.
Source
Thrown at webscan/lib/Eval.go:461
i--
}
// 右移已使用的位,更新计数器
cache >>= letterIdxBits
remain--
}
return string(randBytes)
}
// DoRequest 执行 HTTP 请求
// session 为 nil 时回退到全局 state(兼容 CEL runtime 等无 session 场景)
func DoRequest(req *http.Request, redirect bool, session *common.ScanSession) (*Response, error) {
// 处理请求头
if req.Body != nil && req.Body != http.NoBody {
body, err := io.ReadAll(req.Body)
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.GetText("webscan_request_body_read_failed"), err)
}
_ = req.Body.Close()
req.Body = io.NopCloser(bytes.NewReader(body))
req.GetBody = func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(body)), nil
}
req.ContentLength = int64(len(body))
// 设置 Content-Length
req.Header.Set("Content-Length", strconv.FormatInt(req.ContentLength, 10))
// 如果未指定 Content-Type,设置默认值
if req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
}
// 执行请求View on GitHub (pinned to 95cc12e753)
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
Example fix
// before (unclosable streaming body) req.Body = pipeReader // after b, _ := io.ReadAll(pipeReader) req.Body = io.NopCloser(bytes.NewReader(b))
Defensive patterns
Strategy: fallback
Validate before calling
if req.GetBody == nil && req.Body != nil && req.Body != http.NoBody {
// body may not be replayable; buffer it yourself first
} Try / catch
resp, err := DoRequest(req, false, session)
if err != nil {
if strings.Contains(err.Error(), i18n.GetText("webscan_request_body_read_failed")) {
// rebuild request with in-memory body and retry once
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- network_rate_limited
- %s: %w (webscan_request_create_error)
- %s: %w (webscan_http_request_error)
- %s: %w (webscan_request_send_error)
- webscan_poc_convert_failed
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/054948ff08e8d98b.
Report an issue: GitHub.