diff --git a/Makefile b/Makefile index b962c3f..cd1ebf8 100644 --- a/Makefile +++ b/Makefile @@ -33,4 +33,12 @@ clean: @go clean @rm -f ./coverage.out ./coverage.html +fuzz: + @echo "Running fuzz tests..." + @fuzz_tests=("FuzzParseCEF" "FuzzParseExtensions" "FuzzAsJSON" "FuzzSecurityCEF" "FuzzStructuredCEF"); \ + for fuzz_test in $${fuzz_tests[@]}; do \ + echo "Running fuzz test: $$fuzz_test"; \ + go test ./parser -fuzz=$$fuzz_test -fuzztime=30s; \ + done + all: test lint fmt vet staticcheck ast diff --git a/parser/parser_fuzz_test.go b/parser/parser_fuzz_test.go new file mode 100644 index 0000000..0bbac38 --- /dev/null +++ b/parser/parser_fuzz_test.go @@ -0,0 +1,138 @@ +// parser_fuzz_test.go +package parser + +import ( + "context" + "encoding/json" + "strings" + "testing" +) + +// FuzzParseCEF is a fuzz test for the ParseCEF function. +func FuzzParseCEF(f *testing.F) { + f.Add(ImpervaCEF1) + f.Add(CentrifyCEF) + f.Add(ImpervaCEFCombined) + + f.Fuzz(func(t *testing.T, data string) { + _, err := ParseCEFWithContext(context.Background(), data) + if err != nil { + t.Logf("Error parsing CEF: %v", err) + } + }) +} + +// FuzzParseExtensions is a fuzz test for the parseExtensions function. +func FuzzParseExtensions(f *testing.F) { + f.Add("fileId=123 sourceServiceName=example siteid=123") + f.Fuzz(func(t *testing.T, data string) { + parseExtensions(data) + }) +} + +// FuzzAsJSON is a fuzz test for the AsJSON method in the CEF struct. +func FuzzAsJSON(f *testing.F) { + cef := &CEF{ + Version: "0", + DeviceVendor: "ExampleVendor", + DeviceProduct: "ExampleProduct", + DeviceVersion: "1.0", + SignatureID: "1000", + Name: "TestEvent", + Severity: "5", + Extensions: &DefaultExtensions{Fields: map[string]string{"key": "value"}}, + } + + f.Fuzz(func(t *testing.T, data string) { + cef.DeviceVendor = data + cef.AsJSON() + }) +} + +// FuzzStructuredCEF is a structured fuzz test for the ParseCEF function. +func FuzzStructuredCEF(f *testing.F) { + f.Add("Incapsula", "SIEMintegration", "1", "Sample CEF Event") + + f.Fuzz(func(t *testing.T, vendor, product, version, name string) { + cef := "CEF:0|" + vendor + "|" + product + "|" + version + "|1000|" + name + "|5|" + _, err := ParseCEFWithContext(context.Background(), cef) + if err != nil { + t.Logf("Error parsing structured CEF: %v", err) + } + }) +} + +// FuzzSecurityCEF is a security-focused fuzz test for CEF parsing. +func FuzzSecurityCEF(f *testing.F) { + // SQL Injection attempt + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid=' OR '1'='1`) + // Cross-Site Scripting (XSS) attempt + f.Add(`CEF:0|Incapsula|SIEMintegration|1|1||3| fileid=`) + // Command Injection attempt + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid=|touch /tmp/evilfile`) + // Log Forging attempt + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid=maliciousValue\nSecondEvent|malicious|attack`) + // JSON Injection attempt + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid={"key":"value"}`) + // Large JSON object to test for JSON parsing limits and possible memory exhaustion + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid={"key":` + strings.Repeat(`"value",`, 10000) + `"last":"value"}`) + // Nested JSON structure to test recursion limits + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid=` + nestedJSON(100)) + // JSON with special characters that may escape JSON string boundaries + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid="\u202Eevil\json\u202C"`) + // JSON with null bytes to test if Go properly handles them + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid="\u0000"`) + // JSON with extremely deep nesting to test stack overflows or panics + f.Add(`CEF:0|Vendor|Product|1.0|1000|TestEvent|5|fileid=` + deeplyNestedJSON(10000)) + + // Fuzz testing with random inputs to simulate other potential security issues + f.Fuzz(func(t *testing.T, data string) { + cefEvent, err := ParseCEFWithContext(context.Background(), data) + if err != nil { + t.Logf("Security test triggered error: %v", err) + } + + // Additional checks to ensure that fields are properly sanitized + if cefEvent != nil && cefEvent.Extensions != nil { + jsonOutput := cefEvent.AsJSON() + if len(jsonOutput) == 0 { + t.Errorf("JSON output is empty for input: %s", data) + } + if !isValidJSON(jsonOutput) { + t.Errorf("JSON output is malformed for input: %s", data) + } + } + }) +} + +// isValidJSON checks if the string is valid JSON. +func isValidJSON(s string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(s), &js) == nil +} + +// nestedJSON creates a nested JSON string for testing. +func nestedJSON(depth int) string { + json := `{"key":` + for i := 0; i < depth; i++ { + json += `{"key":` + } + json += `"value"` + for i := 0; i < depth; i++ { + json += `}` + } + return json + `}` +} + +// deeplyNestedJSON creates a deeply nested JSON string for testing recursion limits. +func deeplyNestedJSON(depth int) string { + json := `{"key":` + for i := 0; i < depth; i++ { + json += `{"nested":` + } + json += `"deep_value"` + for i := 0; i < depth; i++ { + json += `}` + } + return json + `}` +}