@@ -39,6 +39,10 @@ func (mbp *multipartBodyProcessor) ProcessRequest(reader io.Reader, v plugintype filesCombinedSizeCol := v.FilesCombinedSize() filesNamesCol := v.FilesNames() headersNames := v.MultipartPartHeaders()
Show 4 hidden lines
+ multipartFilenameCol := v.MultipartFilename()+ multipartFilenameCharsetCol := v.MultipartFilenameCharset()+ multipartFilenameLanguageCol := v.MultipartFilenameLanguage()+ multipartNameCol := v.MultipartName()
for { p, err := mr.NextPart() if err == io.EOF {@@ -49,13 +53,57 @@ func (mbp *multipartBodyProcessor) ProcessRequest(reader io.Reader, v plugintype return err } partName := p.FormName()
Show 6 hidden lines
+ duplicateHeader := false for key, values := range p.Header {+ if len(values) > 1 {+ duplicateHeader = true+ } for _, value := range values {
headersNames.Add(partName, fmt.Sprintf("%s: %s", key, value)) } }+ fields := originFileName(p)+ if duplicateHeader || fields.duplicateParam {+ v.MultipartDuplicatePartHeader().(*collections.Single).Set("1")+ }
Show 8 hidden lines
+ if fields.invalidQuoting {+ v.MultipartInvalidQuoting().(*collections.Single).Set("1")+ }+ if duplicateHeader || fields.duplicateParam || fields.malformed || fields.invalidQuoting {+ v.MultipartStrictError().(*collections.Single).Set("1")+ }+ // Add, not Set: distinct parts sharing one "name" (e.g. a multi-file+ // input) must each keep their own filename visible to rules. Set
+ // would let the last part silently overwrite the ones before it,+ // the same class of bypass this advisory already covers -- see+ // ModSecurity's GHSA-5pww-8rfg-9crf, which names this "a second,+ // distinct bypass" fixed by keeping MULTIPART_FILENAME/_NAME as+ // multi-valued collections rather than single overwritable slots.+ multipartNameCol.Add(partName, partName)+ if fields.filename != "" {
Show 11 hidden lines
+ multipartFilenameCol.Add(partName, fields.filename)+ }+ // altFilename is the reading a backend using different filename*+ // precedence than Coraza would resolve instead: added alongside the+ // primary one rather than picking a side, so a rule matches whichever+ // name is actually used downstream.+ if fields.altFilename != "" {+ multipartFilenameCol.Add(partName, fields.altFilename)+ }+ if fields.hasExtended {+ multipartFilenameCharsetCol.Add(partName, fields.charset)
+ multipartFilenameLanguageCol.Add(partName, fields.language)+ } // if is a file- filename := originFileName(p)+ filename := fields.filename+ if filename == "" {+ // An extended "filename*" that decodes to the empty string (e.g.
Show 6 hidden lines
+ // filename*=utf-8'') would otherwise misclassify the part as a+ // field even though a non-empty plain "filename" is present --+ // PHP, Go mime/multipart, python-multipart and formidable all+ // still resolve it as a file. See GHSA-3wr7-993q-jrff review.+ filename = fields.altFilename+ }
if filename != "" { var size int64 seenUnexpectedEOF := false@@ -98,6 +146,15 @@ func (mbp *multipartBodyProcessor) ProcessRequest(reader io.Reader, v plugintype totalSize += size filesCol.Add("", filename) fileSizesCol.SetIndex(filename, 0, fmt.Sprintf("%d", size))
Show 16 hidden lines
+ if fields.altFilename != "" && fields.altFilename != filename {+ // Same part, same bytes, same size -- just a second name a+ // differently-implemented backend might resolve instead.+ // (Skipped when equal to filename: that happens when+ // fields.filename was empty and altFilename was already+ // promoted to primary above -- not two distinct readings.)+ filesCol.Add("", fields.altFilename)+ fileSizesCol.SetIndex(fields.altFilename, 0, fmt.Sprintf("%d", size))+ } filesNamesCol.Add("", p.FormName()) filesCombinedSizeCol.(*collections.Single).Set(fmt.Sprintf("%d", totalSize)) if seenUnexpectedEOF {@@ -132,19 +189,333 @@ var ( _ plugintypes.BodyProcessor = (*multipartBodyProcessor)(nil) )
originFileName supplies primary and alternate filename readings; ProcessRequest exposes them via MULTIPART_FILENAME and related metadata, and duplicate or malformed headers set MULTIPART_STRICT_ERROR. This incomplete handling still misses a continuation beside a bare filename*.