Skip to content

Commit

Permalink
more forgiving base64 transformation
Browse files Browse the repository at this point in the history
  • Loading branch information
M4tteoP committed Dec 13, 2023
1 parent 968dc71 commit cfa39f1
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 10 deletions.
18 changes: 15 additions & 3 deletions internal/transformations/base64decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
78 changes: 71 additions & 7 deletions internal/transformations/base64decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <TEST>",
input: "PFRFU1Q+",
expected: "<TEST>",
},
{
name: "decoded up to the space (invalid caracter)",
input: "PFR FU1Q+",
expected: "<T",
},
{
name: "decoded up to the dot (invalid caracter)",
input: "P.HNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==",
expected: "",
},
{
name: "decoded up to the dot (invalid caracter)",
input: "PHNjcmlwd.D5hbGVydCgxKTwvc2NyaXB0Pg==",
expected: "<scrip",
},
{
name: "decoded up to the dot (invalid caracter)",
input: "PHNjcmlwdD.5hbGVydCgxKTwvc2NyaXB0Pg==",
expected: "<script",
},
{
name: "decoded up to the dash (invalid caracter for base64.RawStdEncoding)",
input: "PFRFU1Q-",
expected: "<TEST",
},
}

func TestBase64Decode(t *testing.T) {
for _, tt := range b64DecodeTests {
t.Run(tt.name, func(t *testing.T) {
actual, _, err := base64decode(tt.input)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if actual != tt.expected {
t.Errorf("Expected %q, but got %q", tt.expected, actual)
}
})
}
}
func BenchmarkB64Decode(b *testing.B) {
for _, tt := range b64DecodeTests {
b.Run(tt, func(b *testing.B) {
b.Run(tt.input, func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _, err := base64decode(tt)
_, _, err := base64decode(tt.input)
if err != nil {
b.Error(err)
}
Expand All @@ -31,7 +95,7 @@ func BenchmarkB64Decode(b *testing.B) {

func FuzzB64Decode(f *testing.F) {
for _, tc := range b64DecodeTests {
f.Add(tc)
f.Add(tc.input)
}
f.Fuzz(func(t *testing.T, tc string) {
data, _, err := base64decode(tc)
Expand Down

0 comments on commit cfa39f1

Please sign in to comment.