From cfa39f15bc156bf0125babe3471cae43b8254533 Mon Sep 17 00:00:00 2001 From: Matteo Pace Date: Wed, 13 Dec 2023 16:02:02 +0100 Subject: [PATCH 1/2] more forgiving base64 transformation --- internal/transformations/base64decode.go | 18 ++++- internal/transformations/base64decode_test.go | 78 +++++++++++++++++-- 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/internal/transformations/base64decode.go b/internal/transformations/base64decode.go index c4be9897c..52bb86baa 100644 --- a/internal/transformations/base64decode.go +++ b/internal/transformations/base64decode.go @@ -5,16 +5,28 @@ package transformations import ( "encoding/base64" + "strings" stringsutil "github.com/corazawaf/coraza/v3/internal/strings" ) // base64decode decodes a Base64-encoded string. func base64decode(data string) (string, bool, error) { - dec, err := base64.StdEncoding.DecodeString(data) + // RawStdEncoding.DecodeString accepts and requires an unpadded string as input + // https://stackoverflow.com/questions/31971614/base64-encode-decode-without-padding-on-golang-appengine + dataNoPadding := strings.TrimRight(data, "=") + dec, err := base64.RawStdEncoding.DecodeString(dataNoPadding) if err != nil { - // Forgiving implementation, which ignores invalid characters - return data, false, nil + // If the error is of type CorruptInputError, we can get the position of the illegal character + // and perform a partial decoding up to that point + if corrErr, ok := err.(base64.CorruptInputError); ok { + illegalCharPos := int(corrErr) + // Forgiving call to DecodeString, decoding is performed up to the illegal characther + // If an error occurs, dec will still contain the decoded string up to the error + dec, _ = base64.RawStdEncoding.DecodeString(dataNoPadding[:illegalCharPos]) + } else { + return data, false, nil + } } return stringsutil.WrapUnsafe(dec), true, nil } diff --git a/internal/transformations/base64decode_test.go b/internal/transformations/base64decode_test.go index d022b1659..65b43adbf 100644 --- a/internal/transformations/base64decode_test.go +++ b/internal/transformations/base64decode_test.go @@ -10,17 +10,81 @@ import ( "testing" ) -var b64DecodeTests = []string{ - "VGVzdENhc2U=", - "P.HNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==", - "VGVzdABDYXNl", +var b64DecodeTests = []struct { + name string + input string + expected string +}{ + { + name: "Valid", + input: "VGVzdENhc2U=", + expected: "TestCase", + }, + { + name: "Valid with \u0000", + input: "VGVzdABDYXNl", + expected: "Test\x00Case", + }, + { + name: "Valid without padding", + input: "VGVzdENhc2U", + expected: "TestCase", + }, + { + name: "Valid without longer padding", + input: "PA==", + expected: "<", + }, + { + name: "valid ", + input: "PFRFU1Q+", + expected: "", + }, + { + name: "decoded up to the space (invalid caracter)", + input: "PFR FU1Q+", + expected: " Date: Fri, 15 Dec 2023 10:59:11 +0100 Subject: [PATCH 2/2] better comment about second DecodeString call --- internal/transformations/base64decode.go | 6 ++++-- internal/transformations/base64decode_test.go | 15 ++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/transformations/base64decode.go b/internal/transformations/base64decode.go index 52bb86baa..42eb0b5f3 100644 --- a/internal/transformations/base64decode.go +++ b/internal/transformations/base64decode.go @@ -21,8 +21,10 @@ func base64decode(data string) (string, bool, error) { // and perform a partial decoding up to that point if corrErr, ok := err.(base64.CorruptInputError); ok { illegalCharPos := int(corrErr) - // Forgiving call to DecodeString, decoding is performed up to the illegal characther - // If an error occurs, dec will still contain the decoded string up to the error + // Forgiving call (no error check) to DecodeString. Decoding is performed truncating + // the input string to the first error index. If a new decoding error occurs, + // it will not be about an illegal character but a malformed encoding of the trailing + // character because of the truncation. The dec will still contain a best effort decoded string dec, _ = base64.RawStdEncoding.DecodeString(dataNoPadding[:illegalCharPos]) } else { return data, false, nil diff --git a/internal/transformations/base64decode_test.go b/internal/transformations/base64decode_test.go index 65b43adbf..cd5ef9213 100644 --- a/internal/transformations/base64decode_test.go +++ b/internal/transformations/base64decode_test.go @@ -41,27 +41,32 @@ var b64DecodeTests = []struct { expected: "", }, { - name: "decoded up to the space (invalid caracter)", + name: "Malformed base64 encoding", + input: "PHNjcmlwd", + expected: "