Skip to content

Commit

Permalink
Merge branch 'main' into crs4_rc2
Browse files Browse the repository at this point in the history
  • Loading branch information
M4tteoP authored Dec 13, 2023
2 parents 2725717 + 968dc71 commit f50dd17
Show file tree
Hide file tree
Showing 18 changed files with 151 additions and 87 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/close-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
days-before-issue-stale: 30
days-before-issue-close: 14
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/fix-dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: v1.19.x
cache: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v4
- uses: actions/setup-go@v5
with:
go-version: '>=1.19.0'
- run: go run mage.go fuzz
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: v1.19.x
cache: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tinygo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v4

- name: Install Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
cache: true
Expand Down
52 changes: 27 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,32 +58,34 @@ Coraza can be used as a library for your Go program to implement a security midd
package main

import (
"fmt"
"github.com/corazawaf/coraza/v3"
"fmt"

"github.com/corazawaf/coraza/v3"
)

func main() {
// First we initialize our waf and our seclang parser
waf, err := coraza.NewWAF(coraza.NewWAFConfig().
WithDirectives(`SecRule REMOTE_ADDR "@rx .*" "id:1,phase:1,deny,status:403"`))
// Now we parse our rules
if err != nil {
fmt.Println(err)
}

// Then we create a transaction and assign some variables
tx := waf.NewTransaction()
defer func() {
tx.ProcessLogging()
tx.Close()
}()
tx.ProcessConnection("127.0.0.1", 8080, "127.0.0.1", 12345)

// Finally we process the request headers phase, which may return an interruption
if it := tx.ProcessRequestHeaders(); it != nil {
fmt.Printf("Transaction was interrupted with status %d\n", it.Status)
}
// First we initialize our waf and our seclang parser
waf, err := coraza.NewWAF(coraza.NewWAFConfig().
WithDirectives(`SecRule REMOTE_ADDR "@rx .*" "id:1,phase:1,deny,status:403"`))
// Now we parse our rules
if err != nil {
fmt.Println(err)
}

// Then we create a transaction and assign some variables
tx := waf.NewTransaction()
defer func() {
tx.ProcessLogging()
tx.Close()
}()
tx.ProcessConnection("127.0.0.1", 8080, "127.0.0.1", 12345)

// Finally we process the request headers phase, which may return an interruption
if it := tx.ProcessRequestHeaders(); it != nil {
fmt.Printf("Transaction was interrupted with status %d\n", it.Status)
}
}

