Reducing web application attack surface

For as long as companies rely on web sites to do business with their customers and partners, attackers will keep targeting these web applications searching for new (and old) vulnerabilities and trying to exploit them. Reducing the attack surface has been a good practice for quite some time, hardening applications and web servers usually accomplishes this. In this blogpost we are presenting a Hmac-Based protocol that can be implemented in order to reduce the attack surface with minimum impact to the users and zero changes on the web application itself. Basically, the proposed method consists in parsing HTTP Response data sent by the web application server and signing HTML elements of this response before it is sent back to the client browser, from that point on, the integrity of the communication between the client and the web application will be checked using the protected Request Header or Request Body fields. With this mechanism, no modifications are allowed during a new HTTP Request using the signed elements, reducing quite a number of known web application attacks.

The recent version of ModSecurity 2.7 has some interesting new features that allow people to protect initially a few web resources :

<a href=value_protected> </a>

<frame src=value_protected> </frame>

<iframe src=value_protected> </iframe>

<form action=value_protected> </form>

Location Response Header

Also it is possible to protect additional resources like "inputhidden 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

Blog_hmac_1Figure 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

Blog_hmac_2Figure 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"

Blog_hmac_3Figure 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

Blog_hmac_4Figure 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}\"><input type=\"hidden\" name=\"priceHmac\" value=\"%{TX.priceHmac}\">|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.

Blog_hmac_5Figure 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.

Blog_hmac_6Figure 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}\"><input type=\"hidden\" name=\"idHmac\" value=\"%{TX.IdHmac}\">|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}\"><input type=\"hidden\" name=\"nameHmac\" value=\"%{TX.nameHmac}\">|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"

Captura de tela 2012-07-20 às 3.12.12 PMFigure 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:

<form method="POST" name="xsswideopen2">

Tell us a little about yourself<br/><br/>

<input type="hidden" name="returnto" value="aboutyou2.php" />

First Name: <input name="fname"><br/>

Nick Name: <textarea name="nick"></textarea><br/>

Last Name: <input name="lname"><br/>

<input type="submit" name="submit" value="submit" />

</form>

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 desmonstrated 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

Trustwave reserves the right to review all comments in the discussion below. Please note that for security and other reasons, we may not approve comments containing links.