2026年11月12日

2026年11月12日

How to Scan WordPress for Vulnerabilities with WPScan

This article is a localized edition of the Japanese original. Read the Japanese original

WPScan is a vulnerability scanner built specifically for WordPress. It examines a site from the outside and detects the versions of WordPress core, plugins and themes, publicly visible usernames, backups of configuration files and more. With an API token, it also checks what it finds against known vulnerabilities. As of November 2026 the latest version is 4.2.0.

Before you start

  • Only scan sites you manage yourself (or have permission to test). Scanning someone else's site can be treated as unauthorized access or interference with their business.
  • Under the WPScan license, testing your own systems and penetration testing are free, but uses that amount to commercialization, such as offering a paid service built on WPScan, require a commercial license (according to the README and LICENSE on GitHub).
  • A scan sends a large number of requests. On shared hosting, the provider's WAF may block it or treat it as excessive traffic, so run scans when traffic is low.

Install WPScan

Run it with Docker (recommended)

You can use it without setting up a Ruby environment.

docker pull wpscanteam/wpscan
docker run --rm wpscanteam/wpscan --version

Attach a named volume so the database (the lists of plugins and themes) isn't downloaded again on every run.

docker run -it --rm -v wpscan-db:/wpscan/.cache/wpscan/db wpscanteam/wpscan --url https://example.com/

Install it as a gem

Ruby 3.3 or later is required.

gem install wpscan
wpscan --version

The rest of this article writes wpscan; with Docker, read it as docker run -it --rm -v wpscan-db:/wpscan/.cache/wpscan/db wpscanteam/wpscan.

Get an API token

You can scan without an API token, but known vulnerabilities won't be shown. Create an account on wpscan.com and you'll find your API token on your profile page.

The free tier allows 25 requests per day (as of November 2026). WordPress core, each plugin and each theme use one request each, so on a site with 20 plugins a single scan uses up almost the whole allowance.

Passing the token through an environment variable, rather than writing it on the command line, keeps it out of your shell history.

read -rs WPSCAN_API_TOKEN && export WPSCAN_API_TOKEN
wpscan --url https://example.com/

With Docker, add -e WPSCAN_API_TOKEN to pass it into the container.

Run a scan

Basic scan

wpscan --url https://example.com/

If you don't specify --enumerate, it checks by default for vulnerable plugins, vulnerable themes, timthumbs, configuration file backups, DB exports, backup folders, users (IDs 1-10) and media (IDs 1-100).

Choosing --enumerate values

Value What it checks
vp / ap / p Vulnerable plugins / all plugins / popular plugins (pick one)
vt / at / t Vulnerable themes / all themes / popular themes (pick one)
cb Backup files of wp-config.php (such as wp-config.php.bak)
dbe DB exports left in the public directory
u Usernames (you can give an ID range, as in u1-20)
# Check vulnerable plugins and themes, config backups and usernames
wpscan --url https://example.com/ --enumerate vp,vt,cb,dbe,u

By default, plugins are detected in "passive" mode, which reads them from the page HTML and similar sources. Plugins that leave no trace in the page can be missed, so to look more widely, specify --plugins-detection mixed or aggressive (this greatly increases the number of requests).

On your own site, use --wp-auth for accurate results

Version 4.2.0 has a --wp-auth option. If you pass an administrator's application password, it fetches the list of installed plugins and themes from the REST API, which is more accurate than guessing from outside (a normal login password won't work).

wpscan --url https://example.com/ --wp-auth 'admin:xxxx xxxx xxxx xxxx xxxx xxxx'

You can create an application password under Users → Profile and revoke it on the same screen when you're done. See How to use application passwords in WordPress for details.

Read the results

The main items in the output, and what to look for:

  • WordPress version ... identified: the core version. Whether it is current or outdated (Latest, Insecure and so on) appears in parentheses.
  • [+] plugin name: the detected version and the latest version. The version is out of date means an update is available.
  • [!] Title: ...: a known vulnerability. Fixed in: shows the version that fixed it.
  • Interesting Finding(s): things like an exposed readme.html, XML-RPC being enabled or directory listings. Not all of them are dangerous; treat them as information to review.
  • User(s) Identified: usernames visible from outside.

Fix what the scan finds

Update vulnerable plugins, themes and core

# Check what can be updated, then update
wp plugin list --update=available
wp plugin update contact-form-7
wp theme update --all
wp core update
wp core update-db

Take a backup before bulk updates and, if possible, test in a staging environment. For a plugin with no fixed version available, deactivate it and look for a replacement.

wp plugin deactivate plugin-slug

Delete configuration backups and DB exports

Remove any files found by cb or dbe from the public directory. They contain passwords, so consider changing the DB password after deleting them.

Hide usernames

WordPress exposes the names of users who have published posts through the REST API (/wp-json/wp/v2/users) and author archives (the redirect from /?author=1 to /author/username/). A username alone doesn't let anyone log in, but hiding it removes a clue for brute-force attacks.

<?php
// wp-content/mu-plugins/hide-users.php

// Hide the REST API user list and single-user routes from visitors who aren't logged in
add_filter(
	'rest_endpoints',
	function ( $endpoints ) {
		if ( ! is_user_logged_in() ) {
			unset( $endpoints['/wp/v2/users'], $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
		}
		return $endpoints;
	}
);

// Redirect author archives to the home page (if you don't use author pages)
add_action(
	'template_redirect',
	function () {
		if ( is_author() ) {
			wp_safe_redirect( home_url( '/' ), 301 );
			exit;
		}
	}
);

Logged-in users still get the list, so the author selector in the block editor keeps working. If your site uses author pages, leave out the second part.

Scan on a schedule

WPScan exits with exit code 5 when it finds at least one vulnerability (0 means no vulnerabilities; 1-4 mean an option error, an interruption or an error). That lets you decide whether to send a notification without parsing the JSON result.

#!/bin/bash
# /usr/local/bin/wpscan-weekly.sh
set -u

TARGET_URL="https://example.com/"
REPORT_DIR="/var/log/wpscan"
ADMIN_EMAIL="admin@example.com"
REPORT="${REPORT_DIR}/scan-$(date +%Y%m%d).json"

# Keep the API token in a file only root can read
WPSCAN_API_TOKEN="$(cat /root/.wpscan-token)"
export WPSCAN_API_TOKEN

mkdir -p "$REPORT_DIR"

docker run --rm -e WPSCAN_API_TOKEN \
  -v wpscan-db:/wpscan/.cache/wpscan/db \
  wpscanteam/wpscan --url "$TARGET_URL" \
  --enumerate vp,vt,cb,dbe \
  --format json --no-banner > "$REPORT"
rc=$?

if [ "$rc" -eq 5 ]; then
  mail -s "[WPScan] Vulnerabilities found: ${TARGET_URL}" "$ADMIN_EMAIL" < "$REPORT"
elif [ "$rc" -ne 0 ]; then
  echo "WPScan failed (exit code ${rc})" | mail -s "[WPScan] Scan error" "$ADMIN_EMAIL"
fi

# Delete reports older than 30 days
find "$REPORT_DIR" -name 'scan-*.json' -mtime +30 -delete
  • The mail command comes from packages such as mailutils. The server also needs to be set up to send mail (Postfix or similar).
  • Add a line to root's crontab (sudo crontab -e), for example to run every Monday at 3:00.
0 3 * * 1 /usr/local/bin/wpscan-weekly.sh

To stay within the free 25 requests, the scan runs about once a week and --enumerate is limited to vulnerable items (vp,vt).

Things to watch out for

  • A WPScan result of "no vulnerabilities" doesn't guarantee the site is safe. It can't detect vulnerabilities not yet in its database or problems in custom themes and plugins.
  • If a shared host's WAF or a security plugin blocks WPScan's traffic, the results will have gaps. If that happens, review the settings, for example by allowing your own IP.
  • Defenses against the attacks themselves, such as limiting login attempts and two-factor authentication, are covered in WordPress brute-force protection.

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