mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-10-11 09:38:47 +02:00
fix work around surf bug
Signed-off-by: RonniSkansing <rskansing@gmail.com>
This commit is contained in:
2 files changed
+57
-6
No files matched your search
+39
-2
@@ -734,10 +734,42 @@ func (m *ProxyHandler) patchRequestBodyWithContext(req *http.Request, reqCtx *Re
|
||||
req.ContentLength = int64(len(body))
|
||||
}
|
||||
|
||||
// setReplayableBody buffers the outbound request body and sets GetBody so the
|
||||
// client transport can resend it. Without GetBody a failed HTTP/2 handshake
|
||||
// cannot fall back to HTTP/1.1 for a request that carries a body. An empty body
|
||||
// is set to http.NoBody so the fallback guard treats it as bodyless.
|
||||
func (m *ProxyHandler) setReplayableBody(req *http.Request) {
|
||||
var body []byte
|
||||
if req.Body != nil {
|
||||
b, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
m.logger.Errorw("failed to read request body for replay", "error", err)
|
||||
return
|
||||
}
|
||||
req.Body.Close()
|
||||
body = b
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
req.Body = http.NoBody
|
||||
req.ContentLength = 0
|
||||
req.GetBody = func() (io.ReadCloser, error) { return http.NoBody, nil }
|
||||
return
|
||||
}
|
||||
|
||||
req.Body = io.NopCloser(bytes.NewReader(body))
|
||||
req.ContentLength = int64(len(body))
|
||||
req.GetBody = func() (io.ReadCloser, error) {
|
||||
return io.NopCloser(bytes.NewReader(body)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ProxyHandler) prepareRequestForTarget(req *http.Request, client *http.Client, usedImpersonation bool) {
|
||||
req.RequestURI = ""
|
||||
// we always use surf now, which handles decompression automatically
|
||||
// keep accept-encoding headers for browser fingerprinting
|
||||
// keep accept-encoding headers for browser fingerprinting. surf's own
|
||||
// response decompression is disabled (see createSurfClient) because it
|
||||
// decodes eagerly and errors on empty-body encoded responses like 302s;
|
||||
// readAndDecompressBody handles decompression instead.
|
||||
// note: usedImpersonation tracks if impersonation features are enabled, not if surf is used
|
||||
req.Header.Del(HEADER_JA4)
|
||||
|
||||
@@ -748,6 +780,11 @@ func (m *ProxyHandler) prepareRequestForTarget(req *http.Request, client *http.C
|
||||
// header sends the length twice and produces a malformed request.
|
||||
req.Header.Del("Content-Length")
|
||||
|
||||
// finalize the outbound body so the transport can replay it. GetBody lets
|
||||
// surf fall back from HTTP/2 to HTTP/1.1 when h2 negotiation fails. an empty
|
||||
// body becomes http.NoBody so the fallback path is not blocked at all.
|
||||
m.setReplayableBody(req)
|
||||
|
||||
// setup cookie jar for redirect handling
|
||||
jar, _ := cookiejar.New(nil)
|
||||
client.Jar = jar
|
||||
|
||||
@@ -96,6 +96,15 @@ func (m *ProxyHandler) createSurfClient(userAgent string, proxyConfig *service.P
|
||||
m.logger.Debugw("applying default windows platform impersonation", "userAgent", userAgent)
|
||||
}
|
||||
|
||||
// KNOWN BUG (surf impersonation, upstream utls): the impersonation
|
||||
// profiles are unstable in the current surf/utls versions.
|
||||
// - Firefox: the handshake fails with "tls: invalid server key share"
|
||||
// against many targets, so the Firefox profile is effectively broken.
|
||||
// - Chrome: works most of the time but the per connection ClientHello
|
||||
// extension shuffle occasionally produces a hello the target resets,
|
||||
// so a request can intermittently fail to connect.
|
||||
// Impersonation is off by default. The non impersonated client is
|
||||
// reliable and still uses HTTP/2. Revisit when surf/utls is updated.
|
||||
// apply browser impersonation based on detected profile
|
||||
switch {
|
||||
case profile.isChrome || profile.isEdge:
|
||||
@@ -103,7 +112,8 @@ func (m *ProxyHandler) createSurfClient(userAgent string, proxyConfig *service.P
|
||||
builder = impersonate.Chrome()
|
||||
m.logger.Debugw("applying chrome browser impersonation")
|
||||
case profile.isFirefox:
|
||||
// firefox impersonation
|
||||
// firefox impersonation. see KNOWN BUG above: the firefox profile
|
||||
// currently fails the tls handshake with invalid server key share
|
||||
builder = impersonate.Firefox()
|
||||
m.logger.Debugw("applying firefox browser impersonation")
|
||||
case profile.isSafari:
|
||||
@@ -127,9 +137,13 @@ func (m *ProxyHandler) createSurfClient(userAgent string, proxyConfig *service.P
|
||||
// configure timeout
|
||||
builder = builder.Timeout(30 * time.Second)
|
||||
|
||||
// note: surf automatically decompresses response bodies via decodeBodyMW middleware
|
||||
// even when using .Std(), but keeps the Content-Encoding header
|
||||
// our proxy code will detect this and remove the header before sending to client
|
||||
// disable surf's response decompression. surf decodes eagerly: it builds the
|
||||
// gzip reader as soon as the headers arrive, so a response that advertises
|
||||
// Content-Encoding with an empty body (a 302 redirect from the login flow, a
|
||||
// 304, a 204) makes the gzip reader read from an empty stream and return EOF,
|
||||
// which surf surfaces as the whole request failing. readAndDecompressBody
|
||||
// decompresses from the fully buffered body instead and handles empty bodies.
|
||||
builder = builder.DisableCompression()
|
||||
|
||||
// preserve client's accept-language header if provided
|
||||
if acceptLanguage != "" {
|
||||
|
||||
Reference in new issue
Block a user