value_protected >
Location Response Header
Also it is possible to protect additional resources like "input hidden fields " using the @rsub operator and Lua. We will show how can you use it later in this post.
For both methods content injection and stream variables must be enabled. Just add the following directives in your main configuration file:
SecContentInjection On
SecStreamOutBodyInspection On
SecDisableBackendCompression On
We are not presenting here all possibilities to use this new feature. Please check the ModSecurity reference manual available in www.modsecurity.org for additional information.
Method #1: Built-in Directives
For the first method we are going to use some new built-in Modsecurity directives. Below we have a few ones to protect "href" elements as an example.
For this demonstration we have a sample html file containing an href element
Figure 1. HTML file with unprotected href.
Let's turn on the authentication
SecEncryptionEngine On #Encryption engine is enabled
SecEncryptionParam "hmac" #Modsecurity will add a new hmac parameter
SecEncryptionKey "rand" "KeyOnly" #Modsecurity will for us a password
SecEncryptionMethodrx "HashHref" "product_id" #Let's protect href Fields that contains the word product_id
Figure 2. HTML file with protected href.
This is still not enough, attackers can still manipulate it. So we need to enforce the HMAC token. Enforcement means:
- HMAC token is present ?
- HMAC value is correct ?
To accomplish that we need to create a SecRule within a new operator, protecting the REQUEST_URI containing "product_id" word.
SecRule REQUEST_URI "@validateEncryption product_id" "phase:2,id:1001,deny"
Figure 3. Injecting code into protected href.
In this example we tried to inject a code into a parameter, since the HMAC didn't match ModSecurity blocked it.
Method #2 : Rsub operator + Lua
For the second method we are going to use the rsub ModSecurity operator + Lua scripts. This possibility was first demonstrated by Josh Zlatin. Some portions of the scripts were based on Josh's work.
The HMAC algorithm used into our scripts for this example is written in pure Lua and can downloaded from : http://regex.info/code/sha1.lua .
For this demonstration we have a sample html file containing a hidden field we want to protect its value
Figure 4. HTML file with unprotected hidden field.
Let's turn on the authentication
SecRule RESPONSE_BODY "name=\"price\" value=\"([\d]+)" "phase:4,chain,id:125,pass,capture,exec:/etc/modsecurity/CreateHMAC.lua"
SecRule STREAM_OUTPUT_BODY "@rsub s/name=\"price\" value=\"[\d]+\">/name=\"price\" value=\"%{TX.1}\"> |00|/d"
This is a chain of rules. The first rule will capture the value we want to protect, then store it into a TX variable used by the CreateHMAC script that compute the hmac value and store it into a new variable called TX.priceHmac. The second rule will replace the response body inserting a new hidden field called priceHmac.
Figure 5. HTML file with protected hidden field.
As described in method we also need to enforce the protected fields:
SecRule &ARGS:price "@ge 1" "phase:2,id:1000,t:none,chain,deny,log,msg:'Missing priceHmac parameter'"
SecRule &ARGS:priceHmac "!@ge 1"
SecRule &ARGS:price "@ge 1" "phase:2,id:1001,t:none,chain,nolog,exec:/etc/ modsecurity/VerifyHMAC.lua"
SecRule &ARGS:priceHmac "@ge 1"
SecRule &TX:block "@gt 0" "phase:2,id:1002,log,msg:'Invalid HMAC submitted',deny"
The first chain of rules consists of checking the presence of hmac hidden field if the price parameter is present. The second chain of rules checks the hmac value itself. The last rule work together with the last chain, if the hmac value doesn't match a variable TX.block is set and ModSecurity will deny the transaction.
Figure 6. Injecting code into protected hidden field.
HackMe Test
Now let's evaluate this feature against a real vulnerable web application. To do this we select a "HackMe" like app and setup ModSecurity in front of it as a proxy.
We executed some tests against www.webscantest.com using WebSecurity Scanner. The tests were executed twice, before enabling encryption features and after it.
Below we provide all configuration options and rules we used to protect the WebScanTest page:
SecEncryptionEngine On
SecEncryptionParam "hmac"
SecEncryptionKey "rand" "IpAddress"
SecEncryptionMethodpm "HashHref" "search_get_by_name search_get_by_id redir blockedbyns aboutyou2 search_by_id search_by_name rfplaces search_single_by_name search_double_by_name"
SecEncryptionMethodpm "HashFormAction" "aboutyou2 search_by_id search_by_name rfplaces search_single_by_name search_double_by_name"
SecRule REQUEST_URI "@validateEncryption search_get_by_name|search_get_by_id|redir|blockedbyns|aboutyou2|search_by_id|search_by_name|rfplaces|search_single_by_name|search_double_by_name" "phase:1,id:1,deny"
SecRule RESPONSE_BODY "name=\"id\" value=\"([\d]+)" "phase:4,chain,id:2,pass,capture,exec:/etc/apache2/modsecurity/CreateHMACid.lua" SecRule STREAM_OUTPUT_BODY "@rsub s/name=\"id\" value=\"[\d]+\">/name=\"id\" value=\"%{TX.1}\"> |00|/d"
SecRule &ARGS:id "@ge 1" "phase:2,id:3,t:none,chain,nolog,exec:/etc/apache2/modsecurity/VerifyHMACid.lua" SecRule &ARGS:idHmac "@ge 1"
SecRule &ARGS:id "@ge 1" "phase:2,id:4,t:none,chain,deny,log,msg:'Missing IdHmac parameter'" SecRule &ARGS:idHmac "!@ge 1"
SecRule &TX:block "@gt 0" "phase:2,id:5,log,msg:'Invalid HMAC submitted',deny,status:403"
SecRule RESPONSE_BODY "name=\"name\" value=\"([a-zA-Z]+)" "phase:4,chain,id:6,pass,capture,exec:/etc/apache2/modsecurity/CreateHMACname.lua" SecRule STREAM_OUTPUT_BODY "@rsub s/name=\"name\" value=\"[a-zA-Z]+\">/name=\"name\" value=\"%{TX.1}\"> |00|/d"
SecRule &ARGS:name "@ge 1" "phase:2,id:7,t:none,chain,nolog,exec:/etc/apache2/modsecurity/VerifyHMACname.lua" SecRule &ARGS:nameHmac "@ge 1"
SecRule &ARGS:name "@ge 1" "phase:2,id:8,t:none,chain,deny,log,msg:'Missing nameHmac parameter'" SecRule &ARGS:nameHmac "!@ge 1"
SecRule &TX:block "@gt 0" "phase:2,id:9,log,msg:'Invalid HMAC submitted',deny,status:403"
Figure 7. Result from security scanning.
We can conclude from the report that at least this new feature was be able to add a good layer of protection, with a few rules and directives.
We hadn't success to eliminate all XSS vulnerabilities because the case present into http://www.webscantest.com/crosstraining/aboutyou2.php url is an input type without pre-defined value:
Tell us a little about yourself
First Name:
Nick Name:
Last Name:
It's important to mention that no rules were enabled, except the encryption ones.
Protocol Limitations
The Idea described on this post has some limitations. There are situations you cannot use it for protection:
- Client-side generated resources (ie: URLs).
- Input fields or resources without a value.
- Still cannot protect cookies.
That being said, we do not expect that this feature alone will solve all problems because by design it cannot do that. However we know it can be used as a extra security layer to reduce the attack surface.
Conclusion
In this research, we developed a new security feature for ModSecurity Web Application Firewall, which the goal is to reduce the attack surface of a web application, by authentication and data checking integrity of HTML input vectors.
The implementation experience, described during this post, in a vulnerable environment demonstrated how simple it was to add this new feature to protect almost an entire web application with a few options and rules.
Finally, the performed security tests gave us an idea of how efficient the solution is, reducing the vulnerability detection and attacks, even if the attacker happens to use bypassing methods, like code obfuscation.
Resources
CreateHmac.lua
#!/usr/bin/lua
require "Hmac"
function main() local id = m.getvar("TX.1", "none"); local secret = "MyPassword"; hmac = hmac_sha1(secret,id); m.setvar("TX.priceHmac", hmac); return 1; end
VerifyHmac.lua
!/usr/bin/lua
require "Hmac" function main() local price = m.getvars("ARGS.price", "none"); local pricehmac = m.getvars("ARGS.priceHmac", "none"); local secret = "MyPassword";
if price ~= null then for i = 1, #price do for i = 1, #pricehmac do hmac = hmac_sha1(secret, price[i].value); m.log(3, "Price: " .. price[i].value .. " hmac: " .. hmac .." priceHmac: " .. pricehmac[i].value); if hmac ~= pricehmac[i].value then m.setvar("TX.block", 1); end end end end return 0;
CreateHMACid.lua
#!/usr/bin/lua
require "Hmac"
function main() local id = m.getvar("TX.1", "none"); local secret = "MyPassword"; hmac = hmac_sha1(secret,id); m.log(3, "ID: " .. id); m.setvar("TX.IdHmac", hmac); return 1; end
VerifyHMACid.lua
#!/usr/bin/lua
require "Hmac"
function main()
local id = m.getvars("ARGS.id", "none");
local idhmac = m.getvars("ARGS.idHmac", "none");
local secret = "MyPassword";
if id ~= null then
for i = 1, #id do
for i = 1, #idhmac do
hmac = hmac_sha1(secret, id[i].value);
m.log(3, "ID: " .. id[i].value .. " hmac: " .. hmac .. " IdHmac: " .. idhmac[i].value);
if hmac ~= idhmac[i].value then
m.setvar("TX.block", 1);
end
end
end
end
return 0;
end
CreateHMACname.lua
#!/usr/bin/lua
require "Hmac"
function main() local name = m.getvar("TX.1", "none"); local secret = "MyPassword"; hmac = hmac_sha1(secret,name); m.log(3, "Name: " .. name); m.setvar("TX.nameHmac", hmac); return 1 end
VerifyHMACname.lua
#!/usr/bin/lua
require "Hmac"
function main() local name = m.getvars("ARGS.name", "none"); local namehmac = m.getvars("ARGS.nameHmac", "none"); local secret = "MyPassword";
if name ~= null then for i = 1, #name do for i = 1, #namehmac do hmac = hmac_sha1(secret, name[i].value); m.log(3, "Name: " .. name[i].value .. " hmac: " .. hmac .." NameHmac: " .. namehmac[i].value); if hmac ~= namehmac[i].value then m.setvar("TX.block", 1); end end end end return 0; end