WordPress technical audit: XML-RPC, REST API, and CVEs with Bash automation and evidence-based hardening
Back to blog

WordPress technical audit: XML-RPC, REST API, and CVEs with Bash automation and evidence-based hardening

6/7/2026 · 5 min · WordPress

A default WordPress installation, lacking strict hardening, generally exposes a wide enough attack surface to facilitate user enumeration, optimized brute-force attacks, and the exploitation of known core and plugin vulnerabilities.

In this article, I document the real operational workflow I applied for a technical security audit using Bash across three critical fronts:

  1. Correlation between the core version and known CVEs (using NVD + OSV);
  2. Mapping supported methods within xmlrpc.php, with a sharp focus on system.multicall;
  3. Validating public user enumeration exposure via the native REST API.

The ultimate goal was not simply to "collect information," but to generate objective, actionable evidence enabling immediate remediation.

1) Baseline target acquisition and core versioning#

Before actively querying for CVEs, I normalized the target and detected the running WordPress version. Within a production environment, this exact step prevents false positives triggered by partial or incorrect version detection.

Baseline example script:

TARGET="https://domain.tld"

# Attempt detection via generator meta tag (not infallible)
SITE_VERSION=$(curl -skL "$TARGET" | grep -oP 'WordPress\s+\K[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1)

# Simple fallback parsing the readme file (when exposed)
if [[ -z "$SITE_VERSION" ]]; then
  SITE_VERSION=$(curl -skL "$TARGET/readme.html" | grep -oP 'Version\s+\K[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1)
fi

echo "Identified Version: ${SITE_VERSION:-not detected}"

If the version cannot be externally detected, I mark it as "unknown" and proceed strictly with surface auditing (XML-RPC/REST) instead of assuming security by obscurity.

2) Automated CVE lookups (NVD + OSV)#

The subsequent phase involved correlating the detected core version with publicly known vulnerabilities.

2.1 Bash function for the NVD API#

fetch_nvd_cves() {
  local wp_ver="$1"
  local encoded="wordpress%20$wp_ver"

  curl -s "https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=$encoded&keywordExactMatch" |
jq -r '.vulnerabilities[] |
  "- " + .cve.id + ": " +
  (.cve.descriptions[] | select(.lang=="en").value) +
  " (https://osv.dev/vulnerability/" + .cve.id + ")"'
}

2.2 fixing a frequent bug regarding return validation#

A common error is invoking the API function and hastily testing an unpopulated variable. The correct operational flow thoroughly captures the output array before attempting any validation:

nvd_cves=$(fetch_nvd_cves "$SITE_VERSION")

if [[ -n "${nvd_cves//[[:space:]]/}" ]]; then
echo "$nvd_cves"
else
echo "No CVEs found associated with version $SITE_VERSION"
fi

This specific attention to detail prevents a false "No CVEs found" flag resulting directly from faulty Bash logic.

2.3 resiliency engineering (production grade)#

In a daily operational routine, I explicitly added timeout/retry policies to prevent the entire pipeline from halting due to external API unreliability:

curl -s --connect-timeout 8 --max-time 25 --retry 2 --retry-delay 1 "https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=$encoded&keywordExactMatch"

3) XML-RPC: method mapping and consolidated brute-force risk#

xmlrpc.php resolutely remains a relevant attack vector, profoundly more so whenever system.multicall is left enabled.

3.1 fetching available methods via system.listMethods#

XMLRPC_GET_METHODS=$(curl -sLk -X POST "$TARGET/xmlrpc.php" \
  -H "Content-Type: text/xml" \
  --data '<?xml version="1.0"?>
  <methodCall>
<methodName>system.listMethods</methodName>
  </methodCall>')

3.2 correct parsing utilizing Bash arrays (dodges overwrite-in-loop)#

declare -a XMLRPC_METHODS_LIST
while IFS= read -r METHOD; do
XMLRPC_METHODS_LIST+=("$METHOD")
done < <(echo "$XMLRPC_GET_METHODS" | grep -oP '(?<=<string>).*?(?=</string>)')

Employing a native array here is compulsory to maintain a fully uncorrupted list of uniquely returned methods.

3.3 accurate detection of system.multicall#

if printf '%s\n' "${XMLRPC_METHODS_LIST[@]}" | grep -q "^system.multicall$"; then
echo "system.multicall ACTIVE - Critical Brute-Force risk via XML-RPC"
fi

Operational Note: Refrain from utilizing -e (which strictly tests for physical file existence) to validate strings or lists. For deep textual content, always utilize [[ -n ... ]] or grep -q as applied above.

3.4 severity classification schema i applied#

4) User enumeration exclusively via the REST API#

The /wp-json/wp/v2/users route natively exposes sweeping public metadata tied to established authors, thereby heavily streamlining targeted attack campaigns.

4.1 automated collection and parsing of users#

REST_USERS=$(curl -sk "$TARGET/wp-json/wp/v2/users" |
  jq -r '.[] | "\(.id): \(.name) (\(.slug))"' 2>/dev/null)

if [[ -n "$REST_USERS" ]]; then
echo "Found Users:"
echo "$REST_USERS"

# Deep Heuristics: Exposed primary ID 1
if echo "$REST_USERS" | grep -q "^1:"; then
    echo "ALERT: User ID 1 (default system admin) is publicly exposed!"
fi

