Web application security étude.
A few weeks back, jullrich posted "My Top 6 Honeytokens" to his web application security blog. I began thinking about how his suggestions could be implemented in mod_security -- not just because I have some weird caveman-ish wiring that leads me to think about technical solutions to other people's problems, but because governance is often a lot more effective if you can speak the language of the governed. In my case, the governed include our intrepid information technology group, the folks who run our web servers and whom I task with defending our systems against attack.
After enabling mod_security and mod_unique_id on my test server, and reading up on prior art (especially this blog post by Ryan Barnett), here is the Apache configuration (httpd.conf, etc.) I wound up creating:
The spider loop for #4 proved to be a tougher challenge. My initial foray, in php, looked like this:
While I was able to trick Paros and Burp into getting past the rewritten URL, it didn't yield the infinte loop I'd hoped to induce. At first I thought perhaps a more expensive tool would do better, but looking at the logs confirmed that the spidering feature was working as expected (loading up unique pages as it found them), as was the form (generating unique URLs after /secretadmin/).... as was mod_security, which was picking up on the presence of the ESESSIONID cookie it had previously created and denying the scanner, which was dutifully sending back the cookie it had previously received.
Looking back at the overall process, I had briefly considered using mod_rewrite for this entire exercise but ultimately decide mod_security was the better tool for this task, primarily because of the ease of getting into POST name/value pairs in mod_security; it can be done in mod_rewrite but requires far more code-level gymnastics (i.e. reading POST parameters and using them to fake a GET with the same values). I also toyed with the idea of a negative security model, as suggested in mod_security's documentation for the SESSION collection, but ultimately decided that it was both less predictable in terms of behavior and less scalable, since I had yet to see mod_security's session store shared between load-balanced server instances. Load-balanced servers were also one reason why I ultimately used a cookie to trigger the deny rule, rather than the individual signatures themselves: once set, cookies will be present for all requests coming from the attacker irrespective of what happened in the network to route the request to its final fulfillment host. That is, unless the attacker goes out of their way to ignore or reset my ESESSIONID cookie. Since the intent of this exercise was to detect attacker behavior (mod_security would, of course, have to be configured separately to protect any actual, vulnerable applications I might have been running), evasion turned out to be less of a concern this time.
All in all, a fun little diversion back to my caveman roots, an étude of sorts. Perhaps most of all, a reminder to appreciate the complex world the governed have to navigate every day.
After enabling mod_security and mod_unique_id on my test server, and reading up on prior art (especially this blog post by Ryan Barnett), here is the Apache configuration (httpd.conf, etc.) I wound up creating:
# honeytoken suggestions from https://blogs.sans.org/appsecstreetfighter/2009/06/04/my-top-6-honeytokens/
# the following rules should inherit SecDefaultAction from above
# 2. Add fake admin pages to robots.txt
SecRule REQUEST_FILENAME "^/secretadmin" \
"phase:3,id:10000002,t:none,setenv:modsec_honey=true,pass,log,auditlog,msg:'Honeypot: set cookie for fake robots.txt entry',setsid:%{ENV.UNIQUE_ID},setvar:session.score=+10"
# Fortunately, since I'm writing this for a blog post, I can put blog comments inside httpd.conf comments. More after the jump...
# 3. Add fake admin cookies
SecRule REQUEST_COOKIES:SiteAdmin "!^$" \
"phase:3,id:10000003,t:none,setenv:modsec_honey=true,pass,log,auditlog,msg:'Honeypot: set cookie for fake admin cookie',setsid:%{ENV.UNIQUE_ID},setvar:session.score=+10"
# 5. Add fake hidden passwords in HTML elements \
SecRule ARGS "^234kwj$" \
"phase:3,id:10000005,t:none,setenv:modsec_honey=true,pass,log,auditlog,msg:'Honeypot: set cookie for fake hidden password',setsid:%{ENV.UNIQUE_ID},setvar:session.score=+10"
# 6. "Hidden" form fields
SecRule ARGS:BackDoorDoNotUse "!Do_Not_Use" \
"phase:3,id:10000006,t:none,setenv:modsec_honey=true,pass,log,auditlog,msg:'Honeypot: set cookie for fake hidden field',setsid:%{ENV.UNIQUE_ID},setvar:session.score=+10"
# 4. Add "spider" loops
RewriteEngine On
RewriteRule /secretadmin/[0-9]+ /secretadmin.php [L]
# honeytoken work block: Header looks for modsec_honey env and sends cookie;
# SecRule looks for return cookie, blocks subsequent requests
Header set Set-Cookie "ESESSIONID=%{UNIQUE_ID}e; path=/;" env=modsec_honey
SecRule REQUEST_COOKIES:ESESSIONID !^$ \
"phase:3,t:none,deny,log,auditlog,msg:'Honeypot cookie found'"
The spider loop for #4 proved to be a tougher challenge. My initial foray, in php, looked like this:
<HTML><BODY>
Administration Form.
<!--
If SiteAdmin cookie is set to "affirmative", or if administrator password = 234kwj,
enable debugging and other neat features. Otherwise, implement
https://blogs.sans.org/appsecstreetfighter/2009/06/04 # 4.
-->
<FORM METHOD="POST" ACTION="/secretadmin/<?php mt_srand(); echo mt_rand()?>" >
<input style="display:none" name="BackDoorDoNotUse" value="Do_Not_Use">
User: <input name="user" value="" size="10">
Pass: <input type="password" name="pass" value="" size="10">
<input type="hidden" name="admin_ticket" value="<?php mt_srand(); echo mt_rand()?>" size="10">
<input type="submit" name="submit">
</FORM></BODY></HTML>
While I was able to trick Paros and Burp into getting past the rewritten URL, it didn't yield the infinte loop I'd hoped to induce. At first I thought perhaps a more expensive tool would do better, but looking at the logs confirmed that the spidering feature was working as expected (loading up unique pages as it found them), as was the form (generating unique URLs after /secretadmin/).... as was mod_security, which was picking up on the presence of the ESESSIONID cookie it had previously created and denying the scanner, which was dutifully sending back the cookie it had previously received.
Looking back at the overall process, I had briefly considered using mod_rewrite for this entire exercise but ultimately decide mod_security was the better tool for this task, primarily because of the ease of getting into POST name/value pairs in mod_security; it can be done in mod_rewrite but requires far more code-level gymnastics (i.e. reading POST parameters and using them to fake a GET with the same values). I also toyed with the idea of a negative security model, as suggested in mod_security's documentation for the SESSION collection, but ultimately decided that it was both less predictable in terms of behavior and less scalable, since I had yet to see mod_security's session store shared between load-balanced server instances. Load-balanced servers were also one reason why I ultimately used a cookie to trigger the deny rule, rather than the individual signatures themselves: once set, cookies will be present for all requests coming from the attacker irrespective of what happened in the network to route the request to its final fulfillment host. That is, unless the attacker goes out of their way to ignore or reset my ESESSIONID cookie. Since the intent of this exercise was to detect attacker behavior (mod_security would, of course, have to be configured separately to protect any actual, vulnerable applications I might have been running), evasion turned out to be less of a concern this time.
All in all, a fun little diversion back to my caveman roots, an étude of sorts. Perhaps most of all, a reminder to appreciate the complex world the governed have to navigate every day.
