Skip to content

Commit

Permalink
fix: multipart rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
jptosso committed Dec 6, 2021
1 parent 04d2c21 commit 24af0c8
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 43 deletions.
115 changes: 75 additions & 40 deletions bodyprocessors/multipart.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ package bodyprocessors
import (
"fmt"
"io"
"net/http"
"log"
"mime"
"mime/multipart"
"os"
"strings"

"github.com/jptosso/coraza-waf/v2/types/variables"
)
Expand All @@ -26,53 +30,84 @@ type multipartBodyProcessor struct {
collections *collectionsMap
}

func (mbp *multipartBodyProcessor) Read(reader io.Reader, mime string, storagePath string) error {
req, _ := http.NewRequest("GET", "/", reader)
req.Header.Set("Content-Type", mime)
err := req.ParseMultipartForm(1000000000)
func (mbp *multipartBodyProcessor) Read(reader io.Reader, mimeType string, storagePath string) error {
mediaType, params, err := mime.ParseMediaType(mimeType)
if err != nil {
return err
log.Fatal(err)
}
totalSize := int64(0)
fn := map[string][]string{
"": {},
}
fl := map[string][]string{
"": {},
if !strings.HasPrefix(mediaType, "multipart/") {
return fmt.Errorf("not a multipart body")
}
fs := map[string][]string{
"": {},
}
for field, fheaders := range req.MultipartForm.File {
// TODO add them to temporal storage
// or maybe not, according to http.MultipartForm, it does exactly that
// the main issue is how do I get this path?
fn[""] = append(fn[""], field)
for _, header := range fheaders {
fl[""] = append(fl[""], header.Filename)
totalSize += header.Size
fs[""] = append(fs[""], fmt.Sprintf("%d", header.Size))
mr := multipart.NewReader(reader, params["boundary"])
totalSize := int64(0)
filesNames := []string{}
filesArgNames := []string{}
fileList := []string{}
fileSizes := []string{}
postNames := []string{}
postFields := map[string][]string{}
for {
p, err := mr.NextPart()
if err == io.EOF {
break
}
if err != nil {
return err
}
// we create a temp file

// if is a file
if p.FileName() != "" {
temp, err := os.CreateTemp(storagePath, "crzmp*")
if err != nil {
return err
}
sz, err := io.Copy(temp, p)
if err != nil {
return err
}
totalSize += sz
filesNames = append(filesNames, p.FileName())
fileList = append(fileList, temp.Name())
fileSizes = append(fileSizes, fmt.Sprintf("%d", sz))
filesArgNames = append(filesArgNames, p.FormName())
} else {
fmt.Println("VARIABLE", p.FormName())
// if is a field
data, err := io.ReadAll(p)
if err != nil {
return err
}
totalSize += int64(len(data))
postNames = append(postNames, p.FormName())
if _, ok := postFields[p.FormName()]; !ok {
postFields[p.FormName()] = []string{}
}
postFields[p.FormName()] = append(postFields[p.FormName()], string(data))

}
}
m := map[string][]string{}
names := []string{}
for k, vs := range req.MultipartForm.Value {
m[k] = vs
names = append(names, k)
}
fcs := map[string][]string{
"": {fmt.Sprintf("%d", totalSize)},
}
mbp.collections = &collectionsMap{
variables.FilesNames: fn,
variables.Files: fl,
variables.FilesSizes: fs,
variables.FilesCombinedSize: fcs,
variables.ArgsPost: m,
variables.FilesNames: map[string][]string{
"": filesArgNames,
},
variables.FilesTmpNames: map[string][]string{
"": fileList,
},
variables.Files: map[string][]string{
"": filesNames,
},
variables.FilesSizes: map[string][]string{
"": fileSizes,
},
variables.ArgsPostNames: map[string][]string{
"": names,
"": postNames,
},
variables.ArgsPost: postFields,
variables.Args: postFields,
variables.FilesCombinedSize: map[string][]string{
"": {fmt.Sprintf("%d", totalSize)},
},
variables.Args: m,
}

return nil
Expand Down
60 changes: 60 additions & 0 deletions bodyprocessors/multipart_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package bodyprocessors

import (
"os"
"strings"
"testing"

"github.com/jptosso/coraza-waf/v2/types/variables"
)

func TestMultipartProcessor(t *testing.T) {
payload := `-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="text"
text default
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
<!DOCTYPE html><title>Content of a.html.</title>
-----------------------------9051914041544843365972754266--`
payload = strings.ReplaceAll(payload, "\n", "\r\n")

p := multipartBodyProcessor{}
if err := p.Read(strings.NewReader(payload), "multipart/form-data; boundary=---------------------------9051914041544843365972754266", "/tmp"); err != nil {
t.Error(err)
}

res := p.Collections()
if len(res[variables.FilesNames][""]) != 2 {
t.Errorf("Expected 2 files, got %d", len(res[variables.FilesNames]))
}
if len(res[variables.ArgsPostNames]) != 1 {
t.Errorf("Expected 1 args, got %d", len(res[variables.ArgsPostNames]))
}
if len(res[variables.ArgsPost]["text"]) == 0 || res[variables.ArgsPost]["text"][0] != "text default" {
t.Errorf("Expected text3 to be 'some super text content 3', got %v", res[variables.ArgsPost])
}
if len(res[variables.FilesTmpNames][""]) != 2 {
t.Errorf("Expected 2 files, got %d", len(res[variables.FilesTmpNames]))
}
if len(res[variables.FilesTmpNames]) > 0 {
if len(res[variables.FilesTmpNames][""]) == 0 {
t.Errorf("Expected files, got %d", len(res[variables.FilesTmpNames][""]))
} else {
fname := res[variables.FilesTmpNames][""][0]
if _, err := os.Stat(fname); err != nil {
t.Errorf("Expected file %s to exist", fname)
}

}
}
}
2 changes: 1 addition & 1 deletion transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func TestTxMultipart(t *testing.T) {
}
exp := map[string]string{
"%{args_post.text}": "test-value",
"%{files_combined_size}": "50",
"%{files_combined_size}": "60",
"%{files}": "a.html",
"%{files_names}": "file1",
}
Expand Down
4 changes: 2 additions & 2 deletions types/variables/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const (
// Geo contains the location information of the client
Geo RuleVariable = iota
RequestCookiesNames RuleVariable = iota
FilesTmpnames RuleVariable = iota
FilesTmpNames RuleVariable = iota
// ArgsNames contains the names of the arguments (POST and GET)
ArgsNames RuleVariable = iota
// ArgsGetNames contains the names of the GET arguments
Expand Down Expand Up @@ -243,7 +243,7 @@ var rulemap = map[RuleVariable]string{
ResponseHeaders: "RESPONSE_HEADERS",
Geo: "GEO",
RequestCookiesNames: "REQUEST_COOKIES_NAMES",
FilesTmpnames: "FILES_TMPNAMES",
FilesTmpNames: "FILES_TMPNAMES",
ArgsNames: "ARGS_NAMES",
ArgsGetNames: "ARGS_GET_NAMES",
ArgsPostNames: "ARGS_POST_NAMES",
Expand Down

0 comments on commit 24af0c8

Please sign in to comment.