Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions example/plugins/lua-api/modsecurity/C.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.


-- module containing common C functions to be used

local ffi = require("ffi")

ffi.cdef[[
void free(void *ptr);
]]

return ffi.C
70 changes: 70 additions & 0 deletions example/plugins/lua-api/modsecurity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Integrating ATS with ModSecurity V3 using LuaJIT and FFI
====

Opensource WAF for [Apache Traffic Server](http://trafficserver.apache.org/).

Requirement
====
- ModSecurity v3.0.4
- ATS 8.0.8

How to Use
====
- Copy all lua files to `/usr/local/var/lua`
- Put the example modsecurity rule file (`example.conf`) to `/usr/local/var/modsecurity` , readable by the ATS process
- Add a line in `/usr/local/etc/trafficserver/plugin.config` and restart ats

```
tslua.so --enable-reload /usr/local/var/lua/ats-luajit-modsecurity.lua /usr/local/var/modsecurity/example.conf
```

- Changes can be made to example.conf and can be reloaded without restarting ATS. Just follow instructions [here](https://docs.trafficserver.apache.org/en/latest/appendices/command-line/traffic_ctl.en.html#cmdoption-traffic-ctl-config-arg-reload)

Contents/Rules inside example.conf
====
- deny any request with query parameter of `testparam=test2` with a 403 status response
- return any request with query parameter of `testparam=test1` with 301 redirect response to https://www.yahoo.com/
- override any response with header `test` equal to `1` with a 403 status response
- override any response with header `test` equal to `2` with a 301 redirect response to https://www.yahoo.com/
- write debug log out to `/tmp/debug.log`

Working with CRS
====
- Go to [here](https://github.com/SpiderLabs/owasp-modsecurity-crs) and get release v3.2.0
- Uncompress the contents and copy `crs-setup.conf.example` to `/usr/local/var/modsecurity` and rename it to `crs-setup.conf`
- Copy all files in `rules` directory to `/usr/local/var/modsecurity/rules`
- Copy `owasp.conf` in this repository to `/usr/local/var/modsecurity`
- Change `/usr/local/etc/trafficserver/plugin.config` to add the following line and restart ats

```
tslua.so --enable-reload /usr/local/var/lua/ats-luajit-modsecurity.lua /usr/local/var/modsecurity/owasp.conf
```

- The following example curl command against your server should get a status 403 Forbidden response

```
curl -v -H "User-Agent: Nikto" 'http://<your server>/'
```

Extra Notes with CRS
====
- Please check out this [link](https://github.com/SpiderLabs/ModSecurity/issues/1734) for performance related information
- To turn on debugging, you can uncomment the following inside `owasp.conf`

```
SecDebugLog /tmp/debug.log
SecDebugLogLevel 9
```

- Rule ID 910100 in REQUEST-910-IP-REPUTATION.conf in `rules` directory requires GeoIP and have to be commented out if you do not built the modsecurity library with it.
- We use `SecRuleRemoveById` inside `owasp.conf` to remove rules checking for request and response body. This trick can be used to remove other rules that does not apply well in some situations


TODOs/Limitations
====
- No support for `REQUEST_BODY` examination (We need to buffer the request body for examination first before we send to origin.)
- No support for `RESPONSE BODY` examination (We need to uncompress the contents first if they are gzipped. And that will be expensive operation for proxy)
- How does this work with the lua engine inside ModSecurity V3?
- Unit Test using busted framework
- More functional testing needed
- Performance testing - impact to latency and capacity
222 changes: 222 additions & 0 deletions example/plugins/lua-api/modsecurity/ats-luajit-modsecurity.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
-- Licensed to the Apache Software Foundation (ASF) under one
-- or more contributor license agreements. See the NOTICE file
-- distributed with this work for additional information
-- regarding copyright ownership. The ASF licenses this file
-- to you under the Apache License, Version 2.0 (the
-- "License"); you may not use this file except in compliance
-- with the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.


ts.add_package_path('/usr/local/var/lua/?.lua')

local msc = require("msc")
local msc_config = require("msc_config")
local ffi = require("ffi")
local C = require("C")

local mst = msc.msc_init()
msc.msc_set_connector_info(mst, "ModSecurity-ats")

-- Initialization. Load the modsecurity configuration passed to this lua module
function __init__(argtb)
if (#argtb) < 1 then
ts.error("No ModSecurity Conf is given")
return -1
end

msc_config.rulesfile = argtb[1]
ts.debug("ModSecurity Conf file is " .. msc_config.rulesfile)

msc_config.rules = msc.msc_create_rules_set()
local error = ffi.new("const char *[1]")
local result = msc.msc_rules_add_file(msc_config.rules, msc_config.rulesfile, error)
if(result < 0) then
ts.error("Problems loading the rules: ".. ffi.string(error[0]))
msc.msc_rules_cleanup(msc_config.rules)
msc_config.rules = nil
return -1
end

end

-- Reload modsecurity configuration. Trigger by "traffic_ctl config reload"
function __reload__()
ts.debug("Reloading ModSecurity Conf: " .. msc_config.rulesfile)

newrules = msc.msc_create_rules_set()
local error = ffi.new("const char *[1]")
local result = msc.msc_rules_add_file(newrules, msc_config.rulesfile, error)
if(result < 0) then
ts.error("Problems loading the rules during reload: ".. ffi.string(error[0]))

msc.msc_rules_cleanup(newrules)
newrules = nil
else
-- TODO: we are not doing clean up on the old rules and thus leaking resources here
msc_config.rules = newrules
end

end

-- Entry point function run for each incoming request
function do_global_read_request()
if(msc_config.rules == nil) then
ts.debug("No rules loaded. Thus there is no processing done")
return 0
end
local txn = msc.msc_new_transaction(mst, msc_config.rules ,nil)

-- processing for the connection information
local client_ip, client_port, client_ip_family = ts.client_request.client_addr.get_addr()
local incoming_port = ts.client_request.client_addr.get_incoming_port()
msc.msc_process_connection(txn, client_ip, client_port, "127.0.0.1", incoming_port)

-- processing for the uri information
local uri = ts.client_request.get_uri()
local query_params = ts.client_request.get_uri_args() or ''
if (query_params ~= '') then
uri = uri .. '?' .. query_params
end
msc.msc_process_uri(txn, uri, ts.client_request.get_method(), ts.client_request.get_version())

-- processing for the request headers
local hdrs = ts.client_request.get_headers()
for k, v in pairs(hdrs) do
msc.msc_add_request_header(txn, k, v)
end
msc.msc_process_request_headers(txn)
msc.msc_process_request_body(txn)
ts.debug("done with processing request")

-- detect if intervention is needed
local iv = ffi.new("ModSecurityIntervention")
iv.status = 200
iv.log = nil
iv.url = nil
iv.disruptive = 0
local iv_res = msc.msc_intervention(txn, iv)
ts.debug("done with intervention ".. iv_res .. ' with status ' .. iv.status )

if(iv.log ~= nil) then
ts.debug("Intervention log: " .. ffi.string(iv.log))
C.free(iv.log)
end

-- if found an intervention url, trigger handler when sending response to client
if(iv.url ~= nil) then
ts.ctx['url'] = ffi.string(iv.url)
ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, send_response)
C.free(iv.url)
end

-- intervention is needed if status is not 200
if (iv.status ~= 200) then
ts.http.set_resp(iv.status)
msc.msc_process_logging(txn)
msc.msc_transaction_cleanup(txn)
ts.debug("done with setting custom response")
return 0
end

-- storing modsecurity object in context
ts.ctx["mst"] = txn
ts.debug("done with setting context")

-- trigger handler to run when response is received
ts.hook(TS_LUA_HOOK_READ_RESPONSE_HDR, read_response)

return 0
end

-- function run when response is received from origin
function read_response()
-- retriving modsecurity object
local txn = ts.ctx["mst"]

if(txn == nil) then
ts.error("no transaction object")
return 0
end

-- processing for the response headers
local hdrs = ts.server_response.get_headers()
for k, v in pairs(hdrs) do
msc.msc_add_response_header(txn, k, v)
end
msc.msc_process_response_headers(txn, ts.server_response.get_status(), "HTTP/"..ts.server_response.get_version())
msc.msc_process_response_body(txn)
ts.debug("done with processing response")

-- determine if intervention is needed
local iv = ffi.new("ModSecurityIntervention")
iv.status = 200
iv.log = nil
iv.url = nil
iv.disruptive = 0
local iv_res = msc.msc_intervention(txn, iv)
ts.debug("done with intervention ".. iv_res .. ' with status ' .. iv.status )

if(iv.log ~= nil) then
ts.debug("Intervention log: " .. ffi.string(iv.log))
C.free(iv.log)
end

-- if found an intervention url, trigger handler when sending response to client
ts.ctx['url'] = ''
if(iv.url ~= nil) then
ts.ctx['url'] = ffi.string(iv.url)
C.free(iv.url)
end

-- intervention needed when status is not 200
ts.ctx['status'] = nil
if (iv.status ~= 200) then
ts.ctx['status'] = iv.status
end

-- response needs to be modified?
if(ts.ctx['url'] ~= '' or ts.ctx['status'] ~= nil) then
ts.hook(TS_LUA_HOOK_SEND_RESPONSE_HDR, send_response)
end

-- we need to return -1 for lua to reset response body with an error body
if(ts.ctx['status'] ~= nil) then
ts.ctx["mst"] = nil
msc.msc_process_logging(txn)
msc.msc_transaction_cleanup(txn)
ts.debug("done with cleaning up context and return error response")
return -1
end

-- cleaning up
ts.ctx["mst"] = nil
msc.msc_process_logging(txn)
msc.msc_transaction_cleanup(txn)
ts.debug("done with cleaning up context")

return 0
end

-- function run when sending response to client
function send_response()
-- retrieve intervention url and add it as "Location" header on response to client
local location = ts.ctx['url']
if (location ~= '') then
ts.debug('location: ' .. location)
ts.client_response.header['Location'] = location
end

-- retrieve status and reset the response with it
local status = ts.ctx['status']
if (status ~= nil) then
ts.client_response.set_error_resp(status, 'Contents Reset by ModSecurity\n')
end
end
28 changes: 28 additions & 0 deletions example/plugins/lua-api/modsecurity/example.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


# Example Modsecurity Configuration file. See README.md for details

SecRuleEngine On

SecDebugLog /tmp/debug.log
SecDebugLogLevel 9

SecRule ARGS:testparam "@contains test2" "id:1234,deny,status:403"
SecRule ARGS:testparam "@contains test1" "id:1235,redirect:https://www.yahoo.com/"
SecRule RESPONSE_HEADERS:test "@contains 1" "id:1236,phase:3,deny,status:403"
SecRule RESPONSE_HEADERS:test "@contains 2" "id:1237,phase:3,redirect:https://www.yahoo.com/"
Loading