add dynamic resource url patcher as standalone responsemodifier

This commit is contained in:
Kevin Pham
2023-11-23 08:14:52 -06:00
parent 5d55a2f3f0
commit 6c54d31086
6 changed files with 112 additions and 13 deletions

View File

@@ -88,6 +88,7 @@ func main() {
fiber.Config{
Prefork: *prefork,
GETOnly: false,
ReadBufferSize: 4096 * 4, // increase max header size
},
)

View File

@@ -56,15 +56,16 @@ func NewProxySiteHandler(opts *ProxyOptions) fiber.Handler {
SetFiberCtx(c).
SetDebugLogging(opts.Verbose).
SetRequestModifications(
//rx.DeleteOutgoingCookies(),
rx.DeleteOutgoingCookies(),
//rx.RequestArchiveIs(),
rx.MasqueradeAsGoogleBot(),
).
AddResponseModifications(
//tx.DeleteIncomingCookies(),
tx.RewriteHTMLResourceURLs(),
tx.BypassCORS(),
tx.BypassContentSecurityPolicy(),
tx.DeleteIncomingCookies(),
tx.RewriteHTMLResourceURLs(),
tx.PatchDynamicResourceURLs(),
).
Execute()

View File

@@ -256,8 +256,11 @@ func (chain *ProxyChain) extractUrl() (*url.URL, error) {
return reconstructUrlFromReferer(referer, relativePath)
}
// AddBodyRewriter adds a HTMLTokenRewriter to the chain
// HTMLTokenRewriters modify the body response by parsing the HTML
// AddBodyRewriter adds a HTMLTokenRewriter to the chain.
// - HTMLTokenRewriters modify the body response by parsing the HTML
// and making changes to the DOM as it streams to the client
// - In most cases, you don't need to use this method. It's usually called by
// a ResponseModifier to batch queue changes for performance reasons.
func (chain *ProxyChain) AddHTMLTokenRewriter(rr rr.IHTMLTokenRewriter) *ProxyChain {
chain.htmlTokenRewriters = append(chain.htmlTokenRewriters, rr)
return chain

View File

@@ -0,0 +1,55 @@
package responsemodifers
import (
_ "embed"
"fmt"
"ladder/proxychain"
"ladder/proxychain/responsemodifers/rewriters"
"strings"
)
//go:embed patch_dynamic_resource_urls.js
var patchDynamicResourceURLsScript string
// PatchDynamicResourceURLs patches the javascript runtime to rewrite URLs client-side.
// - This function is designed to allow the proxified page
// to still be browsible by routing all resource URLs through the proxy.
// - Native APIs capable of network requests will be hooked
// and the URLs arguments modified to point to the proxy instead.
// - fetch('/relative_path') -> fetch('/https://proxiedsite.com/relative_path')
// - Element.setAttribute('src', "/assets/img.jpg") -> Element.setAttribute('src', "/https://proxiedsite.com/assets/img.jpg") -> fetch('/https://proxiedsite.com/relative_path')
func PatchDynamicResourceURLs() proxychain.ResponseModification {
return func(chain *proxychain.ProxyChain) error {
// don't add rewriter if it's not even html
ct := chain.Response.Header.Get("content-type")
if !strings.HasPrefix(ct, "text/html") {
return nil
}
// this is the original URL sent by client:
// http://localhost:8080/http://proxiedsite.com/foo/bar
originalURI := chain.Context.Request().URI()
// this is the extracted URL that the client requests to proxy
// http://proxiedsite.com/foo/bar
reqURL := chain.Request.URL
params := map[string]string{
// ie: http://localhost:8080
"{{PROXY_ORIGIN}}": fmt.Sprintf("%s://%s", originalURI.Scheme(), originalURI.Host()),
// ie: http://proxiedsite.com
"{{ORIGIN}}": fmt.Sprintf("%s://%s", reqURL.Scheme, reqURL.Host),
}
// the rewriting actually happens in chain.Execute() as the client is streaming the response body back
rr := rewriters.NewScriptInjectorRewriterWithParams(
patchDynamicResourceURLsScript,
rewriters.BeforeDOMContentLoaded,
params,
)
// we just queue it up here
chain.AddHTMLTokenRewriter(rr)
return nil
}
}

View File

@@ -2,6 +2,19 @@
// Also overrides the attribute setter prototype to modify the request URLs
// fetch("/relative_script.js") -> fetch("http://localhost:8080/relative_script.js")
(() => {
// ============== PARAMS ===========================
// if the original request was: http://localhost:8080/http://proxiedsite.com/foo/bar
// proxyOrigin is http://localhost:8080
const proxyOrigin = "{{PROXY_ORIGIN}}";
//const proxyOrigin = globalThis.window.location.origin;
// if the original request was: http://localhost:8080/http://proxiedsite.com/foo/bar
// origin is http://proxiedsite.com
const origin = "{{ORIGIN}}";
//const origin = (new URL(decodeURIComponent(globalThis.window.location.pathname.substring(1)))).origin
// ============== END PARAMS ======================
const blacklistedSchemes = [
"ftp:",
"mailto:",
@@ -24,11 +37,6 @@
// don't rewrite special URIs
if (blacklistedSchemes.includes(url)) return url;
//const proxyOrigin = globalThis.window.location.origin;
const proxyOrigin = "R_PROXYURL";
//const origin = (new URL(decodeURIComponent(globalThis.window.location.pathname.substring(1)))).origin
const origin = "R_BASEURL";
// don't rewrite invalid URIs
try { new URL(url, origin) } catch { return url }

View File

@@ -3,6 +3,7 @@ package rewriters
import (
_ "embed"
"fmt"
"sort"
"strings"
"golang.org/x/net/html"
@@ -50,6 +51,22 @@ func (r *ScriptInjectorRewriter) ModifyToken(token *html.Token) (string, string)
}
}
// applies parameters by string replacement of the template script
func (r *ScriptInjectorRewriter) applyParams(params map[string]string) {
// Sort the keys by length in descending order
keys := make([]string, 0, len(params))
for key := range params {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
return len(keys[i]) > len(keys[j])
})
for _, key := range keys {
r.script = strings.ReplaceAll(r.script, key, params[key])
}
}
// NewScriptInjectorRewriter implements a HtmlTokenRewriter
// and injects JS into the page for execution at a particular time
func NewScriptInjectorRewriter(script string, execTime ScriptExecTime) *ScriptInjectorRewriter {
@@ -58,3 +75,17 @@ func NewScriptInjectorRewriter(script string, execTime ScriptExecTime) *ScriptIn
script: script,
}
}
// NewScriptInjectorRewriterWith implements a HtmlTokenRewriter
// and injects JS into the page for execution at a particular time
// accepting arguments into the script, which will be added via a string replace
// the params map represents the key-value pair of the params.
// the key will be string replaced with the value
func NewScriptInjectorRewriterWithParams(script string, execTime ScriptExecTime, params map[string]string) *ScriptInjectorRewriter {
rr := &ScriptInjectorRewriter{
execTime: execTime,
script: script,
}
rr.applyParams(params)
return rr
}