128 lines
2.5 KiB
Haskell
128 lines
2.5 KiB
Haskell
{-
|
|
©AngelaMos | 2026
|
|
Rule.hs
|
|
-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module Aenebris.WAF.Rule
|
|
( Rule(..)
|
|
, Operator(..)
|
|
, Action(..)
|
|
, Severity(..)
|
|
, Phase(..)
|
|
, Target(..)
|
|
, ParanoiaLevel(..)
|
|
, RuleSet(..)
|
|
, severityScore
|
|
, defaultInboundThreshold
|
|
, defaultOutboundThreshold
|
|
, compileRegex
|
|
, CompiledRegex
|
|
, runRegex
|
|
) where
|
|
|
|
import Data.ByteString (ByteString)
|
|
import Data.Word (Word32)
|
|
import Text.Regex.TDFA (Regex)
|
|
import Text.Regex.TDFA.ByteString (compile, execute)
|
|
import qualified Text.Regex.TDFA as TDFA
|
|
|
|
data Phase
|
|
= PhaseHeaders
|
|
| PhaseRequestBody
|
|
| PhaseResponseHeaders
|
|
| PhaseResponseBody
|
|
deriving (Eq, Show, Ord)
|
|
|
|
data Severity
|
|
= SevNotice
|
|
| SevWarning
|
|
| SevError
|
|
| SevCritical
|
|
deriving (Eq, Show, Ord)
|
|
|
|
severityScore :: Severity -> Int
|
|
severityScore SevNotice = 2
|
|
severityScore SevWarning = 3
|
|
severityScore SevError = 4
|
|
severityScore SevCritical = 5
|
|
|
|
data Action
|
|
= Block
|
|
| Score
|
|
| Log
|
|
| Pass
|
|
deriving (Eq, Show)
|
|
|
|
data ParanoiaLevel
|
|
= PL1
|
|
| PL2
|
|
| PL3
|
|
| PL4
|
|
deriving (Eq, Show, Ord, Enum, Bounded)
|
|
|
|
data Target
|
|
= TargetMethod
|
|
| TargetPath
|
|
| TargetQuery
|
|
| TargetHeaderValue !ByteString
|
|
| TargetAnyHeaderName
|
|
| TargetAnyHeaderValue
|
|
| TargetHost
|
|
| TargetUserAgent
|
|
deriving (Eq, Show)
|
|
|
|
newtype CompiledRegex = CompiledRegex { unCompiledRegex :: Regex }
|
|
|
|
instance Show CompiledRegex where
|
|
show _ = "<CompiledRegex>"
|
|
|
|
instance Eq CompiledRegex where
|
|
_ == _ = False
|
|
|
|
data Operator
|
|
= OpRegex !CompiledRegex
|
|
| OpStreq !ByteString
|
|
| OpContains !ByteString
|
|
| OpAnyMatch ![ByteString]
|
|
deriving (Eq, Show)
|
|
|
|
data Rule = Rule
|
|
{ ruleId :: !Word32
|
|
, ruleName :: !ByteString
|
|
, rulePhase :: !Phase
|
|
, ruleOp :: !Operator
|
|
, ruleTargets :: ![Target]
|
|
, ruleSeverity :: !Severity
|
|
, ruleAction :: !Action
|
|
, ruleParanoia :: !ParanoiaLevel
|
|
} deriving (Show)
|
|
|
|
data RuleSet = RuleSet
|
|
{ rsRules :: ![Rule]
|
|
, rsParanoia :: !ParanoiaLevel
|
|
, rsInboundThreshold :: !Int
|
|
, rsOutboundThreshold :: !Int
|
|
} deriving (Show)
|
|
|
|
defaultInboundThreshold :: Int
|
|
defaultInboundThreshold = 5
|
|
|
|
defaultOutboundThreshold :: Int
|
|
defaultOutboundThreshold = 4
|
|
|
|
compileRegex :: ByteString -> Either String CompiledRegex
|
|
compileRegex pat =
|
|
case compile compOpts execOpts pat of
|
|
Left err -> Left err
|
|
Right r -> Right (CompiledRegex r)
|
|
where
|
|
compOpts = TDFA.defaultCompOpt { TDFA.caseSensitive = False }
|
|
execOpts = TDFA.defaultExecOpt
|
|
|
|
runRegex :: CompiledRegex -> ByteString -> Bool
|
|
runRegex (CompiledRegex r) input =
|
|
case execute r input of
|
|
Right (Just _) -> True
|
|
_ -> False
|