PostgreSQL, PL/Perl, and CVE-2016-1238 (Part 2)

Datetime:2016-08-23 02:18:50          Topic: Perl  PostgreSQL           Share

Welcome back to our discussion of PostreSQL, PL/PerlU, and avoiding writing yourselves into corners.Last time, we covered some of the basics of user-defined functions in PostreSQL. Now, we're seeing where they can cause problems.

What Gets You Into Trouble?

To have an exploit in PL/PerlU code, several things have to happen:

  1. Either a previous exploit must exist which has written files to the PostgreSQL data directory, or one must change directories.
  2. After changing directories, a vulnerable module must be loaded while in a directory the attacker has write access to.

It is possible, though unlikely, for the first to occur behind your back. But people ask me all the time how to send email from the database backend, so you cannot always guarantee people are thinking through the consequences of their actions.

So vulnerabilities can occur when people are thinking tactically and coding in undisciplined ways. This is true everywhere, but here the problems are especially subtle, and they are as dangerous as they are rare.

An Attack Scenario

So suppose Company A receives a text file via an anonymous FTP drop box in X12 format (the specific format is not relevant, just that it comes in with contents and a file name that are specified by the external user). A complex format like X12 means they are highly unlikely to do the parsing themselves, so they implement a module loader in PostgreSQL. The module loader operates on a file handle as follows.

On import, the module loader changes directories to the incoming directory. In the actual call to get_handle, it opens a file handle, and creates an iterator based on that and returns it. Nobody notices that the directory is not changed back because this is then loaded into the DB, and no other file access is done here. I have seen design problems of this magnitude go undetected for an extended period, so coding defensively means assuming they will exist.

Now, next, this is re-implemented in the database as such:

CREATE OR REPLACE FUNCTION load_incoming_file(fiilename text) 
RETURNS int
language plperlu as
$$
use CompanyFileLoader '/var/incoming'; # oops, we left the data directory
use CompanyConfig 'our_format'; # oops optional dependency that falls back to .
                                                    # in terms of exploit, the rest is irrelevant
                                                    # for a proof of concept you could just return 1 here
use strict;
use warnings;

my $filename = shift;
my $records = CompanyFileLoader->get_handle($filename);

while ($r = $records->next){
    # logic to insert into the db omitted
}

 return $records->count;

 $$;

The relevant poart of CompanyFileLoader.pm is (the rest could be replaced with stubs for a proof of concept):

package CompanyFileLoader;
use strict;
use warnings;

my @dirstack;
sub import {
    my $dir = pop;
    push @dirstack, $dir;
    chdir $dir;
}

Now, in a real-world published module, this would cause problems but in a company's internal operations it might not pose discovered problems in a timely fashion.

The relevant part of CompanyConfig is:

package CompanyConfig;

use strict;

use warnings;

eval { require 'SupplementalConfig' };  

Now, if a SupplementalConfig.pm is loaded into the same directory as the text files, it will get loaded and run as part of the PL/PerlU function.

Now to exploit this, someone with knowledge of the system has to place this in that directory. It could be someone internal due to failure to secure the inbound web service properly. It could be someone external who has inside knowledge (a former employee for example). Or a more standard exploit could be tried on the basis that some other shared module might have a shared dependency.

The level of inside knowledge required to pull that off is large, but the consequences are actually pretty dire. When loaded, the Perl module interacts with the database with the same permissions as the rest of the function, but it also has filesystem access as the database server. This means it could do any of the following:

  1. Write Perl modules to exploit this vulnerability in other contexts to the Pg data directory.
  2. Delete or corrupt database files.
  3. possibly alter log files depending on setup.
  4. Many other really bad things.

These risks are inherent with the use of untrusted languages, that you can write vulnerable code and introduce security problems into your database. This is one example of that and I think the PostgreSQL team has done an extremely good job of making the platform secure.

Disciplined Coding to Prevent Problems

The danger can be effectively prevented by following some basic rules:

All user defined functions and stored procedures in PL/PerlU should include the line:

no lib '.';

It is possible that modules could add this back in behind your back, but for published modules, this is extremely unlikely. So local development projects should not use lib '.' in order to prevent this.

Secondly, never use chdir in a PL/Perl function. Remember, you can always do file operations with absolute paths. Without chdir, no initial exploit against the current working directory is possible through PL/PerlU. Use of chdir circumvents important safety protections in PostgreSQL.

Thirdly it is important that one sticks to well-maintained modules. Dangling chdirs in a module's load logic are far more likely to be found and fixed when lots of other people are using a module, and a dangling chdir is a near-requirement to accidental vulnerability. For internal modules, they need to be reviewed both for optional dependencies usage and dangling chdir's in the module load logic.





About List