Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions pkg/server/http_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,30 @@
}
}

// b64BodyPrefix marks a request path carrying a base64 encoded response body.
const b64BodyPrefix = "/b64_body:"

// decodeB64BodyPath decodes a /b64_body:<payload> path.
// HasPrefixI is case insensitive, so the payload offset is the prefix length
// rather than a second case-sensitive search. A single trailing slash is
// accepted as a terminator (nuclei templates use it); other slashes are left
// in place because they are valid in StdEncoding.
func decodeB64BodyPath(path string) []byte {
if !stringsutil.HasPrefixI(path, b64BodyPrefix) {
return nil
}
encoded := path[len(b64BodyPrefix):]
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err == nil {
return decoded
}
if strings.HasSuffix(encoded, "/") {
decoded, _ = base64.StdEncoding.DecodeString(strings.TrimSuffix(encoded, "/"))
return decoded
}
return nil
}

// writeResponseFromDynamicRequest writes a response to http.ResponseWriter
// based on dynamic data from HTTP URL Query parameters.
//
Expand All @@ -340,13 +364,8 @@
func writeResponseFromDynamicRequest(w http.ResponseWriter, req *http.Request) {
values := req.URL.Query()

if stringsutil.HasPrefixI(req.URL.Path, "/b64_body:") {
firstindex := strings.Index(req.URL.Path, "/b64_body:")
lastIndex := strings.LastIndex(req.URL.Path, "/")

decodedBytes, _ := base64.StdEncoding.DecodeString(req.URL.Path[firstindex+10 : lastIndex])
_, _ = w.Write(decodedBytes)

if decoded := decodeB64BodyPath(req.URL.Path); decoded != nil {
_, _ = w.Write(decoded)
}
if headers := values["header"]; len(headers) > 0 {
for _, header := range headers {
Expand Down
66 changes: 66 additions & 0 deletions pkg/server/http_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,69 @@ func TestSessionTotalMetric(t *testing.T) {
require.Equal(t, int64(0), atomic.LoadInt64(&stats.Sessions), "sessions should be 0 after deregister")
require.Equal(t, int64(1), atomic.LoadInt64(&stats.SessionsTotal), "sessions_total should remain 1 after deregister")
}

func TestDecodeB64BodyPath(t *testing.T) {
example := "this is example body"
exampleB64 := base64.StdEncoding.EncodeToString([]byte(example))
slashPayload := []byte{0xff, 0xff, 0xff}
slashB64 := base64.StdEncoding.EncodeToString(slashPayload)
require.Equal(t, "////", slashB64)
plusPayload := []byte{0xfb}
plusB64 := base64.StdEncoding.EncodeToString(plusPayload)
require.Contains(t, plusB64, "+")

tests := []struct {
name string
path string
want []byte
}{
{name: "trailing slash", path: "/b64_body:" + exampleB64 + "/", want: []byte(example)},
{name: "no trailing slash", path: "/b64_body:" + exampleB64, want: []byte(example)},
{name: "uppercase prefix", path: "/B64_BODY:" + exampleB64 + "/", want: []byte(example)},
{name: "empty payload", path: "/b64_body:", want: []byte{}},
{name: "slash in payload", path: "/b64_body:" + slashB64, want: slashPayload},
{name: "slash in payload with terminator", path: "/b64_body:" + slashB64 + "/", want: slashPayload},
{name: "plus in payload", path: "/b64_body:" + plusB64, want: plusPayload},
{name: "unrelated path", path: "/other", want: nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.NotPanics(t, func() {
require.Equal(t, tt.want, decodeB64BodyPath(tt.path))
})
})
}
}

func TestWriteResponseFromDynamicRequestB64Path(t *testing.T) {
example := "this is example body"
exampleB64 := base64.StdEncoding.EncodeToString([]byte(example))
slashPayload := []byte{0xff, 0xff, 0xff}
slashB64 := base64.StdEncoding.EncodeToString(slashPayload)

tests := []struct {
name string
path string
want []byte
}{
{name: "trailing slash", path: "/b64_body:" + exampleB64 + "/", want: []byte(example)},
{name: "no trailing slash", path: "/b64_body:" + exampleB64, want: []byte(example)},
{name: "uppercase prefix", path: "/B64_BODY:" + exampleB64 + "/", want: []byte(example)},
{name: "empty payload", path: "/b64_body:", want: []byte{}},
{name: "slash in payload", path: "/b64_body:" + slashB64, want: slashPayload},
{name: "slash in payload with terminator", path: "/b64_body:" + slashB64 + "/", want: slashPayload},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil)
req.URL.Path = tt.path
w := httptest.NewRecorder()
require.NotPanics(t, func() {
writeResponseFromDynamicRequest(w, req)
})
body, err := io.ReadAll(w.Result().Body)
require.NoError(t, err)
require.Equal(t, tt.want, body)
})
}
}
Loading