```

[Examples/http-server](./examples/http-server/) provides an example to practice with Coraza.
Expand Down Expand Up @@ -132,8 +134,8 @@ Coraza only requires Go for development. You can run `mage.go` to issue developm

See the list of commands

```shell
go run mage.go -l
```
$ go run mage.go -l
Targets:
check runs lint and tests.
coverage runs tests with coverage and race detector enabled.
Expand Down Expand Up @@ -163,8 +165,8 @@ Our vulnerability management team will respond within 3 working days of your rep

## Thanks

* Modsecurity team for creating ModSecurity
* OWASP Coreruleset team for the CRS and their help
* Ivan Ristić for creating ModSecurity

### Coraza on Twitter

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/mccutchen/go-httpbin/v2 v2.12.0
github.com/petar-dambovaliev/aho-corasick v0.0.0-20230725210150-fb29fc3c913e
github.com/tidwall/gjson v1.17.0
golang.org/x/net v0.18.0
golang.org/x/net v0.19.0
golang.org/x/sync v0.5.0
rsc.io/binaryregexp v0.2.0
)
Expand All @@ -33,6 +33,6 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/tools v0.6.0 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
Expand All @@ -47,8 +47,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
Expand Down
2 changes: 1 addition & 1 deletion http/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (i *rwInterceptor) Header() http.Header {
}

func (i *rwInterceptor) ReadFrom(r io.Reader) (n int64, err error) {
return io.Copy(i, r)
return io.Copy(struct{ io.Writer }{i}, r)
}

func (i *rwInterceptor) Flush() {
Expand Down
5 changes: 5 additions & 0 deletions http/interceptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ func TestReadFrom(t *testing.T) {
t.Errorf("unexpected error: %v", err)
}

_, err = rw.(io.ReaderFrom).ReadFrom(struct{ io.Reader }{bytes.NewBuffer([]byte("hello world"))})
if err != nil {
t.Errorf("unexpected error: %v", err)
}

err = responseProcessor(tx, req)
if err != nil {
t.Errorf("unexpected error: %v", err)
Expand Down
4 changes: 2 additions & 2 deletions internal/bodyprocessors/urlencoded.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

"github.com/corazawaf/coraza/v3/experimental/plugins/plugintypes"
"github.com/corazawaf/coraza/v3/internal/collections"
"github.com/corazawaf/coraza/v3/internal/url"
urlutil "github.com/corazawaf/coraza/v3/internal/url"
)

type urlencodedBodyProcessor struct {
Expand All @@ -23,7 +23,7 @@ func (*urlencodedBodyProcessor) ProcessRequest(reader io.Reader, v plugintypes.T
}

b := buf.String()
values := url.ParseQuery(b, '&')
values := urlutil.ParseQuery(b, '&')
argsCol := v.ArgsPost()
for k, vs := range values {
argsCol.Set(k, vs)
Expand Down
5 changes: 3 additions & 2 deletions internal/corazawaf/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ func (tx *Transaction) AddRequestHeader(key string, value string) {
}
case "cookie":
// Cookies use the same syntax as GET params but with semicolon (;) separator
values := urlutil.ParseQuery(value, ';')
// WithoutUnescape is used to avoid implicitly performing an URL decode on the cookies
values := urlutil.ParseQueryWithoutUnescape(value, ';')
for k, vr := range values {
for _, v := range vr {
tx.variables.requestCookies.Add(k, v)
Expand Down Expand Up @@ -535,7 +536,7 @@ func (tx *Transaction) GetStopWatch() string {
}

// GetField Retrieve data from collections applying exceptions
// In future releases we may remove de exceptions slice and
// In future releases we may remove the exceptions slice and
// make it easier to use
func (tx *Transaction) GetField(rv ruleVariableParams) []types.MatchData {
col := tx.Collection(rv.Variable)
Expand Down
29 changes: 29 additions & 0 deletions internal/corazawaf/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,35 @@ func TestHeaderSetters(t *testing.T) {
}
}

func TestCookiesNotUrldecoded(t *testing.T) {
waf := NewWAF()
tx := waf.NewTransaction()
fullCookie := "abc=%7Bd+e+f%7D;hij=%7Bklm%7D"
expectedUrlencodedAbcCookieValue := "%7Bd+e+f%7D"
unexpectedUrldencodedAbcCookieValue := "{d e f}"
tx.AddRequestHeader("cookie", fullCookie)
c := tx.variables.requestCookies.Get("abc")[0]
if c != expectedUrlencodedAbcCookieValue {
if c == unexpectedUrldencodedAbcCookieValue {
t.Errorf("failed to set cookie, unexpected urldecoding. Got: %q, expected: %q", unexpectedUrldencodedAbcCookieValue, expectedUrlencodedAbcCookieValue)
} else {
t.Errorf("failed to set cookie, got %q", c)
}
}
if tx.variables.requestHeaders.Get("cookie")[0] != fullCookie {
t.Errorf("failed to set request header, got: %q, expected: %q", tx.variables.requestHeaders.Get("cookie")[0], fullCookie)
}
if !utils.InSlice("cookie", collectionValues(t, tx.variables.requestHeadersNames)) {
t.Error("failed to set header name", collectionValues(t, tx.variables.requestHeadersNames))
}
if !utils.InSlice("abc", collectionValues(t, tx.variables.requestCookiesNames)) {
t.Error("failed to set cookie name")
}
if err := tx.Close(); err != nil {
t.Error(err)
}
}

func collectionValues(t *testing.T, col collection.Collection) []string {
t.Helper()
var values []string
Expand Down
55 changes: 32 additions & 23 deletions internal/url/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@ import (

// ParseQuery parses the URL-encoded query string and returns the corresponding map.
// It takes separators as parameter, for example: & or ; or &;
// It returns error if the query string is malformed.
func ParseQuery(query string, separator byte) map[string][]string {
return doParseQuery(query, separator, true)
}

// ParseQueryWithoutUnescape is a sibling of ParseQuery, but without performing URL unescape of keys and values.
func ParseQueryWithoutUnescape(query string, separator byte) map[string][]string {
return doParseQuery(query, separator, false)
}

func doParseQuery(query string, separator byte, urlUnescape bool) map[string][]string {
m := make(map[string][]string)
for query != "" {
key := query
Expand All @@ -26,15 +34,17 @@ func ParseQuery(query string, separator byte) map[string][]string {
if i := strings.IndexByte(key, '='); i >= 0 {
key, value = key[:i], key[i+1:]
}
key = QueryUnescape(key)
value = QueryUnescape(value)
if urlUnescape {
key = queryUnescape(key)
value = queryUnescape(value)
}
m[key] = append(m[key], value)
}
return m
}

// QueryUnescape is a non-strict version of net/url.QueryUnescape.
func QueryUnescape(input string) string {
// queryUnescape is a non-strict version of net/url.QueryUnescape.
func queryUnescape(input string) string {
ilen := len(input)
res := strings.Builder{}
res.Grow(ilen)
Expand All @@ -49,27 +59,13 @@ func QueryUnescape(input string) string {
res.WriteByte(ci)
continue
}
hi := input[i+1]
lo := input[i+2]
switch {
case hi >= '0' && hi <= '9':
hi -= '0'
case hi >= 'a' && hi <= 'f':
hi -= 'a' - 10
case hi >= 'A' && hi <= 'F':
hi -= 'A' - 10
default:
hi, ok := hexDigitToByte(input[i+1])
if !ok {
res.WriteByte(ci)
continue
}
switch {
case lo >= '0' && lo <= '9':
lo -= '0'
case lo >= 'a' && lo <= 'f':
lo -= 'a' - 10
case lo >= 'A' && lo <= 'F':
lo -= 'A' - 10
default:
lo, ok := hexDigitToByte(input[i+2])
if !ok {
res.WriteByte(ci)
continue
}
Expand All @@ -81,3 +77,16 @@ func QueryUnescape(input string) string {
}
return res.String()
}

func hexDigitToByte(digit byte) (byte, bool) {
switch {
case digit >= '0' && digit <= '9':
return digit - '0', true
case digit >= 'a' && digit <= 'f':
return digit - 'a' + 10, true
case digit >= 'A' && digit <= 'F':
return digit - 'A' + 10, true
default:
return 0, false
}
}
Loading

0 comments on commit f50dd17

Please sign in to comment.