# Deep Heuristics: Subtly sensitive slugs
if echo "$REST_USERS" | grep -Eiq "admin|administrator|root"; then
    echo "WARNING: Explicit administrative user slugs detected in REST output."
fi
fi

4.2 an important technical limitation#

Without explicit authentication tokens, the REST API does not directly serve roles or system capabilities. It only broadcasts public metadata; therefore, the actual residual risk derives from aggregate gathered intelligence (name + slug + authoring pattern) rather than explicit system privileges returned by the public endpoint itself.

5) Consolidated baseline audit script#

Below is a fully functional, consolidated version strictly centering around the principal audit engine. You can expand and review the entire code inline without leaving the page, run it in Google Colab, or access the maintained version on GitHub Gist:

wp-audit-xmlrpc-rest.sh — Consolidated Audit Script
#!/usr/bin/env bash
set -euo pipefail

TARGET="${1:-https://domain.tld}"

fetch_nvd_cves() {
  local wp_ver="$1"
  local encoded="wordpress%20$wp_ver"

  curl -s --connect-timeout 8 --max-time 25 --retry 2 --retry-delay 1 \
"https://services.nvd.nist.gov/rest/json/cves/2.0?keywordSearch=$encoded&keywordExactMatch" |
jq -r '.vulnerabilities[]? |
  "- " + .cve.id + ": " +
  (.cve.descriptions[] | select(.lang=="en").value) +
  " (https://osv.dev/vulnerability/" + .cve.id + ")"'
}

echo "[+] Target: $TARGET"

SITE_VERSION=$(curl -skL "$TARGET" | grep -oP 'WordPress\s+\K[0-9]+\.[0-9]+(\.[0-9]+)?' | head -n1 || true)
echo "[+] Detected Version: ${SITE_VERSION:-not detected}"

if [[ -n "${SITE_VERSION:-}" ]]; then
  echo "[+] Querying NVD for matching CVEs..."
  nvd_cves=$(fetch_nvd_cves "$SITE_VERSION")
  if [[ -n "${nvd_cves//[[:space:]]/}" ]]; then
echo "$nvd_cves"
  else
echo "No vulnerabilities explicitly found matching version $SITE_VERSION"
  fi
fi

echo "[+] Auditing XML-RPC framework methods..."
XMLRPC_GET_METHODS=$(curl -sLk -X POST "$TARGET/xmlrpc.php" \
  -H "Content-Type: text/xml" \
  --data '<?xml version="1.0"?>
  <methodCall><methodName>system.listMethods</methodName></methodCall>' || true)

declare -a XMLRPC_METHODS_LIST
while IFS= read -r METHOD; do
  XMLRPC_METHODS_LIST+=("$METHOD")
done < <(echo "$XMLRPC_GET_METHODS" | grep -oP '(?<=<string>).*?(?=</string>)' || true)

if printf '%s\n' "${XMLRPC_METHODS_LIST[@]:-}" | grep -q "^system.multicall$"; then
  echo "[!] system.multicall remains ACTIVE - critical risk identified"
else
  echo "[-] system.multicall definitively not identified"
fi

echo "[+] Testing REST user-list enumeration..."
REST_USERS=$(curl -sk "$TARGET/wp-json/wp/v2/users" | jq -r '.[]? | "\(.id): \(.name) (\(.slug))"' 2>/dev/null || true)

if [[ -n "${REST_USERS//[[:space:]]/}" ]]; then
  echo "$REST_USERS"
  echo "$REST_USERS" | grep -q '^1:' && echo "[!] Primary ID 1 remains fully exposed"
  echo "$REST_USERS" | grep -Eiq 'admin|administrator|root' && echo "[!] Sensitive administrative slug detected"
else
  echo "[-] Meaningful absence of direct user exposure or properly restricted API endpoint"
fi

6) Trenches lessons learned (Bash and operations)#

  1. Avoid utilizing -e for string comparisons; explicitly use [[ -n "$VAR" ]] for text.
  2. Continually capture internal function returns utilizing VAR=$(function_name).
  3. Rely heavily on formal arrays to manage dynamic lists (helps avoid massive loop data loss).
  4. When dealing with an external API (like NVD), strictly enforce timeout/retry conditions to preserve overall script robustness.
  5. Anticipate entirely absent JSON data payloads utilizing ? inside jq chains to prevent catastrophic parser breaking.

7) Hardening roadmap post-audit execution#

Following the confirmation of architectural findings, I orchestrated my mitigation roadmap structured tightly around priority:

7.1 hardening XML-RPC#

An impenetrable full-block NGINX configuration example:

location = /xmlrpc.php {
deny all;
return 403;
}

7.2 hardening REST API users#

7.3 firm brute-force limitations#

Enforce granular rate-limiting per remote IP/sensitive routing through NGINX rulesets, specifically aiming to decimate the effectiveness associated with automated bot attacks - most notably within aggressive system.multicall abuse scenarios.

7.4 granular vulnerability administration#

8) Technical conclusion#

Actively auditing a WordPress installation fueled strictly by concrete technical evidence (CVE matching + mapped surface exposure + deep abuse heuristic patterns) fundamentally changes the overall dynamic of operational security: your team departs from mere perception and operates exclusively on reproducible empirical data.

Armed with a highly-structured Bash baseline architecture, any SysAdmin or security responder gains profound triage velocity, standardizes underlying diagnostics effectively across distinct remote environments, and subsequently converts all resultant output into highly objective structural hardening - realistically nullifying the exploitation threshold.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

CC BY-NC

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments