From f2d53c5e3af6fee42d15ad18356f2e199031947e Mon Sep 17 00:00:00 2001 From: Yehor Komarov Date: Fri, 15 Nov 2024 17:20:00 +0300 Subject: [PATCH 1/2] Declare time-related variables --- internal/variables/variables.go | 20 ++++++++++++++++++- internal/variables/variables_test.go | 2 +- internal/variables/variablesmap.gen.go | 27 ++++++++++++++++++++++++++ types/variables/variables.go | 18 +++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/internal/variables/variables.go b/internal/variables/variables.go index 717b18f8b..14ecd7c6b 100644 --- a/internal/variables/variables.go +++ b/internal/variables/variables.go @@ -4,7 +4,7 @@ //go:generate go run generator/main.go // Package variables contains the representation of the variables used in the rules -// Variables are created as bytes and they have a string representation +// Variables are created as bytes, and they have a string representation package variables // This internal file contains all variables supported by handling of SecLang, such as @@ -236,4 +236,22 @@ const ( ResBodyProcessorError // ResBodyProcessorErrorMsg ResBodyProcessorErrorMsg + // Time + Time + // TimeDay + TimeDay + // TimeEpoch + TimeEpoch + // TimeHour + TimeHour + // TimeMin + TimeMin + // TimeMon + TimeMon + // TimeSec + TimeSec + // TimeWday + TimeWday + // TimeYear + TimeYear ) diff --git a/internal/variables/variables_test.go b/internal/variables/variables_test.go index cd42bf8c3..a73413721 100644 --- a/internal/variables/variables_test.go +++ b/internal/variables/variables_test.go @@ -18,7 +18,7 @@ import ( ) func TestNameToVariable(t *testing.T) { - vars := []string{"URLENCODED_ERROR", "RESPONSE_CONTENT_TYPE", "UNIQUE_ID", "ARGS_COMBINED_SIZE", "AUTH_TYPE", "FILES_COMBINED_SIZE", "FULL_REQUEST", "FULL_REQUEST_LENGTH", "INBOUND_DATA_ERROR", "MATCHED_VAR", "MATCHED_VAR_NAME", "MULTIPART_BOUNDARY_QUOTED", "MULTIPART_BOUNDARY_WHITESPACE", "MULTIPART_CRLF_LF_LINES", "MULTIPART_DATA_AFTER", "MULTIPART_DATA_BEFORE", "MULTIPART_FILE_LIMIT_EXCEEDED", "MULTIPART_HEADER_FOLDING", "MULTIPART_INVALID_HEADER_FOLDING", "MULTIPART_INVALID_PART", "MULTIPART_INVALID_QUOTING", "MULTIPART_LF_LINE", "MULTIPART_MISSING_SEMICOLON", "MULTIPART_STRICT_ERROR", "MULTIPART_UNMATCHED_BOUNDARY", "OUTBOUND_DATA_ERROR", "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REMOTE_HOST", "REMOTE_PORT", "REQBODY_ERROR", "REQBODY_ERROR_MSG", "REQBODY_PROCESSOR_ERROR", "REQBODY_PROCESSOR_ERROR_MSG", "REQBODY_PROCESSOR", "REQUEST_BASENAME", "REQUEST_BODY", "REQUEST_BODY_LENGTH", "REQUEST_FILENAME", "REQUEST_LINE", "REQUEST_METHOD", "REQUEST_PROTOCOL", "REQUEST_URI", "REQUEST_URI_RAW", "RESPONSE_BODY", "RESPONSE_CONTENT_LENGTH", "RESPONSE_PROTOCOL", "RESPONSE_STATUS", "SERVER_ADDR", "SERVER_NAME", "SERVER_PORT", "SESSIONID", "RESPONSE_HEADERS_NAMES", "REQUEST_HEADERS_NAMES", "USERID", "ARGS", "ARGS_GET", "ARGS_POST", "FILES_SIZES", "FILES_NAMES", "FILES_TMP_CONTENT", "MULTIPART_FILENAME", "MULTIPART_NAME", "MATCHED_VARS_NAMES", "MATCHED_VARS", "FILES", "REQUEST_COOKIES", "REQUEST_HEADERS", "RESPONSE_HEADERS", "GEO", "REQUEST_COOKIES_NAMES", "FILES_TMPNAMES", "ARGS_NAMES", "ARGS_GET_NAMES", "ARGS_POST_NAMES", "RULE", "XML", "TX", "DURATION"} + vars := []string{"URLENCODED_ERROR", "RESPONSE_CONTENT_TYPE", "UNIQUE_ID", "ARGS_COMBINED_SIZE", "AUTH_TYPE", "FILES_COMBINED_SIZE", "FULL_REQUEST", "FULL_REQUEST_LENGTH", "INBOUND_DATA_ERROR", "MATCHED_VAR", "MATCHED_VAR_NAME", "MULTIPART_BOUNDARY_QUOTED", "MULTIPART_BOUNDARY_WHITESPACE", "MULTIPART_CRLF_LF_LINES", "MULTIPART_DATA_AFTER", "MULTIPART_DATA_BEFORE", "MULTIPART_FILE_LIMIT_EXCEEDED", "MULTIPART_HEADER_FOLDING", "MULTIPART_INVALID_HEADER_FOLDING", "MULTIPART_INVALID_PART", "MULTIPART_INVALID_QUOTING", "MULTIPART_LF_LINE", "MULTIPART_MISSING_SEMICOLON", "MULTIPART_STRICT_ERROR", "MULTIPART_UNMATCHED_BOUNDARY", "OUTBOUND_DATA_ERROR", "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REMOTE_HOST", "REMOTE_PORT", "REQBODY_ERROR", "REQBODY_ERROR_MSG", "REQBODY_PROCESSOR_ERROR", "REQBODY_PROCESSOR_ERROR_MSG", "REQBODY_PROCESSOR", "REQUEST_BASENAME", "REQUEST_BODY", "REQUEST_BODY_LENGTH", "REQUEST_FILENAME", "REQUEST_LINE", "REQUEST_METHOD", "REQUEST_PROTOCOL", "REQUEST_URI", "REQUEST_URI_RAW", "RESPONSE_BODY", "RESPONSE_CONTENT_LENGTH", "RESPONSE_PROTOCOL", "RESPONSE_STATUS", "SERVER_ADDR", "SERVER_NAME", "SERVER_PORT", "SESSIONID", "RESPONSE_HEADERS_NAMES", "REQUEST_HEADERS_NAMES", "USERID", "ARGS", "ARGS_GET", "ARGS_POST", "FILES_SIZES", "FILES_NAMES", "FILES_TMP_CONTENT", "MULTIPART_FILENAME", "MULTIPART_NAME", "MATCHED_VARS_NAMES", "MATCHED_VARS", "FILES", "REQUEST_COOKIES", "REQUEST_HEADERS", "RESPONSE_HEADERS", "GEO", "REQUEST_COOKIES_NAMES", "FILES_TMPNAMES", "ARGS_NAMES", "ARGS_GET_NAMES", "ARGS_POST_NAMES", "RULE", "XML", "TX", "DURATION", "TIME", "TIME_DAY", "TIME_EPOCH", "TIME_HOUR", "TIME_MIN", "TIME_MON", "TIME_SEC", "TIME_WDAY", "TIME_YEAR"} for _, v := range vars { _, err := Parse(v) if err != nil { diff --git a/internal/variables/variablesmap.gen.go b/internal/variables/variablesmap.gen.go index 8b707aced..0bbb4b949 100644 --- a/internal/variables/variablesmap.gen.go +++ b/internal/variables/variablesmap.gen.go @@ -206,6 +206,24 @@ func (v RuleVariable) Name() string { return "RES_BODY_PROCESSOR_ERROR" case ResBodyProcessorErrorMsg: return "RES_BODY_PROCESSOR_ERROR_MSG" + case Time: + return "TIME" + case TimeDay: + return "TIME_DAY" + case TimeEpoch: + return "TIME_EPOCH" + case TimeHour: + return "TIME_HOUR" + case TimeMin: + return "TIME_MIN" + case TimeMon: + return "TIME_MON" + case TimeSec: + return "TIME_SEC" + case TimeWday: + return "TIME_WDAY" + case TimeYear: + return "TIME_YEAR" default: return "INVALID_VARIABLE" @@ -309,6 +327,15 @@ var rulemapRev = map[string]RuleVariable{ "RES_BODY_ERROR_MSG": ResBodyErrorMsg, "RES_BODY_PROCESSOR_ERROR": ResBodyProcessorError, "RES_BODY_PROCESSOR_ERROR_MSG": ResBodyProcessorErrorMsg, + "TIME": Time, + "TIME_DAY": TimeDay, + "TIME_EPOCH": TimeEpoch, + "TIME_HOUR": TimeHour, + "TIME_MIN": TimeMin, + "TIME_MON": TimeMon, + "TIME_SEC": TimeSec, + "TIME_WDAY": TimeWday, + "TIME_YEAR": TimeYear, } var errUnknownVariable = errors.New("unknown variable") diff --git a/types/variables/variables.go b/types/variables/variables.go index 659703072..1953ccbd6 100644 --- a/types/variables/variables.go +++ b/types/variables/variables.go @@ -196,6 +196,24 @@ const ( ResBodyProcessorErrorMsg = variables.ResBodyProcessorErrorMsg // MultipartStrictError will be set to 1 when there is an error parsing multipart MultipartStrictError = variables.MultipartStrictError + // Time holds a formatted string representing the time (hour:minute:second). + Time = variables.Time + // TimeDay holds the current day of the month (1-31) + TimeDay = variables.TimeDay + // TimeEpoch holds the time in seconds since 1970 + TimeEpoch = variables.TimeEpoch + // TimeHour holds the current hour of the day (0-23) + TimeHour = variables.TimeHour + // TimeMin holds the current minute of the hour (0-59) + TimeMin = variables.TimeMin + // TimeMon holds the current month of the year (0-11) + TimeMon = variables.TimeMon + // TimeSec holds the current second of the minute (0-59) + TimeSec = variables.TimeSec + // TimeWday holds the current weekday value (1–7), where Monday is 1 + TimeWday = variables.TimeWday + // TimeYear the current four-digit year value + TimeYear = variables.TimeYear ) // Parse returns the byte interpretation From 3e0131eb41bda4b4ef0c66f5692adafc63b25cb4 Mon Sep 17 00:00:00 2001 From: Yehor Komarov Date: Mon, 18 Nov 2024 14:09:51 +0300 Subject: [PATCH 2/2] Set time variables in transaction --- internal/corazawaf/rule_multiphase.go | 18 ++++++ internal/corazawaf/transaction.go | 77 ++++++++++++++++++++++++++ internal/corazawaf/transaction_test.go | 30 ++++++++++ internal/corazawaf/waf.go | 1 + internal/variables/variables.go | 18 +++--- 5 files changed, 135 insertions(+), 9 deletions(-) diff --git a/internal/corazawaf/rule_multiphase.go b/internal/corazawaf/rule_multiphase.go index 00033a837..099e23e6a 100644 --- a/internal/corazawaf/rule_multiphase.go +++ b/internal/corazawaf/rule_multiphase.go @@ -47,6 +47,24 @@ func minPhase(v variables.RuleVariable) types.RulePhase { return types.PhaseResponseHeaders case variables.UniqueID: return types.PhaseRequestHeaders + case variables.Time: + return types.PhaseRequestHeaders + case variables.TimeDay: + return types.PhaseRequestHeaders + case variables.TimeEpoch: + return types.PhaseRequestHeaders + case variables.TimeHour: + return types.PhaseRequestHeaders + case variables.TimeMin: + return types.PhaseRequestHeaders + case variables.TimeMon: + return types.PhaseRequestHeaders + case variables.TimeSec: + return types.PhaseRequestHeaders + case variables.TimeWday: + return types.PhaseRequestHeaders + case variables.TimeYear: + return types.PhaseRequestHeaders case variables.ArgsCombinedSize: // Size changes between phase 1 and 2 so evaluate both times return types.PhaseRequestHeaders diff --git a/internal/corazawaf/transaction.go b/internal/corazawaf/transaction.go index 2fe3585c8..753e6a47e 100644 --- a/internal/corazawaf/transaction.go +++ b/internal/corazawaf/transaction.go @@ -282,6 +282,24 @@ func (tx *Transaction) Collection(idx variables.RuleVariable) collection.Collect return tx.variables.multipartPartHeaders case variables.MultipartStrictError: return tx.variables.multipartStrictError + case variables.Time: + return tx.variables.time + case variables.TimeDay: + return tx.variables.timeDay + case variables.TimeEpoch: + return tx.variables.timeEpoch + case variables.TimeHour: + return tx.variables.timeHour + case variables.TimeMin: + return tx.variables.timeMin + case variables.TimeMon: + return tx.variables.timeMon + case variables.TimeSec: + return tx.variables.timeSec + case variables.TimeWday: + return tx.variables.timeWday + case variables.TimeYear: + return tx.variables.timeYear } return collections.Noop @@ -1591,6 +1609,20 @@ func (tx *Transaction) generateResponseBodyError(err error) { tx.variables.resBodyProcessorErrorMsg.Set(err.Error()) } +// setTimeVariables sets all the time variables +func (tx *Transaction) setTimeVariables() { + timestamp := time.Unix(0, tx.Timestamp) + tx.variables.time.Set(timestamp.Format(time.TimeOnly)) + tx.variables.timeDay.Set(strconv.Itoa(timestamp.Day())) + tx.variables.timeEpoch.Set(strconv.FormatInt(timestamp.Unix(), 10)) + tx.variables.timeHour.Set(strconv.Itoa(timestamp.Hour())) + tx.variables.timeMin.Set(strconv.Itoa(timestamp.Minute())) + tx.variables.timeSec.Set(strconv.Itoa(timestamp.Second())) + tx.variables.timeWday.Set(strconv.Itoa(int(timestamp.Weekday()))) + tx.variables.timeMon.Set(strconv.Itoa(int(timestamp.Month()))) + tx.variables.timeYear.Set(strconv.Itoa(timestamp.Year())) +} + // TransactionVariables has pointers to all the variables of the transaction type TransactionVariables struct { args *collections.ConcatKeyed @@ -1669,6 +1701,15 @@ type TransactionVariables struct { resBodyErrorMsg *collections.Single resBodyProcessorError *collections.Single resBodyProcessorErrorMsg *collections.Single + time *collections.Single + timeDay *collections.Single + timeEpoch *collections.Single + timeHour *collections.Single + timeMin *collections.Single + timeMon *collections.Single + timeSec *collections.Single + timeWday *collections.Single + timeYear *collections.Single } func NewTransactionVariables() *TransactionVariables { @@ -1741,6 +1782,15 @@ func NewTransactionVariables() *TransactionVariables { v.requestXML = collections.NewMap(variables.RequestXML) v.multipartPartHeaders = collections.NewMap(variables.MultipartPartHeaders) v.multipartStrictError = collections.NewSingle(variables.MultipartStrictError) + v.time = collections.NewSingle(variables.Time) + v.timeDay = collections.NewSingle(variables.TimeDay) + v.timeEpoch = collections.NewSingle(variables.TimeEpoch) + v.timeHour = collections.NewSingle(variables.TimeHour) + v.timeMin = collections.NewSingle(variables.TimeMin) + v.timeMon = collections.NewSingle(variables.TimeMon) + v.timeSec = collections.NewSingle(variables.TimeSec) + v.timeWday = collections.NewSingle(variables.TimeWday) + v.timeYear = collections.NewSingle(variables.TimeYear) // XML is a pointer to RequestXML v.xml = v.requestXML @@ -2299,6 +2349,33 @@ func (v *TransactionVariables) All(f func(v variables.RuleVariable, col collecti if !f(variables.XML, v.xml) { return } + if !f(variables.Time, v.time) { + return + } + if !f(variables.TimeDay, v.timeDay) { + return + } + if !f(variables.TimeEpoch, v.timeEpoch) { + return + } + if !f(variables.TimeHour, v.timeHour) { + return + } + if !f(variables.TimeMin, v.timeMin) { + return + } + if !f(variables.TimeMon, v.timeMon) { + return + } + if !f(variables.TimeSec, v.timeSec) { + return + } + if !f(variables.TimeWday, v.timeWday) { + return + } + if !f(variables.TimeYear, v.timeYear) { + return + } } type formattable interface { diff --git a/internal/corazawaf/transaction_test.go b/internal/corazawaf/transaction_test.go index 07881c092..de986cbfa 100644 --- a/internal/corazawaf/transaction_test.go +++ b/internal/corazawaf/transaction_test.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/corazawaf/coraza/v3/collection" "github.com/corazawaf/coraza/v3/debuglog" @@ -67,6 +68,23 @@ func TestTxSetters(t *testing.T) { validateMacroExpansion(exp, tx, t) } + +func TestTxTime(t *testing.T) { + tx := makeTransactionTimestamped(t) + exp := map[string]string{ + "%{TIME}": "15:27:34", + "%{TIME_DAY}": "18", + "%{TIME_EPOCH}": fmt.Sprintf("%d", tx.Timestamp/1e9), // 1731943654 in UTC, may differ in local timezone + "%{TIME_HOUR}": "15", + "%{TIME_MIN}": "27", + "%{TIME_MON}": "11", + "%{TIME_SEC}": "34", + "%{TIME_WDAY}": "1", + "%{TIME_YEAR}": "2024", + } + validateMacroExpansion(exp, tx, t) +} + func TestTxMultipart(t *testing.T) { tx := NewWAF().NewTransaction() body := []string{ @@ -1360,6 +1378,18 @@ func makeTransaction(t testing.TB) *Transaction { return tx } +func makeTransactionTimestamped(t testing.TB) *Transaction { + t.Helper() + tx := NewWAF().NewTransaction() + timestamp, err := time.ParseInLocation(time.DateTime, "2024-11-18 15:27:34", time.Local) + if err != nil { + panic(err) + } + tx.Timestamp = timestamp.UnixNano() + tx.setTimeVariables() + return tx +} + func makeTransactionMultipart(t *testing.T) *Transaction { if t != nil { t.Helper() diff --git a/internal/corazawaf/waf.go b/internal/corazawaf/waf.go index e4430a815..55db79942 100644 --- a/internal/corazawaf/waf.go +++ b/internal/corazawaf/waf.go @@ -242,6 +242,7 @@ func (w *WAF) newTransaction(opts Options) *Transaction { tx.variables.duration.Set("0") tx.variables.highestSeverity.Set("0") tx.variables.uniqueID.Set(tx.id) + tx.setTimeVariables() tx.debugLogger.Debug().Msg("Transaction started") diff --git a/internal/variables/variables.go b/internal/variables/variables.go index 14ecd7c6b..9a792994a 100644 --- a/internal/variables/variables.go +++ b/internal/variables/variables.go @@ -236,22 +236,22 @@ const ( ResBodyProcessorError // ResBodyProcessorErrorMsg ResBodyProcessorErrorMsg - // Time + // Time holds a formatted string representing the time (hour:minute:second). Time - // TimeDay + // TimeDay holds the current day of the month (1-31) TimeDay - // TimeEpoch + // TimeEpoch holds the time in seconds since 1970 TimeEpoch - // TimeHour + // TimeHour holds the current hour of the day (0-23) TimeHour - // TimeMin + // TimeMin holds the current minute of the hour (0-59) TimeMin - // TimeMon + // TimeMon holds the current month of the year (0-11) TimeMon - // TimeSec + // TimeSec holds the current second of the minute (0-59) TimeSec - // TimeWday + // TimeWday holds the current weekday value (1–7), where Monday is 1 TimeWday - // TimeYear + // TimeYear the current four-digit year value TimeYear )