Skip to content

HTTP Body handling different when redirecting? #565

@Seanstoppable

Description

@Seanstoppable

I noticed this when reviewing the http redirect bug I introduced.

We have this block of code in getCheckRedirect:

b := new(bytes.Buffer)
maxReadLen := int64(scan.scanner.config.MaxSize) * 1024
readLen := maxReadLen
if res.ContentLength >= 0 && res.ContentLength < maxReadLen {
readLen = res.ContentLength
}
bytesRead, _ := io.CopyN(b, res.Body, readLen)
if scan.scanner.config.WithBodyLength {
res.BodyTextLength = bytesRead
}
res.BodyText = b.String()
if len(res.BodyText) > 0 {
if scan.scanner.decodedHashFn != nil {
res.BodyHash = scan.scanner.decodedHashFn([]byte(res.BodyText))
} else {
m := sha256.New()
m.Write(b.Bytes())
res.BodySHA256 = m.Sum(nil)
}
}

and this much LARGER block in the overall Grab method:

buf := new(bytes.Buffer)
maxReadLen := int64(scan.scanner.config.MaxSize) * 1024
readLen := maxReadLen
if resp.ContentLength >= 0 && resp.ContentLength < maxReadLen {
readLen = resp.ContentLength
}
if n, err := io.CopyN(buf, resp.Body, readLen); err != nil && !strings.Contains(err.Error(), "EOF") {
return zgrab2.NewScanError(zgrab2.SCAN_UNKNOWN_ERROR, fmt.Errorf("error populating response body after %d bytes: %w", n, err))
}
encoder, encoding, certain := charset.DetermineEncoding(buf.Bytes(), resp.Header.Get("content-type"))
bodyText := ""
decodedSuccessfully := false
decoder := encoder.NewDecoder()
//"windows-1252" is the default value and will likely not decode correctly
if certain || encoding != "windows-1252" {
decoded, decErr := decoder.Bytes(buf.Bytes())
if decErr == nil {
bodyText = string(decoded)
decodedSuccessfully = true
}
}
if !decodedSuccessfully {
bodyText = buf.String()
}
// Application-specific logic for retrying HTTP as HTTPS; if condition matches, return protocol error
bodyTextLen := int64(len(bodyText))
if scan.scanner.config.FailHTTPToHTTPS && scan.results.Response.StatusCode == 400 && bodyTextLen < 1024 && bodyTextLen > 24 {
// Apache: "You're speaking plain HTTP to an SSL-enabled server port"
// NGINX: "The plain HTTP request was sent to HTTPS port"
var sliceLen int64 = 128
if readLen < sliceLen {
sliceLen = readLen
}
if bodyTextLen < sliceLen {
sliceLen = bodyTextLen
}
sliceBuf := bodyText[:sliceLen]
if strings.Contains(sliceBuf, "The plain HTTP request was sent to HTTPS port") ||
strings.Contains(sliceBuf, "You're speaking plain HTTP") ||
strings.Contains(sliceBuf, "combination of host and port requires TLS") ||
strings.Contains(sliceBuf, "Client sent an HTTP request to an HTTPS server") {
return zgrab2.NewScanError(zgrab2.SCAN_PROTOCOL_ERROR, errors.New("NGINX or Apache HTTP over HTTPS failure"))
}
}
// re-enforce readlen
if int64(len(bodyText)) > readLen {
scan.results.Response.BodyText = bodyText[:int(readLen)]
} else {
scan.results.Response.BodyText = bodyText
}
if scan.scanner.config.WithBodyLength {
scan.results.Response.BodyTextLength = int64(len(scan.results.Response.BodyText))
}
if len(scan.results.Response.BodyText) > 0 {
if scan.scanner.decodedHashFn != nil {
scan.results.Response.BodyHash = scan.scanner.decodedHashFn([]byte(scan.results.Response.BodyText))
} else {
m := sha256.New()
m.Write(buf.Bytes())
scan.results.Response.BodySHA256 = m.Sum(nil)
}
}
// Check if the BodyText is binary, we'll need to base64 encode it
// This occurs after length enforcement, since readLen is the size of data read on the wire, not encoded
if !utf8.ValidString(scan.results.Response.BodyText) {
// body isn't valid UTF-8, so we need to base64 encode it
// without this, binary data gets set as
scan.results.Response.BodyText = base64.StdEncoding.EncodeToString([]byte(scan.results.Response.BodyText))
}
return nil

These seem to be trying to do the same kind of parsing, but the logic of the two diverges in some respects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions