WordPress Version Disclosed
Checks whether WordPress sites reveal their exact version in the page or in readme.html.
- Why It Matters
- The version number lets automated tools pick matching exploits for outdated cores and plugins.
- How It Detects
- 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.
- How to 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.
- How to Verify
curl -s https://<site>/ | grep -i 'name="generator"'- CVSS v3.1 Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N- Control Category
- Information Exposure: CIS Controls v8 3, 16; NIST CSF 2.0 PR.DS
- Fix Effort
- Low
- CIS Controls v8 Safeguard
- 16.7
- NIST CSF 2.0 Subcategory
- PR.PS-01
- OWASP WSTG v4.2
- WSTG-INFO-08
- CWE
- CWE-497
- OWASP Top 10
- A05:2021, A06:2021
- OWASP ASVS 4.0.3
- 14.3.3
- NIST SP 800-53 Rev. 5
- CM-7, SI-11
- References
- WordPress Developer Resources, Hardening WordPress
Source Code
The complete file that defines this check. Run it with openvapt scan --only WEB-CMS-001, or read it offline with openvapt explain WEB-CMS-001.
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-001 WordPress Version Disclosed (Version 0.1.0-alpha.6). OpenVAPT Check Catalogue. Retrieved October 16, 2026, from https://openvapt.com/checks/WEB-CMS-001.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_001,
author = {{OpenVAPT}},
title = {{WEB-CMS-001 WordPress Version Disclosed}},
year = {2026},
howpublished = {\url{https://openvapt.com/checks/WEB-CMS-001.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.