-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 NEFARIOUSPLAN-CANONICAL-V1 {"body_md":"## The plugin maintains its own unauthenticated nonce mint\n\n`inc/dnd-upload-cf7.php`, lines 32 through 71, in the version that carries CVE-2026-5718:\n\n```php\nadd_action('wp_ajax__wpcf7_check_nonce', 'dnd_wpcf7_nonce_check');\nadd_action('wp_ajax_nopriv__wpcf7_check_nonce', 'dnd_wpcf7_nonce_check');\n\n// ...\n\nfunction dnd_wpcf7_nonce_check() {\n // Block curl request.\n if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'curl' ) !== false ) {\n wp_send_json_error('Request blocked: cURL access is forbidden.');\n }\n\n if( ! check_ajax_referer( 'dnd-cf7-security-nonce', false, false ) ){\n wp_send_json_success( wp_create_nonce( \"dnd-cf7-security-nonce\" ) );\n }\n}\n```\n\nThree behavioral lines describe the entire authorization model. The first asks whether the User-Agent header contains the four-character substring `curl`. If it does, the request is rejected with a JSON error. If it does not, control moves on.\n\nThe second calls `check_ajax_referer` with `$action = 'dnd-cf7-security-nonce'`, `$query_arg = false` (do not look for the nonce in a specific request key), and `$die = false` (do not call `wp_die` on failure). The function returns true if any nonce in the request validates against that action, false otherwise.\n\nThe third is the only branch most callers ever reach. When `check_ajax_referer` returns false, which it does for every fresh caller who has no nonce yet, the function calls `wp_create_nonce(\"dnd-cf7-security-nonce\")` and writes the result back to the caller as a JSON success payload. WordPress nonces are computed against the requesting user ID, the action name, and a 12-hour time slice. For an unauthenticated caller, the user ID is `0`. The returned token is valid for the next 24 hours for any caller whose user ID resolves to `0`, which is to say, every unauthenticated caller.\n\nA `requests.get` to `/wp-admin/admin-ajax.php?action=_wpcf7_check_nonce` with the default `python-requests/2.x` User-Agent produces this:\n\n```\n{\"success\":true,\"data\":\"a1b2c3d4e5\"}\n```\n\nThe string in `data` is a valid `dnd-cf7-security-nonce` for every other unauthenticated caller for the next 24 hours, against any caller whose User-Agent is not the literal string `curl`.\n\n## The upload handler verifies the nonce the mint just issued\n\n`dnd_upload_cf7_upload()`, the handler bound to `dnd_codedropz_upload`, opens with this:\n\n```php\nfunction dnd_upload_cf7_upload() {\n // ... read $_POST['form_id'], $_POST['upload_name'], settings ...\n\n // check and verify ajax request\n if( ! check_ajax_referer( 'dnd-cf7-security-nonce', 'security', false ) ) {\n wp_send_json_error('The security nonce is invalid or expired.');\n }\n // ...\n}\n```\n\n`check_ajax_referer` here verifies that the `security` POST field carries a nonce minted for `dnd-cf7-security-nonce`. The mint at line 62 issues exactly that nonce, against exactly that action, to exactly that population of callers (user ID `0`). The mint and the verifier are paired. They were written to be paired. The pairing is the bug.\n\n`current_user_can()` is not called anywhere in `dnd_upload_cf7_upload`. The function proceeds to a long chain of file extension checks, MIME checks, filename sanitization, and finally `move_uploaded_file` into the WordPress uploads directory under a folder the caller picks via `$_POST['upload_folder']`. Identity is never tested. The handler trusts that anyone who can present a valid nonce has been authorized somewhere upstream. Upstream is the line above. The end of that chain is the [unauth-write-to-execution-path](/patterns/unauth-write-to-execution-path) shape: an unauthenticated POST lands a file at a path the same Apache serves with mod_php. Every CVE in this plugin's series is a different way to keep `.php` in the filename through that chain.\n\n## What the 1.3.9.7 patch closes\n\nThe May 2026 patch tightens three file-validation paths in the upload handler. The first changes the blacklist setting from a replacement to a merge:\n\n```diff\n-$blacklist_types = dnd_cf7_not_allowed_ext();\n-if ( isset( $blacklist[\"$cf7_upload_name\"] ) && ! empty( $blacklist[\"$cf7_upload_name\"] ) ) {\n- $blacklist_types = explode( '|', $blacklist[\"$cf7_upload_name\"] );\n-}\n+$blacklist_types = dnd_cf7_not_allowed_ext();\n+if ( isset( $blacklist[\"$cf7_upload_name\"] ) && ! empty( $blacklist[\"$cf7_upload_name\"] ) ) {\n+ $blacklist_option = explode( '|', $blacklist[\"$cf7_upload_name\"] );\n+ $custom_blacklist = array_filter( array_map( 'trim', $blacklist_option ) );\n+ $blacklist_types = array_unique( array_merge( $blacklist_types, $custom_blacklist ) );\n+}\n```\n\nIn the pre-patch code, an admin who configured a per-form blacklist of `exe|scr` overwrote the default list (`'html', 'svg', 'phar', 'php', 'php3', 'php4', 'pht', 'php5', 'php7', 'php8', ...`). When the form's allowed types were set to `*`, an attacker uploading `shell.php` cleared the per-form blacklist (which did not contain `php`) and a second hardcoded fallback list at line 927 that only contained `phar, svg, php5, php7, php8`. The patch replaces the assignment with `array_merge`. The patch also expands the wildcard-mode fallback list to include `php, php3, php4, pht, phtml`:\n\n```diff\n-$not_allowed_ext = array( 'phar', 'svg', 'php5', 'php7', 'php8' );\n+$not_allowed_ext = array( 'phar', 'svg', 'php', 'php3','php4', 'pht', 'phtml', 'php5', 'php7', 'php8' );\n```\n\nThe CVE description says \"arbitrary file upload.\" The fix line, in 2026, adds the literal string `php` to the list of file extensions a hardcoded server-side filter for arbitrary file uploads considers dangerous.\n\nThe third change removes a conditional around the antiscript filename function:\n\n```diff\n-$ascii_name = dnd_cf7_remove_icons( $filename );\n-if ( dnd_cf7_check_ascii( $ascii_name ) ) {\n- $filename = wpcf7_antiscript_file_name( $ascii_name );\n-}\n+$filename = dnd_cf7_remove_icons( $filename );\n+$filename = wpcf7_antiscript_file_name( $filename );\n```\n\n`wpcf7_antiscript_file_name` is the WordPress Contact Form 7 helper that strips PHP-script-extension parts (`php`, `phtml`, `pl`, `py`, `rb`, `cgi`, `asp`, `aspx`) from multi-part filenames. A filename like `shell.php.pdf` becomes `shell.pdf`. Pre-patch, this function was called only when `dnd_cf7_check_ascii` returned true. A filename containing any non-ASCII byte caused the function to be skipped. Post-patch, it runs unconditionally.\n\n## The mint is unchanged\n\nThe eleven-line block at the top of `inc/dnd-upload-cf7.php` is byte-identical between version 1.3.9.6 (vulnerable) and 1.3.9.7 (patched):\n\n```php\nadd_action('wp_ajax__wpcf7_check_nonce', 'dnd_wpcf7_nonce_check');\nadd_action('wp_ajax_nopriv__wpcf7_check_nonce', 'dnd_wpcf7_nonce_check');\n\nfunction dnd_wpcf7_nonce_check() {\n if ( strpos( $_SERVER['HTTP_USER_AGENT'], 'curl' ) !== false ) {\n wp_send_json_error('Request blocked: cURL access is forbidden.');\n }\n if( ! check_ajax_referer( 'dnd-cf7-security-nonce', false, false ) ){\n wp_send_json_success( wp_create_nonce( \"dnd-cf7-security-nonce\" ) );\n }\n}\n```\n\nThe patch closes three file-validation gaps in the handler. The endpoint that mints the credential required to reach the handler is in the same source file, registered for unauthenticated callers, and the patch does not touch it. The plugin's idea of who is not authorized to receive a fresh upload nonce is `curl`.\n\n## This is the fifth file-upload security entry in this plugin's changelog\n\nThe `readme.txt` Changelog section, read in chronological order:\n\n```\n= 1.3.9.0 =\n- Security: Fixed security issues reported by Wordfence \"Remote Code Execution\n via PHAR File Upload if changing the filename something like poc.phar\".\n\n= 1.3.9.3 =\n- Security: Fixed an unauthenticated limited arbitrary file upload issue\n allowing .phar and .svg files when using blacklist mode with file types\n set to *. (by WordFence via andrea bocchetti)\n\n= 1.3.9.6 =\n- Security: Unauthenticated Arbitrary File Upload (Reported by Thomas Sanzey\n via WordFence) - user able to upload \"php5 - php8\" for non ascii filename\n by bypassing extensions present in the blacklists.\n\n= 1.3.9.7 =\n- Security Fix : Unauthenticated Arbitrary File Upload via Non-ASCII Filename\n Blacklist (Thanks to Leonid Semenenko via WordFence)\n```\n\nFour consecutive releases, four file-upload security entries, four different researchers. This is [design-debt-driver](/patterns/design-debt-driver) at the file-validation layer of one plugin. Each release closes the specific bypass the previous researcher reported. The next researcher finds the next bypass. The plugin has not changed its handler architecture (`wp_ajax_nopriv_` for the upload, `wp_ajax_nopriv_` for the nonce mint, User-Agent string for the mint's only check) across any of those releases.\n\nThe 1.3.9.6 entry and the 1.3.9.7 entry are about the same bug class. Sanzey's 1.3.9.6 fix added `dnd_cf7_remove_icons` to strip emoji, regional indicators, ZWJ, and skin-tone modifiers from the filename before the ASCII check, which addressed the specific bypass Sanzey reported (a ZWJ inside the filename made it non-ASCII and skipped the antiscript step). Sanzey's fix kept the ASCII gate. Semenenko's 1.3.9.7 fix removes the gate. Sanzey closed the door named in the report. Semenenko closed the doorway.\n\n## The exploit that ships with the CVE harvests forms before it harvests nonces\n\nThe `kyukazamiqq/cve-2026-5718` repository on GitHub is two Python scripts and seven markdown files. Four of the markdown files (`FORM_ID_VISUAL_GUIDE.md`, `HOW_FORM_ID_WORKS.md`, `README_FORM_ID_GUIDE.md`, `FORM_ID_QUICK_REFERENCE.txt`) are zero bytes on disk. They are filename placeholders for a documentation set the author named before they wrote.\n\nThe two scripts are `discover_forms.py` and `exploit_cve_2026_5718.py`. The first reads a target list, fetches each WordPress homepage, queries `/wp-json/contact-form-7/v1/forms`, parses `/sitemap.xml` for indexed pages (the first fifty), and tries seven common contact-page paths (`/contact`, `/contact-us`, `/get-in-touch`, `/inquire`, `/support`, `/feedback`, `/contact-form`). For each site that responds, it extracts Contact Form 7 form IDs via five separate regex patterns and writes them to `discovered_forms.txt` in `||file_upload` format.\n\nThe second reads from `targets.txt`, runs both file-validation bypass attempts against each site with a 0.5-second sleep between hosts, and writes confirmed hits to `cve_2026_5718_results.txt` in this format:\n\n```\nhttp://site1.com | Uploaded: shell-abc123.php | 2026-05-08 15:32:45\nhttp://site2.com | Uploaded: shell-def456.php | 2026-05-08 15:32:47\n```\n\nThree columns. URL, deployed-shell filename, timestamp. That is a campaign hit-list, not a debug log. The output banner the script prints declares \"CVE-2026-5718 Mass Exploitation Tool, Unauthenticated File Upload.\"\n\nThe README for the kit describes the workflow as `targets.txt > discover_forms.py > discovered_forms.txt > exploit_mass.py > successful.txt`. The chain is named in full. `exploit_mass.py` is not in the repository. The QUICK_START.md instructs readers to \"create exploit_mass.py for batch exploitation\" as the next step. The disclaimer reads \"EDUCATIONAL USE ONLY\" and lists \"vulnerability assessment on systems you own\" among the purposes the tool is provided for. A discovery harvester that walks WordPress REST APIs to enumerate Contact Form 7 form IDs and feeds them to a list-mode exploit is not a tool for assessing systems you own. This is the [disclaimer-wrapped campaign kit](/patterns/disclaimer-wrapped-campaign-kit) shape, identical to AnggaTechI's pix-woocommerce harvester documented in [the prior post](/posts/pix-woocommerce-nonce-is-not-auth) on this catalog.\n\nNeither script in the kit uses the `_wpcf7_check_nonce` mint. `exploit_cve_2026_5718.py` line 36 scrapes the nonce by regex from the homepage HTML:\n\n```python\nnonce_match = re.search(\n r'dnd-cf7-security-nonce[\"\\']?\\s*:\\s*[\"\\']([a-f0-9]+)[\"\\']',\n resp.text\n)\n```\n\nThe plugin's frontend script localizes a nonce inline on every page that loads its assets (`'ajax_nonce' => wp_create_nonce(\"dnd-cf7-security-nonce\")` at line 590 of the source). When the regex finds a hit on the homepage, the upload step has a nonce. When it does not, the script falls back to `self.nonce or ''` and the upload fails. The `_wpcf7_check_nonce` endpoint, which would issue a fresh nonce on demand against any target whose User-Agent is not the literal string `curl`, is not used. The author either did not read past line 33 of the plugin source, or read it and chose not to surface the simpler primitive in the kit.\n\n## The non-ASCII bypass the kit ships does not work against the source the kit names\n\n`exploit_cve_2026_5718.py` line 96 constructs its non-ASCII filename like this:\n\n```python\nfilename_bypass = f\"shell.php\\u200d.pdf\"\n```\n\nA zero-width joiner between `.php` and `.pdf`. The PoC's claim is that the ZWJ makes the filename non-ASCII, which causes `dnd_cf7_check_ascii` to return false, which skips `wpcf7_antiscript_file_name`. The filename therefore reaches disk with `.php.` in the middle.\n\n`dnd_cf7_remove_icons` at line 1015 of the plugin source strips `\\x{200D}`. The ZWJ is in the strip pattern, alongside the emoji block, regional indicators, variation selectors, and skin-tone modifiers. By the time the ASCII check at line 970 runs, the filename has been normalized to `shell.php.pdf`, which is ASCII. The antiscript function runs. The `php` part is removed. The file lands as `shell.pdf`.\n\nThe bypass that does work uses a non-ASCII codepoint outside the strip pattern. A Cyrillic `а` at U+0430, a Hebrew `א` at U+05D0, a Greek `α` at U+03B1, none of these appear in `dnd_cf7_remove_icons`'s pattern. A filename `shell.phpа.pdf` (Cyrillic `а` between `php` and `.pdf`) survives the strip step with the non-ASCII byte intact, fails the ASCII check, skips the antiscript function, and is written to disk with the `.php` substring preserved. On Apache configurations that route `.php` extension matches via `AddHandler` rather than ``, the embedded `.php` triggers PHP execution. The PoC author tested their bypass against a simulated version of the function (lines 39 to 46 of `cve_2026_5718_test.php`) that omitted the icon-strip step. They did not test against the source the CVE names.\n\n## The shape, three plugins on\n\nThe plugin's CVE record reads as a file-validation problem. The exploit kit reads as a file-validation problem. The patch reads as a file-validation problem. The handler that lets unauthenticated callers reach those file-validation paths is the same shape we have written about in [Pix for WooCommerce CVE-2026-3891](/posts/pix-woocommerce-nonce-is-not-auth), in [vczapi CVE-2026-1368](/posts/vczapi-cve-2026-1368-patch-is-two-characters), and in the [midi-Synth handler family for CVE-2026-1306](/posts/midi-synth-empty-api-key-is-the-payload). Every one of those plugins registers a privileged upload, settings-write, or token-issue handler under `wp_ajax_nopriv_`. Every one of them uses a nonce check as the gate. Every one of them ships an unauthenticated nonce source somewhere else: a companion `wp_ajax_nopriv_` AJAX action whose only purpose is to call `wp_create_nonce`, or a `wp_localize_script` block printing the nonce inline on a public page, or, in this plugin, a `wp_ajax_nopriv_` AJAX action whose only purpose is to call `wp_create_nonce` after asking whether the User-Agent contains `curl`.\n\nThis is [`nonce-is-not-auth`](/patterns/nonce-is-not-auth). A WordPress nonce is a CSRF token; valid-nonce means fresh-and-server-issued, not authenticated. The dnd-cf7 plugin's `dnd_wpcf7_nonce_check` is the explicit, named, unauthenticated nonce source the pattern's mechanism description warns against. The author wrote it as a feature. The feature is the bug.\n\nPoC: [kyukazamiqq/cve-2026-5718](https://github.com/kyukazamiqq/cve-2026-5718)","closing_line":"The plugin's nonce mint blocks one caller. Its name is `curl`.","hook_md":"CVE-2026-5718 names an unauthenticated arbitrary file upload in Drag and Drop Multiple File Upload for Contact Form 7, the codedropz plugin that adds drag-and-drop attachment fields to Contact Form 7 on hundreds of thousands of WordPress sites. The vulnerable upload handler is registered for both `wp_ajax_dnd_codedropz_upload` and `wp_ajax_nopriv_dnd_codedropz_upload`. Its only identity gate is `check_ajax_referer('dnd-cf7-security-nonce', 'security', false)`.\n\nThe same source file registers a second `nopriv` AJAX action whose entire purpose is to mint a `dnd-cf7-security-nonce` and return it as JSON. Before issuing the nonce, the function performs one inspection of the request. It checks whether the `User-Agent` header contains the substring `curl`.","post_id":257,"slug":"dnd-cf7-cve-2026-5718-nonce-mint-blocks-curl","title":"CVE-2026-5718: The Drag-and-Drop CF7 Nonce Mint Blocks One Caller, And Its Name Is curl","type":"initial","unreadable_sentence":"The plugin's nonce mint blocks one caller. Its name is `curl`."} -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCaqWGEgAKCRDeZjl4jgkQ JmvHAP9krfM+FDCZY1bTC95pCUpWXm7g/79ekSG4l+h/G+c52gEAhxzr7rIC8wQi 0ULsTsYXhq6PrZhlkH5slLbL7FIpIA0= =dO/N -----END PGP SIGNATURE-----