modsecurity.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package waap_rule
  2. import (
  3. "fmt"
  4. "hash/fnv"
  5. "strings"
  6. )
  7. type ModsecurityRule struct {
  8. ids []uint32
  9. }
  10. var zonesMap map[string]string = map[string]string{
  11. "ARGS": "ARGS_GET",
  12. "ARGS_NAMES": "ARGS_GET_NAMES",
  13. "BODY_ARGS": "ARGS_POST",
  14. "BODY_ARGS_NAMES": "ARGS_POST_NAMES",
  15. "HEADERS": "REQUEST_HEADERS",
  16. "METHOD": "REQUEST_METHOD",
  17. "PROTOCOL": "REQUEST_PROTOCOL",
  18. "URI": "REQUEST_URI",
  19. }
  20. var transformMap map[string]string = map[string]string{
  21. "lowercase": "t:lowercase",
  22. "uppercase": "t:uppercase",
  23. "b64decode": "t:base64Decode",
  24. "hexdecode": "t:hexDecode",
  25. "length": "t:length",
  26. }
  27. var matchMap map[string]string = map[string]string{
  28. "regex": "@rx",
  29. "equal": "@streq",
  30. "startsWith": "@beginsWith",
  31. "endsWith": "@endsWith",
  32. "contains": "@contains",
  33. "libinjectionSQL": "@detectSQLi",
  34. "libinjectionXSS": "@detectXSS",
  35. "gt": "@gt",
  36. "lt": "@lt",
  37. "ge": "@ge",
  38. "le": "@le",
  39. }
  40. var bodyTypeMatch map[string]string = map[string]string{
  41. "json": "JSON",
  42. "xml": "XML",
  43. "multipart": "MULTIPART",
  44. "urlencoded": "URLENCODED",
  45. }
  46. func (m *ModsecurityRule) Build(rule *CustomRule, waapRuleName string) (string, []uint32, error) {
  47. rules, err := m.buildRules(rule, waapRuleName, false, 0, 0)
  48. if err != nil {
  49. return "", nil, err
  50. }
  51. //We return the id of the first generated rule, as it's the interesting one in case of chain or skip
  52. return strings.Join(rules, "\n"), m.ids, nil
  53. }
  54. func (m *ModsecurityRule) generateRuleID(rule *CustomRule, waapRuleName string, depth int) uint32 {
  55. h := fnv.New32a()
  56. h.Write([]byte(waapRuleName))
  57. h.Write([]byte(rule.Match.Type))
  58. h.Write([]byte(rule.Match.Value))
  59. h.Write([]byte(fmt.Sprintf("%d", depth)))
  60. for _, zone := range rule.Zones {
  61. h.Write([]byte(zone))
  62. }
  63. for _, transform := range rule.Transform {
  64. h.Write([]byte(transform))
  65. }
  66. id := h.Sum32()
  67. m.ids = append(m.ids, id)
  68. return id
  69. }
  70. func (m *ModsecurityRule) buildRules(rule *CustomRule, waapRuleName string, and bool, toSkip int, depth int) ([]string, error) {
  71. ret := make([]string, 0)
  72. if rule.And != nil {
  73. for c, andRule := range rule.And {
  74. depth++
  75. //subName := fmt.Sprintf("%s_and_%d", waapRuleName, c)
  76. lastRule := c == len(rule.And)-1 // || len(rule.Or) == 0
  77. rules, err := m.buildRules(&andRule, waapRuleName, !lastRule, 0, depth)
  78. if err != nil {
  79. return nil, err
  80. }
  81. ret = append(ret, rules...)
  82. }
  83. }
  84. if rule.Or != nil {
  85. for c, orRule := range rule.Or {
  86. depth++
  87. //subName := fmt.Sprintf("%s_or_%d", waapRuleName, c)
  88. skip := len(rule.Or) - c - 1
  89. rules, err := m.buildRules(&orRule, waapRuleName, false, skip, depth)
  90. if err != nil {
  91. return nil, err
  92. }
  93. ret = append(ret, rules...)
  94. }
  95. }
  96. r := strings.Builder{}
  97. r.WriteString("SecRule ")
  98. if rule.Zones == nil {
  99. return ret, nil
  100. }
  101. for idx, zone := range rule.Zones {
  102. mappedZone, ok := zonesMap[zone]
  103. if !ok {
  104. return nil, fmt.Errorf("unknown zone '%s'", zone)
  105. }
  106. if len(rule.Variables) == 0 {
  107. r.WriteString(mappedZone)
  108. } else {
  109. for j, variable := range rule.Variables {
  110. if idx > 0 || j > 0 {
  111. r.WriteByte('|')
  112. }
  113. r.WriteString(fmt.Sprintf("%s:%s", mappedZone, variable))
  114. }
  115. }
  116. }
  117. r.WriteByte(' ')
  118. if rule.Match.Type != "" {
  119. if match, ok := matchMap[rule.Match.Type]; ok {
  120. r.WriteString(fmt.Sprintf(`"%s %s"`, match, rule.Match.Value))
  121. } else {
  122. return nil, fmt.Errorf("unknown match type '%s'", rule.Match.Type)
  123. }
  124. }
  125. //Should phase:2 be configurable?
  126. r.WriteString(fmt.Sprintf(` "id:%d,phase:2,deny,log,msg:'%s',tag:'crowdsec-%s'`, m.generateRuleID(rule, waapRuleName, depth), waapRuleName, waapRuleName))
  127. if rule.Transform != nil {
  128. for _, transform := range rule.Transform {
  129. r.WriteByte(',')
  130. if mappedTransform, ok := transformMap[transform]; ok {
  131. r.WriteString(mappedTransform)
  132. } else {
  133. return nil, fmt.Errorf("unknown transform '%s'", transform)
  134. }
  135. }
  136. }
  137. if rule.BodyType != "" {
  138. if mappedBodyType, ok := bodyTypeMatch[rule.BodyType]; ok {
  139. r.WriteString(fmt.Sprintf(",ctl:requestBodyProcessor=%s", mappedBodyType))
  140. } else {
  141. return nil, fmt.Errorf("unknown body type '%s'", rule.BodyType)
  142. }
  143. }
  144. if and {
  145. r.WriteString(",chain")
  146. }
  147. if toSkip > 0 {
  148. r.WriteString(fmt.Sprintf(",skip:%d", toSkip))
  149. }
  150. r.WriteByte('"')
  151. ret = append(ret, r.String())
  152. return ret, nil
  153. }