WordPress XML-RPC Interface Enabled
Checks whether xmlrpc.php answers on WordPress sites.
- Why It Matters
- XML-RPC allows many password guesses in one request (system.multicall) and is abused for pingback amplification. Few sites still need it.
- How It Detects
- Sends one GET to /xmlrpc.php; the standard reply 'XML-RPC server accepts POST requests only.' confirms it is enabled. No XML-RPC call is made.
- How to Fix
- Block it at the web server (<Files xmlrpc.php> Require all denied </Files>) or disable it with a security plugin, unless Jetpack or the mobile app needs it.
- How to Verify
curl -s https://<site>/xmlrpc.php should return 403 or 404.- CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L- Control Category
- Web Application Security: CIS Controls v8 16; NIST CSF 2.0 PR.PS, PR.DS
- Fix Effort
- Low
- CIS Controls v8 Safeguard
- 16.7
- NIST CSF 2.0 Subcategory
- PR.PS-01
- CWE
- CWE-307
- OWASP Top 10
- A07:2021
- OWASP ASVS 4.0.3
- 2.2.1
- NIST SP 800-53 Rev. 5
- AC-7, CM-7
- References
- WordPress Developer Resources, Brute Force Attacks (XML-RPC)
Source Code
The complete file that defines this check. Run it with openvapt scan --only WEB-CMS-002, or read it offline with openvapt explain WEB-CMS-002.
internal/checks/web/cms.go · Lines 1-84View on GitHub at This Commit
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "regexp" |
| 6 | "time" |
| 7 | |
| 8 | "github.com/openvapt/openvapt/internal/check" |
| 9 | "github.com/openvapt/openvapt/internal/sys" |
| 10 | ) |
| 11 | |
| 12 | // wpReadmeRe matches the signature of WordPress's own readme.html: the logo |
| 13 | // heading followed by the version line, for example |
| 14 | // <h1 id="logo"><a href="https://wordpress.org/">WordPress</a><br /> Version 6.4</h1>. |
| 15 | var wpReadmeRe = regexp.MustCompile(`(?is)<h1 id="logo">.{0,200}?wordpress.{0,200}?version\s+(\d+(?:\.\d+)+)`) |
| 16 | |
| 17 | // wpReadmeVersion returns the version stated by a WordPress readme.html. |
| 18 | func wpReadmeVersion(body string) (string, bool) { |
| 19 | if m := wpReadmeRe.FindStringSubmatch(body); m != nil { |
| 20 | return m[1], true |
| 21 | } |
| 22 | return "", false |
| 23 | } |
| 24 | |
| 25 | func today() string { return time.Now().UTC().Format("2006-01-02") } |
| 26 | |
| 27 | func init() { |
| 28 | check.Register(&check.Check{ |
| 29 | ID: "WEB-CMS-001", |
| 30 | Title: "WordPress Version Disclosed", |
| 31 | Domain: check.DomainWeb, |
| 32 | Severity: check.Low, |
| 33 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", |
| 34 | Summary: "Checks whether WordPress sites reveal their exact version in the page or in readme.html.", |
| 35 | Rationale: "The version number lets automated tools pick matching exploits for outdated cores and plugins.", |
| 36 | Detection: "Looks for the WordPress generator meta tag on the home page and requests /readme.html, which counts only when it carries the WordPress readme signature (the logo heading and a Version line), so a soft-404 page that merely mentions WordPress does not fire.", |
| 37 | Fix: "Keep WordPress updated (automatic minor updates on). Remove the generator tag (remove_action('wp_head','wp_generator')) and delete readme.html after each update, or block it at the web server.", |
| 38 | Verify: "curl -s https://<site>/ | grep -i 'name=\"generator\"'", |
| 39 | Refs: []check.Ref{{Title: "WordPress Developer Resources, Hardening WordPress", URL: "https://developer.wordpress.org/advanced-administration/security/hardening/"}}, |
| 40 | Standards: check.Standards{CWE: []string{"CWE-497"}, OWASP: []string{"A05:2021", "A06:2021"}, ASVS: []string{"14.3.3"}, WSTG: []string{"WSTG-INFO-08"}, NIST: []string{"CM-7", "SI-11"}}, |
| 41 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 42 | gen := regexp.MustCompile(`(?i)<meta name="generator" content="WordPress ([\d.]+)"`) |
| 43 | return ForEachSite(ctx, s, func(st Site) check.Result { |
| 44 | var ev []string |
| 45 | if m := gen.FindStringSubmatch(st.Home.Body); m != nil { |
| 46 | ev = append(ev, "generator tag: WordPress "+m[1]) |
| 47 | } |
| 48 | if r, err := st.Get(ctx, s, "/readme.html"); err == nil && r.Status == 200 { |
| 49 | if v, ok := wpReadmeVersion(r.Body); ok { |
| 50 | ev = append(ev, "/readme.html is public and states WordPress "+v) |
| 51 | } |
| 52 | } |
| 53 | if len(ev) > 0 { |
| 54 | return check.FailR(st.Name, "WordPress version is disclosed", ev...) |
| 55 | } |
| 56 | return check.PassR(st.Name, "No WordPress version disclosure found") |
| 57 | }) |
| 58 | }, |
| 59 | }) |
| 60 | |
| 61 | check.Register(&check.Check{ |
| 62 | ID: "WEB-CMS-002", |
| 63 | Title: "WordPress XML-RPC Interface Enabled", |
| 64 | Domain: check.DomainWeb, |
| 65 | Severity: check.Low, |
| 66 | CVSS: "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L", |
| 67 | Summary: "Checks whether xmlrpc.php answers on WordPress sites.", |
| 68 | Rationale: "XML-RPC allows many password guesses in one request (system.multicall) and is abused for pingback amplification. Few sites still need it.", |
| 69 | Detection: "Sends one GET to /xmlrpc.php; the standard reply 'XML-RPC server accepts POST requests only.' confirms it is enabled. No XML-RPC call is made.", |
| 70 | Fix: "Block it at the web server (<Files xmlrpc.php> Require all denied </Files>) or disable it with a security plugin, unless Jetpack or the mobile app needs it.", |
| 71 | Verify: "curl -s https://<site>/xmlrpc.php should return 403 or 404.", |
| 72 | Refs: []check.Ref{{Title: "WordPress Developer Resources, Brute Force Attacks (XML-RPC)", URL: "https://developer.wordpress.org/advanced-administration/security/brute-force/"}}, |
| 73 | Standards: check.Standards{CWE: []string{"CWE-307"}, OWASP: []string{"A07:2021"}, NIST: []string{"AC-7", "CM-7"}}, |
| 74 | Run: func(ctx context.Context, s sys.System) []check.Result { |
| 75 | return ForEachSite(ctx, s, func(st Site) check.Result { |
| 76 | r, err := st.Get(ctx, s, "/xmlrpc.php") |
| 77 | if err == nil && regexp.MustCompile(`XML-RPC server accepts POST`).MatchString(r.Body) { |
| 78 | return check.FailR(st.Name, "xmlrpc.php is enabled") |
| 79 | } |
| 80 | return check.PassR(st.Name, "xmlrpc.php is not reachable") |
| 81 | }) |
| 82 | }, |
| 83 | }) |
| 84 | } |
Also available as plain Markdown for notes, scripts and AI tools. Same facts, same source, no styling.
Cite This Page
If this page helped your work, please cite it. The version and web address are filled in; the access date is today's date in your browser. Every other style, and when credit is required, is on How to Cite OpenVAPT.
OpenVAPT. (2026). WEB-CMS-002 WordPress XML-RPC Interface Enabled (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/WEB-CMS-002.html
In text: (OpenVAPT, 2026). Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
@misc{openvapt_web_cms_002,
author = {{OpenVAPT}},
title = {{WEB-CMS-002 WordPress XML-RPC Interface Enabled}},
year = {2026},
howpublished = {\url{https://openvapt.com/checks/WEB-CMS-002.html}},
note = {OpenVAPT Check Catalogue, version 0.1.0-alpha.6. Accessed 16 October 2026}
}
Accessed: the date shown is today's date from your own browser (the site's build date without JavaScript); change it to the day you read the page.
Citing OpenVAPT does not mean OpenVAPT endorses, approves or has reviewed your work.