mirror of
https://gh.wpcy.net/https://github.com/JulesJujuu/wpaudit.git
synced 2026-04-17 08:42:18 +08:00
152 lines
9.4 KiB
Python
152 lines
9.4 KiB
Python
# Module for WordPress Cron Job (wp-cron.php) Analysis
|
|
import requests
|
|
from urllib.parse import urljoin
|
|
from .utils import make_request # Assuming a utility for requests exists
|
|
|
|
def analyze_cron(state, config, target_url):
|
|
"""
|
|
Analyzes the accessibility and potential issues related to wp-cron.php.
|
|
Updates the state with findings.
|
|
"""
|
|
module_key = "wp_analyzer"
|
|
findings_key = "cron_analysis"
|
|
|
|
all_wp_analyzer_findings = state.get_module_findings(module_key, {})
|
|
findings = all_wp_analyzer_findings.get(findings_key, {})
|
|
if not findings: # Initialize with default structure
|
|
findings = {
|
|
"status": "Not Run",
|
|
"details": "Analyzing wp-cron.php accessibility and configuration hints.",
|
|
"wp_cron_url": None,
|
|
"wp_cron_accessible": None,
|
|
"wp_cron_status_code": None,
|
|
"potential_dos_risk": False,
|
|
"disable_wp_cron_hint": None,
|
|
"alternate_wp_cron_info": "Not actively checked. If used, ensure it's intentional.",
|
|
"x_robots_tag_present": None
|
|
}
|
|
|
|
findings["status"] = "Running"
|
|
# Ensure all keys are present if findings dict was pre-existing but incomplete
|
|
default_sub_keys = ["wp_cron_url", "wp_cron_accessible", "wp_cron_status_code",
|
|
"potential_dos_risk", "disable_wp_cron_hint",
|
|
"alternate_wp_cron_info", "x_robots_tag_present"]
|
|
for key in default_sub_keys:
|
|
if key not in findings:
|
|
# Set a sensible default based on the original structure
|
|
if key in ["potential_dos_risk"]:
|
|
findings[key] = False
|
|
else:
|
|
findings[key] = None if key not in ["alternate_wp_cron_info"] else "Not actively checked. If used, ensure it's intentional."
|
|
|
|
|
|
all_wp_analyzer_findings[findings_key] = findings
|
|
state.update_module_findings(module_key, all_wp_analyzer_findings) # Save initial state
|
|
|
|
print(" [i] Analyzing WordPress Cron (wp-cron.php)...")
|
|
|
|
cron_url = urljoin(target_url, 'wp-cron.php')
|
|
findings["wp_cron_url"] = cron_url
|
|
print(f" Checking cron path: {cron_url}")
|
|
|
|
response_text_snippet = ""
|
|
response_headers = {}
|
|
|
|
try:
|
|
# Prefer GET for wp-cron.php as HEAD might not always behave identically or reveal headers like X-Robots-Tag
|
|
# Adding a doing_wp_cron query param can sometimes elicit a more standard response or bypass caches.
|
|
cron_test_url = f"{cron_url}?doing_wp_cron&{requests.utils.quote(str(requests.compat.urlparse(target_url).netloc))}" # Mimic WP's own call
|
|
|
|
response = make_request(cron_test_url, config, method="GET", timeout=15)
|
|
|
|
if response:
|
|
findings["wp_cron_status_code"] = response.status_code
|
|
response_headers = response.headers
|
|
response_text_snippet = response.text[:250] if response.text else "" # Get a snippet
|
|
|
|
if response.status_code == 404:
|
|
findings["wp_cron_accessible"] = False
|
|
findings["details"] = "wp-cron.php returned 404 Not Found. It might be deleted or blocked at the server level."
|
|
print(f" [-] {cron_url} not found (404).")
|
|
elif response.status_code == 403:
|
|
findings["wp_cron_accessible"] = False # Blocked
|
|
findings["details"] = f"wp-cron.php is forbidden (Status: {response.status_code}). Access is likely blocked."
|
|
print(f" [+] {cron_url} access forbidden (403). Good if server cron is used.")
|
|
elif 200 <= response.status_code < 300: # Typically 200
|
|
findings["wp_cron_accessible"] = True
|
|
findings["potential_dos_risk"] = True # Always a potential if directly accessible
|
|
findings["details"] = f"wp-cron.php is accessible (Status: {response.status_code})."
|
|
print(f" [+] {cron_url} is accessible (Status: {response.status_code}).")
|
|
|
|
# Heuristic for DISABLE_WP_CRON:
|
|
# If wp-cron.php returns 200 OK but is completely empty or very minimal,
|
|
# it *might* hint at DISABLE_WP_CRON, but this is weak.
|
|
# WordPress core wp-cron.php, even when DISABLE_WP_CRON is true, will still execute
|
|
# and exit early. It usually returns a blank page.
|
|
# If it's accessible, the main concern is DoS regardless of DISABLE_WP_CRON.
|
|
# The constant itself prevents WP from *spawning* cron, not direct access.
|
|
if not response.text or len(response.text.strip()) < 10: # Arbitrary small length
|
|
findings["disable_wp_cron_hint"] = "Possible (Minimal Response)"
|
|
findings["details"] += " Response was minimal, DISABLE_WP_CRON might be true, but file is still accessible."
|
|
print(" [i] Response from wp-cron.php was minimal. DISABLE_WP_CRON might be true, but file remains accessible.")
|
|
else:
|
|
findings["disable_wp_cron_hint"] = "Not Evident from Response"
|
|
|
|
state.add_remediation_suggestion("wp_cron_dos_risk_v2", { # Updated key
|
|
"source": "WP Analyzer (Cron Check)",
|
|
"description": f"wp-cron.php is publicly accessible ({cron_url}). While necessary for scheduled tasks if server-side cron isn't used, it can be abused for DoS attacks by overwhelming the server with requests.",
|
|
"severity": "Low",
|
|
"remediation": "If using a real server-side cron job, define('DISABLE_WP_CRON', true); in wp-config.php AND block direct web access to wp-cron.php (e.g., via .htaccess/Nginx rules). If relying on built-in WP-Cron, ensure adequate server resources or consider solutions like Action Scheduler for more robust background processing, and implement rate limiting if possible."
|
|
})
|
|
elif 500 <= response.status_code < 600:
|
|
findings["wp_cron_accessible"] = True # It exists but is erroring
|
|
findings["potential_dos_risk"] = True # Can still be hit repeatedly
|
|
findings["details"] = f"wp-cron.php returned a server error (Status: {response.status_code}). It exists but is misconfigured or causing load."
|
|
print(f" [!] {cron_url} returned server error {response.status_code}. Potential issue or DoS vector.")
|
|
else: # Other codes (e.g. 401, 405)
|
|
findings["wp_cron_accessible"] = True # Exists, but access is modified/restricted
|
|
findings["potential_dos_risk"] = True # Still potentially hittable
|
|
findings["details"] = f"wp-cron.php returned status {response.status_code}. Access is modified."
|
|
print(f" [?] {cron_url} returned status {response.status_code}. Access seems modified (e.g., auth, method block).")
|
|
else:
|
|
findings["wp_cron_accessible"] = "Error (No Response)"
|
|
findings["details"] = "Request to wp-cron.php failed (no response object)."
|
|
print(f" [-] Request failed for {cron_url} (no response object).")
|
|
|
|
# Check X-Robots-Tag
|
|
x_robots_tag = response_headers.get('X-Robots-Tag', response_headers.get('x-robots-tag'))
|
|
if x_robots_tag:
|
|
if "noindex" in x_robots_tag.lower():
|
|
findings["x_robots_tag_present"] = True
|
|
print(f" [+] X-Robots-Tag: '{x_robots_tag}' found (good).")
|
|
else:
|
|
findings["x_robots_tag_present"] = "Present but not 'noindex'"
|
|
print(f" [?] X-Robots-Tag: '{x_robots_tag}' found but doesn't explicitly contain 'noindex'.")
|
|
elif findings["wp_cron_accessible"] is True: # Only relevant if accessible
|
|
findings["x_robots_tag_present"] = False
|
|
print(" [-] X-Robots-Tag with 'noindex' not found for wp-cron.php.")
|
|
state.add_remediation_suggestion("wp_cron_xrobots", {
|
|
"source": "WP Analyzer (Cron Check)",
|
|
"description": "The X-Robots-Tag: noindex, nofollow header was not detected for wp-cron.php.",
|
|
"severity": "Info",
|
|
"remediation": "Consider adding an X-Robots-Tag HTTP header with 'noindex, nofollow' for wp-cron.php via server configuration to prevent search engines from attempting to index it."
|
|
})
|
|
else:
|
|
findings["x_robots_tag_present"] = "Not Applicable (File Not Accessible)"
|
|
|
|
|
|
except requests.exceptions.RequestException as e:
|
|
print(f" [-] Error during wp-cron.php check: {e}")
|
|
findings["wp_cron_accessible"] = "Error (Request Exception)"
|
|
findings["details"] = f"Request exception for wp-cron.php: {type(e).__name__}"
|
|
|
|
# Add informational note about ALTERNATE_WP_CRON
|
|
if findings["alternate_wp_cron_info"] == "Not actively checked. If used, ensure it's intentional.": # Only if not overridden
|
|
findings["alternate_wp_cron_info"] = "ALTERNATE_WP_CRON is a WordPress constant that, if true, uses a redirect-based mechanism to trigger cron. This is generally less reliable and not commonly used. Its status cannot be easily determined remotely."
|
|
|
|
|
|
findings["status"] = "Completed"
|
|
all_wp_analyzer_findings = state.get_module_findings(module_key, {}) # Re-fetch
|
|
all_wp_analyzer_findings[findings_key] = findings
|
|
state.update_module_findings(module_key, all_wp_analyzer_findings)
|
|
print(f" [+] WordPress Cron (wp-cron.php) analysis finished. Overall status: {findings.get('details', 'See specific findings.')}")
|