This site is the archived OWASP Foundation Wiki and is no longer accepting Account Requests.
To view the new OWASP Foundation website, please visit https://owasp.org
OWASP ModSecurity Securing WebGoat Section4 Sublesson 07.1
7. Concurrency -> 7.1 Thread Safety Problem
Lesson overview
Refer to the zip file with the WebGoat lesson overviews. See Appendix A for more information.
Lesson solution
Refer to the zip file with the WebGoat lesson solutions. See Appendix A for more information.
Strategy
This WebGoat lesson demonstrates a thread safety problem: when 2 users login almost simultaneously, the first user receives the second user's data.
To solve this lesson, at first an attempt to use ModSecurity rules was made, but figuring out how global persistence works and debugging got too time-consuming for an inexperienced ModSec user, so Lua was chosen.
When the first user logs in, a lock is set and the 2nd user is not allowed to log in until the first user's request returns.
Implementation
For this lesson solution, we will walk through the entire development process from a standalone Lua script on Windows to the ModSecurity solution on Linux.
Retrieved from the web proxy is the POST body parameters:
username=jeff&SUBMIT=Submit
A class and method is used for standalone development and testing that closely resembles the ModSecurity implementation of writing to the debug log file:
-- BEGIN: CUT HERE WHEN IN MODSEC -- for standalone testing, simulate ModSec functions -- have to replace ':' with '.'; e.g. 'm:log' with 'm.log' ModSec = {} function ModSec:new (o) o = o or {} setmetatable(o, self) self.__index = self return o end function ModSec:log (loglevel, msg) print(msg) end m = ModSec:new() -- END: CUT HERE WHEN IN MODSEC
So, 'm:log(9, msg2)' in the standalone version, which prints the debug messages to the command line, needs to be edited to 'm.log(9, msg2)' for writing to the ModSecurity debug log.
The project also standardized on debug log messaging:
msg0 = "Luascript (request-on_07-1.lua): " msg2 = ""
A debug log message would be assembled like this:
msg1 = string.format("Request is already pending; user name is '%s'", username) msg2 = msg0 .. msg1 m:log(9, msg2)
This ensures a consistent format and it makes searching through a debug log easier.
The solution is started using a standalone Lua script and simulating an HTTP request which is simple because we only have to run the script to increment the number of users logging in.
The format of the data file 'lesson07-1.data' is:
Entry{ requestpending = 0 }
The source code to process a request (minus the debug messages) from 'request-on_07-1.lua' is:
local username = "jsnow" function Entry (b) local ecount = b.requestpending if ecount >= 1 then msg1 = string.format("Request is already pending; user name is '%s'", username) retval = msg1 end -- increment count and build string to write back to file ecount = ecount + 1 outstr = string.format("Entry{\n requestpending = %d\n}\n\n", ecount) end dofile(datafile)
The 'dofile' function only makes one pass because there is only one entry in the data file.
Test the standalone program (in this case on Windows) by running from the command line:
lua request-on_07-1.lua
If the number of users logging in is greater or equal to 1, the function returns with a non-nil value, which causes a match for the ModSecurity rule. When the count is incremented, the data file with the new count is written back to disk:
local fh2 = io.open(datafile, "w+") fh2:write(outstr) fh2:flush() fh2:close()
Each time the program is run, the number of logged in users should increment by 1 in the data file. Once this works as intended, modify the Lua program and integrate it into ModSecurity on Linux.
To convert to ModSecurity, we only have to: 1. Cut out the log class and method 2. Change m:log to m.log 3. replace the hard-coded 'username' with:
local username = m.getvar("ARGS_POST.username", "none")
4. Remove the 'main()' call at the end of the file.
It's that simple!
The Modsecurity rules are in the file 'rulefile_07-1_thread-safety.conf'.
For the request:
SecRule ARGS:menu "!@eq 800" "phase:2,t:none,skip:4" SecRule &ARGS_POST:SUBMIT "@eq 0" "nolog,skip:3" SecRule &ARGS_POST:username "@eq 0" "nolog,skip:2" # action is triggered if script returns non-nil value SecRuleScript "/etc/modsecurity/data/request-on_07-1.lua" "phase:2,t:none,log,auditlog,deny,severity:3,msg:'Luascript: Concurrency -> Thread Safety: request is pending',tag:'CONCURRENCY',redirect:/_error_pages_/lesson07-1.html" SecAction "phase:2,allow:request,t:none,log,auditlog,msg:'Luascript: Concurrency -> Thread Safety: request is NOT pending'"
For the HTTP response, the user count is decremented each time so running another Lua script, 'request-off_07-1.lua', simulates that in standalone mode. You can view the source - it's exactly the same as above except it decrements the count instead of incrementing the user count.
Once the Lua program is working in standalone mode, convert it and move it over to ModSecurity.
The ModSecurity rules are:
SecRule TX:MENU "!@eq 800" "phase:4,t:none,pass,skip:1" # this will decrement the request-in-progress for any request in Lesson 7 but will not go less than zero SecRuleScript "/etc/modsecurity/data/request-off_07-1.lua" \ "phase:4,t:none,log,auditlog,allow,msg:'Luascript: Concurrency -> \ Thread Safety: in RESPONSE; decrementing requests by 1'"
The best way to test the Lua scripts when integrating into ModSecurity is to first comment out the response rules, then when the request rules are working, do the opposite; then enable both sets once the request set of rules are working properly.
When enabled and doing the WebGoat sublesson, Dave logs in first: (screenshot lesson07-1_dave.jpg)
Quickly followed by Jeff, who gets blocked and receives this message: (screenshot lesson07-1_jeff.jpg)
Comments
- This solution shows the process of developing a standalone Lua script and then integrating it with ModSecurity.