config: propagate errors through Config context (#513)#560
config: propagate errors through Config context (#513)#560Christbowel wants to merge 1 commit intovulncheck-oss:mainfrom
Conversation
Add Errors []error field to Config with five methods: - AddError(err) accumulate an error from a protocol call - HasErrors() bool true if any errors are present - HasTimeoutError() true for context.DeadlineExceeded / net.Error.Timeout - HasEOFError() true for io.EOF (server closed connection after payload) - ClearErrors() reset the error list between retries Add DoRequestErr() to protocol/httphelper.go which returns the raw error instead of a bool, allowing callers to inspect and accumulate it via conf.AddError before deciding whether the failure is expected. This fixes the false-negative described in vulncheck-oss#415 where a fire-and-forget payload causes the HTTP client to time out (or receive EOF) while the exploit is actually succeeding on the target. Closes vulncheck-oss#513 Relates to vulncheck-oss#415
terrorbyte
left a comment
There was a problem hiding this comment.
Thank you for your first contribution! This is pretty much exactly what I was thinking about doing in the issues you linked. My only ask is to maybe add an additional State struct to config that can hold the Rhost specific state requests so that the errors can be cleared as they iterate and to maybe add the new error handling to the core protocol HTTP calls.
| // Errors accumulates errors from protocol calls so exploit implementations | ||
| // can inspect the nature of failures after the fact. | ||
| // Use AddError to populate and HasTimeoutError/HasEOFError/HasErrors to query. | ||
| Errors []error |
There was a problem hiding this comment.
At the moment these error states don't get cleared here:
Lines 578 to 600 in 3845e7e
This will make it so that the errors continue through to multiple rhosts targets.
I was mulling this exact problem over recently and I think I've come to the conclusion that maybe we should add a Config.State struct that can hold the rhost specific state that also clears on a new iteration of conf.RhostsNTuple that also just clears that state for each iteration.
| // return false | ||
| // } | ||
| // return true | ||
| func (conf *Config) HasTimeoutError() bool { |
There was a problem hiding this comment.
Do we want to add the error propagation directly into the protocol calls? I think I'd like to make this accessible uniformly across all HTTP calls!
| // | ||
| // Use this variant for fire-and-forget payloads where a timeout or EOF is | ||
| // expected and should not be treated as a failure. | ||
| func DoRequestErr(client *http.Client, req *http.Request) (*http.Response, string, error) { |
There was a problem hiding this comment.
This might end up being a good place to apply #547 !
|
Thanks for the detailed feedback! I've read through #547 and understand the direction. For the For the uniform HTTP propagation, would it make sense to wait until #547 is merged and align |
Problem
DoRequestreturns only aboolon failure, discarding the underlyingerror. This causes false-negatives with fire-and-forget payloads: the
server receives the payload, stops responding while executing it, and the
HTTP client gets a timeout or EOF, which the framework incorrectly
records as a failed exploit.
This pattern is visible in the wild in
vulncheck-oss/cve-2025-55182,where the terminal output shows:
The exploit succeeded, but without a way to inspect the error type,
RunExploithad no choice but to returnfalse.Solution
Rather than changing
DoRequest(which would break the existing API),this PR adds two small, independent pieces:
config/config.go— a newErrors []errorfield onConfigandfive methods to populate and query it:
AddError(err)HasErrors() boolHasTimeoutError() boolcontext.DeadlineExceededornet.Error.Timeout()HasEOFError() boolio.EOF— server closed connection after receiving payloadClearErrors()protocol/httphelper.go— a newDoRequestErr()function thatmirrors
DoRequestbut returns the rawerrorinstead of abool,so the exploit can pass it to
conf.AddErrorand inspect it.DoRequestis completely untouched — no existing exploit breaks.Usage
Tests
16 new unit tests in
config/config_errors_test.gocover:context.DeadlineExceededdirect and via expired contextnet.ErrorwithTimeout() == trueandfalsenet.OpErrorwrappingcontext.DeadlineExceeded(real*url.Errorshape)io.EOFdirect and wrapped innet.OpErrorClearErrorson empty and non-empty configserrors.IsCloses #513
Relates to #415