From ee9066dedb4aa13d9755c583dfc48de32e496b67 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Sun, 19 Nov 2023 15:03:11 -0600 Subject: [PATCH] refactor wip --- go.mod | 2 +- handlers/proxy.go | 25 ++-- proxychain/cache/memcache.go | 0 proxychain/proxychain.go | 120 ++++++------------ proxychain/proxychain_cache.go | 19 +++ .../masquerade_as_trusted_bot.go | 2 +- .../modify_domain_with_regex.go | 2 +- .../modify_outgoing_cookies.go} | 2 +- .../modify_path_with_regex.go | 2 +- .../modify_query_params.go | 2 +- .../requestmodifers/modify_request_headers.go | 23 ++++ .../request_archive_is.go | 2 +- .../request_google_cache.go | 2 +- .../request_wayback_machine.go | 2 +- .../resolve_with_google_doh.go | 2 +- .../{rqm => requestmodifers}/spoof_origin.go | 2 +- .../spoof_referrer.go | 2 +- .../spoof_user_agent.go | 2 +- .../spoof_x_forwarded_for.go | 2 +- proxychain/responsemodifers/bypass_cors.go | 21 +++ proxychain/responsemodifers/bypass_csp.go | 27 ++++ .../modify_incoming_cookies.go | 102 +++++++++++++++ .../modify_response_header.go | 11 +- .../rewrite_http_resource_urls.go | 114 +++++++++++++++++ proxychain/rsm/block_incoming_cookies.go | 58 --------- proxychain/rsm/bypass_cors.go | 20 --- proxychain/rsm/bypass_csp.go | 19 --- 27 files changed, 377 insertions(+), 210 deletions(-) create mode 100644 proxychain/cache/memcache.go create mode 100644 proxychain/proxychain_cache.go rename proxychain/{rqm => requestmodifers}/masquerade_as_trusted_bot.go (97%) rename proxychain/{rqm => requestmodifers}/modify_domain_with_regex.go (90%) rename proxychain/{rqm/block_outgoing_cookies.go => requestmodifers/modify_outgoing_cookies.go} (97%) rename proxychain/{rqm => requestmodifers}/modify_path_with_regex.go (90%) rename proxychain/{rqm => requestmodifers}/modify_query_params.go (93%) create mode 100644 proxychain/requestmodifers/modify_request_headers.go rename proxychain/{rqm => requestmodifers}/request_archive_is.go (96%) rename proxychain/{rqm => requestmodifers}/request_google_cache.go (94%) rename proxychain/{rqm => requestmodifers}/request_wayback_machine.go (94%) rename proxychain/{rqm => requestmodifers}/resolve_with_google_doh.go (98%) rename proxychain/{rqm => requestmodifers}/spoof_origin.go (95%) rename proxychain/{rqm => requestmodifers}/spoof_referrer.go (96%) rename proxychain/{rqm => requestmodifers}/spoof_user_agent.go (88%) rename proxychain/{rqm => requestmodifers}/spoof_x_forwarded_for.go (91%) create mode 100644 proxychain/responsemodifers/bypass_cors.go create mode 100644 proxychain/responsemodifers/bypass_csp.go create mode 100644 proxychain/responsemodifers/modify_incoming_cookies.go rename proxychain/{rsm => responsemodifers}/modify_response_header.go (55%) create mode 100644 proxychain/responsemodifers/rewrite_http_resource_urls.go delete mode 100644 proxychain/rsm/block_incoming_cookies.go delete mode 100644 proxychain/rsm/bypass_cors.go delete mode 100644 proxychain/rsm/bypass_csp.go diff --git a/go.mod b/go.mod index 5d6ca5d..9bab4b3 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/net v0.18.0 // indirect + golang.org/x/net v0.18.0 golang.org/x/sys v0.14.0 // indirect golang.org/x/term v0.14.0 ) diff --git a/handlers/proxy.go b/handlers/proxy.go index 221108a..fc3beea 100644 --- a/handlers/proxy.go +++ b/handlers/proxy.go @@ -12,8 +12,8 @@ import ( "ladder/pkg/ruleset" "ladder/proxychain" - "ladder/proxychain/rqm" - "ladder/proxychain/rsm" + rx "ladder/proxychain/requestmodifers" + tx "ladder/proxychain/responsemodifers" "github.com/PuerkitoBio/goquery" "github.com/gofiber/fiber/v2" @@ -49,19 +49,20 @@ func NewProxySiteHandler(opts *ProxyOptions) fiber.Handler { rs = r } */ + proxychain := proxychain. + NewProxyChain(). + SetDebugLogging(opts.Verbose). + SetRequestModifications( + rx.DeleteOutgoingCookies(), + ). + AddResponseModifications( + tx.DeleteIncomingCookies(), + ) return func(c *fiber.Ctx) error { - return proxychain.NewProxyChain(). - SetCtx(c). - //AddRuleset(&rs). - SetRequestModifications( - rqm.BlockOutgoingCookies(), - ). - SetResultModifications( - rsm.BlockIncomingCookies(), - ). - Execute() + return proxychain.SetFiberCtx(c).Execute() } + } func modifyURL(uri string, rule ruleset.Rule) (string, error) { diff --git a/proxychain/cache/memcache.go b/proxychain/cache/memcache.go new file mode 100644 index 0000000..e69de29 diff --git a/proxychain/proxychain.go b/proxychain/proxychain.go index 5a4594d..b4c3613 100644 --- a/proxychain/proxychain.go +++ b/proxychain/proxychain.go @@ -14,14 +14,6 @@ import ( "github.com/gofiber/fiber/v2" ) -var defaultClient *http.Client - -func DefaultClient() { - defaultClient = &http.Client{ - Timeout: 15, - } -} - /* ProxyChain manages the process of forwarding an HTTP request to an upstream server, applying request and response modifications along the way. @@ -40,21 +32,22 @@ applying request and response modifications along the way. import ( - "ladder/internal/proxychain/rqm" - "ladder/internal/proxychain/rsm" + rx "ladder/pkg/proxychain/requestmodifers" + tx "ladder/pkg/proxychain/responsemodifers" "ladder/internal/proxychain" ) proxychain.NewProxyChain(). - SetCtx(c). - AddRuleset(&rs). + SetFiberCtx(c). SetRequestModifications( - rqm.BlockOutgoingCookies(), + rx.BlockOutgoingCookies(), + rx.SpoofOrigin(), + rx.SpoofReferrer(), ). SetResultModifications( - rsm.BlockIncomingCookies(), + tx.BlockIncomingCookies(), ). Execute() @@ -90,12 +83,12 @@ type ProxyChain struct { Client *http.Client Request *http.Request Response *http.Response - Body []byte + Body io.Reader requestModifications []RequestModification resultModifications []ResponseModification - ruleset *ruleset.RuleSet - verbose bool - _abort_err error + Ruleset *ruleset.RuleSet + debugMode bool + abortErr error } // a ProxyStrategy is a pre-built proxychain with purpose-built defaults @@ -123,23 +116,16 @@ func (chain *ProxyChain) AddRequestModifications(mods ...RequestModification) *P return chain } -// SetResultModifications sets the ProxyChain's response modifers +// AddResponseModifications sets the ProxyChain's response modifers // the modifier will not fire until ProxyChain.Execute() is run. -func (chain *ProxyChain) SetResultModifications(mods ...ResponseModification) *ProxyChain { +func (chain *ProxyChain) AddResponseModifications(mods ...ResponseModification) *ProxyChain { chain.resultModifications = mods return chain } -// AddResultModifications adds to the ProxyChain's response modifers -// the modifier will not fire until ProxyChain.Execute() is run. -func (chain *ProxyChain) AddResultModifications(mods ...ResponseModification) *ProxyChain { - chain.resultModifications = append(chain.resultModifications, mods...) - return chain -} - // Adds a ruleset to ProxyChain func (chain *ProxyChain) AddRuleset(rs *ruleset.RuleSet) *ProxyChain { - chain.ruleset = rs + chain.Ruleset = rs // TODO: add _applyRuleset method return chain } @@ -178,20 +164,16 @@ func (chain *ProxyChain) _initialize_request() (*http.Request, error) { // _execute sends the request for the ProxyChain and returns the raw body only // the caller is responsible for returning a response back to the requestor -// the caller is also responsible for calling pxc._reset() when they are done with the body -func (chain *ProxyChain) _execute() (*[]byte, error) { - chain._validate_ctx_is_set() - if chain._abort_err != nil { - return nil, chain._abort_err - } - if chain.Context == nil { - return nil, errors.New("request ctx not set. Use ProxyChain.SetCtx()") +// the caller is also responsible for calling chain._reset() when they are done with the body +func (chain *ProxyChain) _execute() (io.Reader, error) { + if chain.validateCtxIsSet() != nil { + return nil, chain.abortErr } if chain.Request.URL.Scheme == "" { return nil, errors.New("request url not set or invalid. Check ProxyChain ReqMods for issues") } - // Apply requestModifications to proxychain (pxc) + // Apply requestModifications to proxychain for _, applyRequestModificationsTo := range chain.requestModifications { err := applyRequestModificationsTo(chain) if err != nil { @@ -205,13 +187,8 @@ func (chain *ProxyChain) _execute() (*[]byte, error) { return nil, chain.abort(err) } chain.Response = resp + chain.Body = chain.Response.Body - // Buffer response into memory - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, chain.abort(err) - } - chain.Body = body defer resp.Body.Close() /* todo: move to rsm @@ -220,7 +197,7 @@ func (chain *ProxyChain) _execute() (*[]byte, error) { } */ - // Apply ResponseModifiers to proxychain (pxc) + // Apply ResponseModifiers to proxychain for _, applyResultModificationsTo := range chain.resultModifications { err := applyResultModificationsTo(chain) if err != nil { @@ -228,7 +205,7 @@ func (chain *ProxyChain) _execute() (*[]byte, error) { } } - return &chain.Body, nil + return chain.Body, nil } // Execute sends the request for the ProxyChain and returns the request to the sender @@ -242,22 +219,7 @@ func (chain *ProxyChain) Execute() error { return err } // Return request back to client - return chain.Context.Send(*body) -} - -// ExecuteAPIContent sends the request for the ProxyChain and returns the response body as -// a structured API response to the client -// if any step in the ProxyChain fails, the request will abort and a 500 error will -// be returned to the client -func (chain *ProxyChain) ExecuteAPIContent() error { - defer chain._reset() - body, err := chain._execute() - if err != nil { - return err - } - // TODO: implement reader API - // Return request back to client - return chain.Context.Send(*body) + return chain.Context.SendStream(body) } // reconstructUrlFromReferer reconstructs the URL using the referer's scheme, host, and the relative path / queries @@ -312,50 +274,51 @@ func (chain *ProxyChain) extractUrl() (*url.URL, error) { return reconstructUrlFromReferer(referer, relativePath) } -// SetCtx takes the request ctx from the client +// SetFiberCtx takes the request ctx from the client // for the modifiers and execute function to use. // it must be set everytime a new request comes through // if the upstream request url cannot be extracted from the ctx, // a 500 error will be sent back to the client -func (chain *ProxyChain) SetCtx(ctx *fiber.Ctx) *ProxyChain { +func (chain *ProxyChain) SetFiberCtx(ctx *fiber.Ctx) *ProxyChain { chain.Context = ctx // initialize the request and prepare it for modification req, err := chain._initialize_request() if err != nil { - chain._abort_err = chain.abort(err) + chain.abortErr = chain.abort(err) } chain.Request = req // extract the URL for the request and add it to the new request url, err := chain.extractUrl() if err != nil { - chain._abort_err = chain.abort(err) + chain.abortErr = chain.abort(err) } chain.Request.URL = url return chain } -func (pxc *ProxyChain) _validate_ctx_is_set() { - if pxc.Context != nil { - return +func (chain *ProxyChain) validateCtxIsSet() error { + if chain.Context != nil { + return nil } err := errors.New("proxyChain was called without setting a fiber Ctx. Use ProxyChain.SetCtx()") - pxc._abort_err = pxc.abort(err) + chain.abortErr = chain.abort(err) + return chain.abortErr } -// SetClient sets a new upstream http client transport +// SetHttpClient sets a new upstream http client transport // useful for modifying TLS -func (pxc *ProxyChain) SetClient(httpClient *http.Client) *ProxyChain { - pxc.Client = httpClient - return pxc +func (chain *ProxyChain) SetHttpClient(httpClient *http.Client) *ProxyChain { + chain.Client = httpClient + return chain } // SetVerbose changes the logging behavior to print // the modification steps and applied rulesets for debugging -func (chain *ProxyChain) SetVerbose() *ProxyChain { - chain.verbose = true +func (chain *ProxyChain) SetDebugLogging(isDebugMode bool) *ProxyChain { + chain.debugMode = isDebugMode return chain } @@ -363,8 +326,8 @@ func (chain *ProxyChain) SetVerbose() *ProxyChain { // this will prevent Execute from firing and reset the state // returns the initial error enriched with context func (chain *ProxyChain) abort(err error) error { - defer chain._reset() - chain._abort_err = err + //defer chain._reset() + chain.abortErr = err chain.Context.Response().SetStatusCode(500) e := fmt.Errorf("ProxyChain error for '%s': %s", chain.Request.URL.String(), err.Error()) chain.Context.SendString(e.Error()) @@ -374,7 +337,7 @@ func (chain *ProxyChain) abort(err error) error { // internal function to reset state of ProxyChain for reuse func (chain *ProxyChain) _reset() { - chain._abort_err = nil + chain.abortErr = nil chain.Body = nil chain.Request = nil chain.Response = nil @@ -384,7 +347,6 @@ func (chain *ProxyChain) _reset() { // NewProxyChain initializes a new ProxyChain func NewProxyChain() *ProxyChain { chain := new(ProxyChain) - //px.Client = defaultClient chain.Client = http.DefaultClient return chain } diff --git a/proxychain/proxychain_cache.go b/proxychain/proxychain_cache.go new file mode 100644 index 0000000..44289af --- /dev/null +++ b/proxychain/proxychain_cache.go @@ -0,0 +1,19 @@ +package proxychain + +import "time" + +// Cache provides an interface for caching mechanisms. +// It supports operations to get, set, and invalidate cache entries. +// Implementations should ensure thread safety, efficiency +type Cache interface { + // Get Retrieves a cached value by its key. Returns the value and a boolean indicating + Get(key string) (value interface{}, found bool) + + // Set - Stores a value associated with a key in the cache for a specified time-to-live (ttl). + // If ttl is zero, the cache item has no expiration. + Set(key string, value interface{}, ttl time.Duration) + + // Invalidate - Removes a value from the cache by its key. If the key does not exist, + // it should perform a no-op or return a suitable error. + Invalidate(key string) error +} diff --git a/proxychain/rqm/masquerade_as_trusted_bot.go b/proxychain/requestmodifers/masquerade_as_trusted_bot.go similarity index 97% rename from proxychain/rqm/masquerade_as_trusted_bot.go rename to proxychain/requestmodifers/masquerade_as_trusted_bot.go index 550329a..fcbdc62 100644 --- a/proxychain/rqm/masquerade_as_trusted_bot.go +++ b/proxychain/requestmodifers/masquerade_as_trusted_bot.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/modify_domain_with_regex.go b/proxychain/requestmodifers/modify_domain_with_regex.go similarity index 90% rename from proxychain/rqm/modify_domain_with_regex.go rename to proxychain/requestmodifers/modify_domain_with_regex.go index d3b93dc..3352d54 100644 --- a/proxychain/rqm/modify_domain_with_regex.go +++ b/proxychain/requestmodifers/modify_domain_with_regex.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/block_outgoing_cookies.go b/proxychain/requestmodifers/modify_outgoing_cookies.go similarity index 97% rename from proxychain/rqm/block_outgoing_cookies.go rename to proxychain/requestmodifers/modify_outgoing_cookies.go index b59a9ff..43bb510 100644 --- a/proxychain/rqm/block_outgoing_cookies.go +++ b/proxychain/requestmodifers/modify_outgoing_cookies.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/modify_path_with_regex.go b/proxychain/requestmodifers/modify_path_with_regex.go similarity index 90% rename from proxychain/rqm/modify_path_with_regex.go rename to proxychain/requestmodifers/modify_path_with_regex.go index 8b96c6f..97163ee 100644 --- a/proxychain/rqm/modify_path_with_regex.go +++ b/proxychain/requestmodifers/modify_path_with_regex.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/modify_query_params.go b/proxychain/requestmodifers/modify_query_params.go similarity index 93% rename from proxychain/rqm/modify_query_params.go rename to proxychain/requestmodifers/modify_query_params.go index 1018a00..06d8122 100644 --- a/proxychain/rqm/modify_query_params.go +++ b/proxychain/requestmodifers/modify_query_params.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/requestmodifers/modify_request_headers.go b/proxychain/requestmodifers/modify_request_headers.go new file mode 100644 index 0000000..601c47e --- /dev/null +++ b/proxychain/requestmodifers/modify_request_headers.go @@ -0,0 +1,23 @@ +package requestmodifers + +import ( + "ladder/proxychain" +) + +// SetRequestHeader modifies a specific outgoing header +// This is the header that the upstream server will see. +func SetRequestHeader(name string, val string) proxychain.RequestModification { + return func(px *proxychain.ProxyChain) error { + px.Request.Header.Set(name, val) + return nil + } +} + +// DeleteRequestHeader modifies a specific outgoing header +// This is the header that the upstream server will see. +func DeleteRequestHeader(name string) proxychain.RequestModification { + return func(px *proxychain.ProxyChain) error { + px.Request.Header.Del(name) + return nil + } +} diff --git a/proxychain/rqm/request_archive_is.go b/proxychain/requestmodifers/request_archive_is.go similarity index 96% rename from proxychain/rqm/request_archive_is.go rename to proxychain/requestmodifers/request_archive_is.go index 525b092..674a48a 100644 --- a/proxychain/rqm/request_archive_is.go +++ b/proxychain/requestmodifers/request_archive_is.go @@ -1,4 +1,4 @@ -package rqm +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/request_google_cache.go b/proxychain/requestmodifers/request_google_cache.go similarity index 94% rename from proxychain/rqm/request_google_cache.go rename to proxychain/requestmodifers/request_google_cache.go index c9d8d9b..1e967a0 100644 --- a/proxychain/rqm/request_google_cache.go +++ b/proxychain/requestmodifers/request_google_cache.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/request_wayback_machine.go b/proxychain/requestmodifers/request_wayback_machine.go similarity index 94% rename from proxychain/rqm/request_wayback_machine.go rename to proxychain/requestmodifers/request_wayback_machine.go index a01926b..36363dc 100644 --- a/proxychain/rqm/request_wayback_machine.go +++ b/proxychain/requestmodifers/request_wayback_machine.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/resolve_with_google_doh.go b/proxychain/requestmodifers/resolve_with_google_doh.go similarity index 98% rename from proxychain/rqm/resolve_with_google_doh.go rename to proxychain/requestmodifers/resolve_with_google_doh.go index 2e2fb05..f236d54 100644 --- a/proxychain/rqm/resolve_with_google_doh.go +++ b/proxychain/requestmodifers/resolve_with_google_doh.go @@ -1,4 +1,4 @@ -package rqm +package requestmodifers import ( "context" diff --git a/proxychain/rqm/spoof_origin.go b/proxychain/requestmodifers/spoof_origin.go similarity index 95% rename from proxychain/rqm/spoof_origin.go rename to proxychain/requestmodifers/spoof_origin.go index 07bbd8a..1dcc109 100644 --- a/proxychain/rqm/spoof_origin.go +++ b/proxychain/requestmodifers/spoof_origin.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/spoof_referrer.go b/proxychain/requestmodifers/spoof_referrer.go similarity index 96% rename from proxychain/rqm/spoof_referrer.go rename to proxychain/requestmodifers/spoof_referrer.go index 1f0ae46..b2e5301 100644 --- a/proxychain/rqm/spoof_referrer.go +++ b/proxychain/requestmodifers/spoof_referrer.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/spoof_user_agent.go b/proxychain/requestmodifers/spoof_user_agent.go similarity index 88% rename from proxychain/rqm/spoof_user_agent.go rename to proxychain/requestmodifers/spoof_user_agent.go index 4950282..4c2083c 100644 --- a/proxychain/rqm/spoof_user_agent.go +++ b/proxychain/requestmodifers/spoof_user_agent.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/rqm/spoof_x_forwarded_for.go b/proxychain/requestmodifers/spoof_x_forwarded_for.go similarity index 91% rename from proxychain/rqm/spoof_x_forwarded_for.go rename to proxychain/requestmodifers/spoof_x_forwarded_for.go index 93a4e91..45a9dff 100644 --- a/proxychain/rqm/spoof_x_forwarded_for.go +++ b/proxychain/requestmodifers/spoof_x_forwarded_for.go @@ -1,4 +1,4 @@ -package rqm // ReQuestModifier +package requestmodifers import ( "ladder/proxychain" diff --git a/proxychain/responsemodifers/bypass_cors.go b/proxychain/responsemodifers/bypass_cors.go new file mode 100644 index 0000000..23dad06 --- /dev/null +++ b/proxychain/responsemodifers/bypass_cors.go @@ -0,0 +1,21 @@ +package responsemodifers + +import ( + "ladder/proxychain" +) + +// BypassCORS modifies response headers to prevent the browser +// from enforcing any CORS restrictions. This should run at the end of the chain. +func BypassCORS() proxychain.ResponseModification { + return func(chain *proxychain.ProxyChain) error { + chain.AddResponseModifications( + SetResponseHeader("Access-Control-Allow-Origin", "*"), + SetResponseHeader("Access-Control-Expose-Headers", "*"), + SetResponseHeader("Access-Control-Allow-Credentials", "true"), + SetResponseHeader("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, HEAD, OPTIONS, PATCH"), + SetResponseHeader("Access-Control-Allow-Headers", "*"), + DeleteResponseHeader("X-Frame-Options"), + ) + return nil + } +} diff --git a/proxychain/responsemodifers/bypass_csp.go b/proxychain/responsemodifers/bypass_csp.go new file mode 100644 index 0000000..71d4d73 --- /dev/null +++ b/proxychain/responsemodifers/bypass_csp.go @@ -0,0 +1,27 @@ +package responsemodifers + +import ( + "ladder/proxychain" +) + +// BypassContentSecurityPolicy modifies response headers to prevent the browser +// from enforcing any CSP restrictions. This should run at the end of the chain. +func BypassContentSecurityPolicy() proxychain.ResponseModification { + return func(chain *proxychain.ProxyChain) error { + chain.AddResponseModifications( + DeleteResponseHeader("Content-Security-Policy"), + DeleteResponseHeader("Content-Security-Policy-Report-Only"), + DeleteResponseHeader("X-Content-Security-Policy"), + DeleteResponseHeader("X-WebKit-CSP"), + ) + return nil + } +} + +// SetContentSecurityPolicy modifies response headers to a specific CSP +func SetContentSecurityPolicy(csp string) proxychain.ResponseModification { + return func(chain *proxychain.ProxyChain) error { + chain.Response.Header.Set("Content-Security-Policy", csp) + return nil + } +} diff --git a/proxychain/responsemodifers/modify_incoming_cookies.go b/proxychain/responsemodifers/modify_incoming_cookies.go new file mode 100644 index 0000000..df6aa53 --- /dev/null +++ b/proxychain/responsemodifers/modify_incoming_cookies.go @@ -0,0 +1,102 @@ +package responsemodifers + +import ( + "fmt" + "ladder/proxychain" + "net/http" +) + +// DeleteIncomingCookies prevents ALL cookies from being sent from the proxy server +// back down to the client. +func DeleteIncomingCookies(whitelist ...string) proxychain.ResponseModification { + return func(px *proxychain.ProxyChain) error { + px.Response.Header.Del("Set-Cookie") + return nil + } +} + +// DeleteIncomingCookiesExcept prevents non-whitelisted cookies from being sent from the proxy server +// to the client. Cookies whose names are in the whitelist are not removed. +func DeleteIncomingCookiesExcept(whitelist ...string) proxychain.ResponseModification { + return func(px *proxychain.ProxyChain) error { + // Convert whitelist slice to a map for efficient lookups + whitelistMap := make(map[string]struct{}) + for _, cookieName := range whitelist { + whitelistMap[cookieName] = struct{}{} + } + + // If the response has no cookies, return early + if px.Response.Header == nil { + return nil + } + + // Filter the cookies in the response + filteredCookies := []string{} + for _, cookieStr := range px.Response.Header["Set-Cookie"] { + cookie := parseCookie(cookieStr) + if _, found := whitelistMap[cookie.Name]; found { + filteredCookies = append(filteredCookies, cookieStr) + } + } + + // Update the Set-Cookie header with the filtered cookies + if len(filteredCookies) > 0 { + px.Response.Header["Set-Cookie"] = filteredCookies + } else { + px.Response.Header.Del("Set-Cookie") + } + + return nil + } +} + +// parseCookie parses a cookie string and returns an http.Cookie object. +func parseCookie(cookieStr string) *http.Cookie { + header := http.Header{} + header.Add("Set-Cookie", cookieStr) + request := http.Request{Header: header} + return request.Cookies()[0] +} + +// SetIncomingCookies adds a raw cookie string being sent from the proxy server down to the client +func SetIncomingCookies(cookies string) proxychain.ResponseModification { + return func(px *proxychain.ProxyChain) error { + px.Response.Header.Set("Set-Cookie", cookies) + return nil + } +} + +// SetIncomingCookie modifies a specific cookie in the response from the proxy server to the client. +func SetIncomingCookie(name string, val string) proxychain.ResponseModification { + return func(px *proxychain.ProxyChain) error { + if px.Response.Header == nil { + return nil + } + + updatedCookies := []string{} + found := false + + // Iterate over existing cookies and modify the one that matches the cookieName + for _, cookieStr := range px.Response.Header["Set-Cookie"] { + cookie := parseCookie(cookieStr) + if cookie.Name == name { + // Replace the cookie with the new value + updatedCookies = append(updatedCookies, fmt.Sprintf("%s=%s", name, val)) + found = true + } else { + // Keep the cookie as is + updatedCookies = append(updatedCookies, cookieStr) + } + } + + // If the specified cookie wasn't found, add it + if !found { + updatedCookies = append(updatedCookies, fmt.Sprintf("%s=%s", name, val)) + } + + // Update the Set-Cookie header + px.Response.Header["Set-Cookie"] = updatedCookies + + return nil + } +} diff --git a/proxychain/rsm/modify_response_header.go b/proxychain/responsemodifers/modify_response_header.go similarity index 55% rename from proxychain/rsm/modify_response_header.go rename to proxychain/responsemodifers/modify_response_header.go index d7a473e..5266d6d 100644 --- a/proxychain/rsm/modify_response_header.go +++ b/proxychain/responsemodifers/modify_response_header.go @@ -1,17 +1,12 @@ -package rsm // ReSponseModifers +package responsemodifers import ( "ladder/proxychain" ) -// ModifyResponseHeader modifies response headers from the upstream server -// if value is "", then the response header is deleted. -func ModifyResponseHeader(key string, value string) proxychain.ResponseModification { +// SetResponseHeader modifies response headers from the upstream server +func SetResponseHeader(key string, value string) proxychain.ResponseModification { return func(px *proxychain.ProxyChain) error { - if value == "" { - px.Context.Response().Header.Del(key) - return nil - } px.Context.Response().Header.Set(key, value) return nil } diff --git a/proxychain/responsemodifers/rewrite_http_resource_urls.go b/proxychain/responsemodifers/rewrite_http_resource_urls.go new file mode 100644 index 0000000..29caff8 --- /dev/null +++ b/proxychain/responsemodifers/rewrite_http_resource_urls.go @@ -0,0 +1,114 @@ +package responsemodifers + +import ( + "bytes" + "io" + "ladder/proxychain" + "net/url" + "strings" + + "golang.org/x/net/html" +) + +type HTMLResourceURLRewriter struct { + src io.Reader + buffer *bytes.Buffer // buffer to temporarily hold rewritten output for the reader + proxyURL *url.URL // proxyURL is the URL of the proxy, not the upstream URL +} + +func NewHTMLResourceURLRewriter(src io.Reader, proxyURL *url.URL) *HTMLResourceURLRewriter { + return &HTMLResourceURLRewriter{ + src: src, + buffer: new(bytes.Buffer), + proxyURL: proxyURL, + } +} + +func rewriteToken(token *html.Token, baseURL *url.URL) { + attrsToRewrite := map[string]bool{"href": true, "src": true, "action": true, "srcset": true} + for i := range token.Attr { + attr := &token.Attr[i] + if attrsToRewrite[attr.Key] && strings.HasPrefix(attr.Val, "/") { + // Make URL absolute + attr.Val = "/https://" + baseURL.Host + attr.Val + } + } +} + +func (r *HTMLResourceURLRewriter) Read(p []byte) (int, error) { + if r.buffer.Len() != 0 { + return r.buffer.Read(p) + } + + tokenizer := html.NewTokenizer(r.src) + for { + tokenType := tokenizer.Next() + if tokenType == html.ErrorToken { + err := tokenizer.Err() + if err == io.EOF { + return 0, io.EOF // End of document + } + return 0, err // Actual error + } + token := tokenizer.Token() + if tokenType == html.StartTagToken || tokenType == html.SelfClosingTagToken { + rewriteToken(&token, r.url) + } + r.buffer.WriteString(token.String()) + if r.buffer.Len() > 0 { + break + } + } + } +} + + +// RewriteHTMLResourceURLs updates src/href attributes in HTML content to route through the proxy. +func RewriteHTMLResourceURLs() proxychain.ResponseModification { + return func(chain *proxychain.ProxyChain) error { + ct := chain.Response.Header.Get("content-type") + if ct != "text/html" { + return nil + } + + // parse dom + tokenizer := html.NewTokenizer(chain.Body) + var buffer bytes.Buffer + + // traverse dom and proxify existing src/img resource links + for { + tokenType := tokenizer.Next() + switch tokenType { + case html.ErrorToken: + // End of the document, set the new body + chain.Body = io.ReaderFrom(buffer) + return nil + case html.StartTagToken, html.SelfClosingTagToken: + token := tokenizer.Token() + // Rewrite the necessary attributes + token = rewriteToken(token, u) + buffer.WriteString(token.String()) + case html.TextToken, html.CommentToken, html.DoctypeToken, html.EndTagToken: + // Write the token to the buffer as is + buffer.WriteString(tokenizer.Token().String()) + } + } + } +} + +// rewriteToken rewrites the tokens with URLs to point to the proxy server. +func rewriteToken(token html.Token, u *url.URL) html.Token { + // Define attributes to rewrite, add more as needed such as "srcset" + rewriteAttrs := map[string]bool{"href": true, "src": true, "action": true, "srcset": true} + + for i, attr := range token.Attr { + _, shouldRewrite := rewriteAttrs[attr.Key] + if shouldRewrite { + val := attr.Val + if strings.HasPrefix(val, "/") { + token.Attr[i].Val = "/https://" + u.Host + val + } + } + } + return token +} diff --git a/proxychain/rsm/block_incoming_cookies.go b/proxychain/rsm/block_incoming_cookies.go deleted file mode 100644 index 0884a6f..0000000 --- a/proxychain/rsm/block_incoming_cookies.go +++ /dev/null @@ -1,58 +0,0 @@ -package rsm // ReSponseModifers - -import ( - "ladder/proxychain" - "net/http" -) - -// BlockIncomingCookies prevents ALL cookies from being sent from the proxy server -// to the client. -func BlockIncomingCookies(whitelist ...string) proxychain.ResponseModification { - return func(px *proxychain.ProxyChain) error { - px.Response.Header.Del("Set-Cookie") - return nil - } -} - -// BlockIncomingCookiesExcept prevents non-whitelisted cookies from being sent from the proxy server -// to the client. Cookies whose names are in the whitelist are not removed. -func BlockIncomingCookiesExcept(whitelist ...string) proxychain.ResponseModification { - return func(px *proxychain.ProxyChain) error { - // Convert whitelist slice to a map for efficient lookups - whitelistMap := make(map[string]struct{}) - for _, cookieName := range whitelist { - whitelistMap[cookieName] = struct{}{} - } - - // If the response has no cookies, return early - if px.Response.Header == nil { - return nil - } - - // Filter the cookies in the response - filteredCookies := []string{} - for _, cookieStr := range px.Response.Header["Set-Cookie"] { - cookie := parseCookie(cookieStr) - if _, found := whitelistMap[cookie.Name]; found { - filteredCookies = append(filteredCookies, cookieStr) - } - } - - // Update the Set-Cookie header with the filtered cookies - if len(filteredCookies) > 0 { - px.Response.Header["Set-Cookie"] = filteredCookies - } else { - px.Response.Header.Del("Set-Cookie") - } - - return nil - } -} - -// parseCookie parses a cookie string and returns an http.Cookie object. -func parseCookie(cookieStr string) *http.Cookie { - header := http.Header{} - header.Add("Set-Cookie", cookieStr) - request := http.Request{Header: header} - return request.Cookies()[0] -} diff --git a/proxychain/rsm/bypass_cors.go b/proxychain/rsm/bypass_cors.go deleted file mode 100644 index dbf7013..0000000 --- a/proxychain/rsm/bypass_cors.go +++ /dev/null @@ -1,20 +0,0 @@ -package rsm // ReSponseModifers - -import ( - "ladder/proxychain" -) - -// BypassCORs modifies response headers to prevent the browser -// from enforcing any CORS restrictions -func BypassCORS() proxychain.ResponseModification { - return func(px *proxychain.ProxyChain) error { - px.AddResultModifications( - ModifyResponseHeader("Access-Control-Allow-Origin", "*"), - ModifyResponseHeader("Access-Control-Expose-Headers", "*"), - ModifyResponseHeader("Access-Control-Allow-Credentials", "true"), - ModifyResponseHeader("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE, HEAD, OPTIONS, PATCH"), - DeleteResponseHeader("X-Frame-Options"), - ) - return nil - } -} diff --git a/proxychain/rsm/bypass_csp.go b/proxychain/rsm/bypass_csp.go deleted file mode 100644 index 4262590..0000000 --- a/proxychain/rsm/bypass_csp.go +++ /dev/null @@ -1,19 +0,0 @@ -package rsm // ReSponseModifers - -import ( - "ladder/proxychain" -) - -// BypassCSP modifies response headers to prevent the browser -// from enforcing any CORS restrictions -func BypassCSP() proxychain.ResponseModification { - return func(px *proxychain.ProxyChain) error { - px.AddResultModifications( - ModifyResponseHeader("Access-Control-Allow-Origin", "*"), - ModifyResponseHeader("Access-Control-Expose-Headers", "*"), - ModifyResponseHeader("Access-Control-Allow-Credentials", "true"), - ModifyResponseHeader("Access-Control-Allow-Methods", ""), - ) - return nil - } -}