Direct answer: CVE-2026-119977 lets an attacker with no login run code on a WordPress server while a core update runs. CISA added this flaw to the KEV catalog on 3 November 2026, so attackers already use it. Today, check the WordPress version, update to 7.1.4 or the safe release on your branch, then look for signs in the logs.

The flaw is in WordPress 4.7 to 7.1.3. The Wordfence CVSS 3.1 score is 8.1, but CISA records exploitation as "active" and the technical impact as "total". Sites that already updated automatically must still look for signs, because their update window fell in the attack period.

Main condition: The site runs WordPress core below the safe release, and the web server serves the wp-content/upgrade/ folder. Limit: This article has no request contents, payloads, or attack sequence.

We read the CVE-2026-119977 record, the Wordfence advisory, the WordPress 7.1.4 announcement, CISA KEV, and the official WordPress version API on 4 November 2026, from 06.35 to 06.50 WIB. We tested 3 check scripts, a core update, and 3 block rules in podman containers with the official WordPress, WP-CLI, MariaDB, and nginx images. We did not test an attack.

The problem: a flaw in the update process that should protect you

The core update is the main WordPress security step, but this flaw is in the update process itself.

When a core update runs, WordPress downloads the release package and unpacks it into the wp-content/upgrade/ folder. This folder is inside the web folder, so the web server usually serves it. The process takes only a few seconds, and the Wordfence advisory calls it the upgrade window.

WordPress released 7.1.4 on 28 October 2026, with security releases for the 4.7 to 7.0 branches. The CVE record came out on 31 October. On 3 November, CISA added the flaw to CISA KEV with a 6 November due date for US federal agencies.

Timeline: 28 October WordPress 7.1.4 and the Wordfence advisory, 31 October CVE record, 3 November CISA KEV, 4 November 13,330 blocked attacks, 6 November CISA due date.
Teal dot: the fix release. Amber dots: Wordfence data. Red dots: the CVE record, the KEV status, and the CISA due date. Source: WordPress.org, Wordfence, CVE.org, and CISA KEV.

On 4 November 2026, the Wordfence advisory page showed 13,330 blocked attacks in the past 24 hours. This number covers only sites that use the Wordfence firewall.

How it works: a staging folder that is easy to guess

This section uses the official summary and the patch contents. It helps you choose the steps. It does not explain how to attack.

Summary from the CVE record

According to the CVE-2026-119977 record, full-package core updates use staging paths that are easy to guess and open over the web. As a result, an attacker with no login can run code on the server during the upgrade window. The flaw class is CWE-367, a race condition between the time of check and the time of use.

The CVSS vector has AC:H, which means that an attack works only at the right moment. The KEV status shows that attackers still succeed in the field.

What the patch changes

We compared the code in the official 7.1.3 and 7.1.4 images. The patch makes 3 changes:

  • The working folder name in wp-content/upgrade/ gets 16 random characters (class-wp-upgrader.php).
  • The upgrade folder gets an index.php file, so nobody can list its contents over the web.
  • The update never copies a wp-config.php from the working folder into the site (update-core.php).

Rama Digital analysis: change 3 is in the update file from the new package, so it already applies during the update to 7.1.4. Changes 1 and 2 apply from the next update. Limit: we read the code and did not test an attack.

Exploitation status on 4 November 2026

SourceStatus
CISA KEVAdded 3 November 2026, due date 6 November 2026
CISA SSVC in the CVE recordExploitation: active, Technical Impact: total
Wordfence13,330 blocked attacks in 24 hours
WordPress.orgAsks all sites to update immediately
Three cards: CVE-2026-119977 core update staging folder score 8.1, added to CISA KEV on 3 November with active exploitation, and the update window condition. Safe: 7.1.4.
(1) Left red card: the flaw and the score. (2) Middle red card: the KEV status. (3) Amber card: when the flaw is open. (4) Teal box: the safe version. Source: the Wordfence CVE record, CISA KEV, and apiAPIThe official door 2 systems use to exchange data, without anybody copying it by hand.Open the glossary.wordpress.org.

A score of 8.1 is in the High class. We still write a critical warning, because attackers already use this flaw, it needs no login, and it ends in code execution.

Who has the highest risk

The risk depends on how your site updates and how the server closes the upgrade folder.

Site conditionRiskReason
Automatic updates off, version below the safe releaseHighestThe site stays on a vulnerable version. The next manual update opens the same window
Updated between 28 October and 3 November, log not checkedHighThe update window fell in the attack period
Many WordPress sites on 1 serverHighMany update windows. 1 hacked site opens the server for all sites
nginx with a plain prefix ruleCheckIn our test, .php files in the upgrade folder stayed open
Block rule inside the upgrade folderCheckIn our test, the update deleted the .htaccess in that folder
7.1.4, folder blocked, clean logLowStill look for signs from before the update
Six risk cards: automatic updates off, an update from 28 October to 3 November with no log check, and many sites are red; plain nginx prefix and in-folder rule are amber.
Red cards: the highest risk. Amber cards: check the configuration. Grey card: low risk. Bottom box: the CISA status and the Wordfence number. Source: CISA KEV, the Wordfence CVE record, and Rama Digital tests.

A WordPress server often also runs the PHP runtime and a hosting panel. Patch those components too, for example for the PHP2Shell warning and the Control Web Panel flaw CVE-2026-77876.

Vulnerable and safe versions

All versions from 4.7.0 to 7.1.3 are vulnerable, except the safe release on each branch. Use this table to choose the action.

WordPress versionStatus on 4 November 2026Safe release
7.1.0 to 7.1.3Vulnerable7.1.4
7.0.0 to 7.0.7Vulnerable7.0.8 or 7.1.4
6.9.0 to 6.9.10Vulnerable6.9.11
6.8.0 to 6.8.11Vulnerable6.8.12
6.7.0 to 6.7.10, 6.6.0 to 6.6.10Vulnerable6.7.11, 6.6.11
6.5, 6.4, 6.3Vulnerable below x.146.5.14, 6.4.14, 6.3.14
6.2, 6.1, 6.0Vulnerable6.2.15, 6.1.16, 6.0.18
5.0 to 5.9Vulnerable5.0.31, 5.1.28, 5.2.30, 5.3.27, 5.4.25, 5.5.24, 5.6.23, 5.7.21, 5.8.19, 5.9.20
4.7 to 4.9Vulnerable4.7.39, 4.8.34, 4.9.35
Below 4.7Outside this CVE, no security patchesMove to 7.1.4
A table of WordPress version ranges: 7.1.0 to 7.1.3, 7.0, 6.x, and 4.7 to 5.9 vulnerable; below 4.7 outside the CVE; 7.1.4, 7.0.8, and 6.9.11 safe.
Red: vulnerable. Amber: outside this CVE, but with no patches. Teal: safe. Bottom box: how to check the version and how to update. Source: the Wordfence CVE record and api.wordpress.org.

The version data comes from the CVE-2026-119977 record, the Wordfence advisory, and the official WordPress version API. According to the WordPress 7.1.4 announcement, WordPress 7.2 Beta 3 also has this fix.

Prerequisites

  • SSH access with sudo to the server, or an administrator account in the WordPress dashboard.
  • WP-CLI on the server when you use the wp commands. Steps 1 and 3 also work in the dashboard.
  • A recent backup of the database and files, for example a VPS snapshot or a hosting panel backup.
  • Access to the web server access log, for example /var/log/apache2/ or /var/log/nginx/.
  • A list of all WordPress sites that you manage, including staging sites and old sites.

Step 1: Check the WordPress version

Save this script as check-version-wp.sh in the ~/cek folder. The script reads $wp_version from wp-includes/version.php, then compares it with the safe release on the same branch.

#!/usr/bin/env bash
# Check the WordPress core version against CVE-2026-119977 (RCE during the upgrade window, CISA KEV 3 November 2026).
# Data: the CVE record from the Wordfence CNA and the official api.wordpress.org API (safe version per branch).
# Use on the server: sudo bash check-version-wp.sh /var/www/html
# Use for versions that you copied from other servers: bash check-version-wp.sh 6.9.10 7.0.8 7.1.4
# The script only reads files. The script changes nothing.

# The first safe version per branch, 4.7 to 7.1.
declare -A SAFE=(
  [4.7]=4.7.39 [4.8]=4.8.34 [4.9]=4.9.35 [5.0]=5.0.31 [5.1]=5.1.28 [5.2]=5.2.30 [5.3]=5.3.27
  [5.4]=5.4.25 [5.5]=5.5.24 [5.6]=5.6.23 [5.7]=5.7.21 [5.8]=5.8.19 [5.9]=5.9.20 [6.0]=6.0.18
  [6.1]=6.1.16 [6.2]=6.2.15 [6.3]=6.3.14 [6.4]=6.4.14 [6.5]=6.5.14 [6.6]=6.6.11 [6.7]=6.7.11
  [6.8]=6.8.12 [6.9]=6.9.11 [7.0]=7.0.8 [7.1]=7.1.4
)

# lower A B: true when version A is below version B.
lower() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }

check() {
  local v="$1" branch safe
  if ! [[ $v =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
    printf '%-9s UNKNOWN, compare it by hand with the safe version table\n' "$v"
    return
  fi
  [[ $v =~ ^[0-9]+\.[0-9]+$ ]] && v="$v.0"
  branch=${v%.*}
  safe=${SAFE[$branch]}
  if [ -n "$safe" ]; then
    if lower "$v" "$safe"; then
      if [ "$safe" = 7.1.4 ]; then
        printf '%-9s VULNERABLE to CVE-2026-119977 -> update to 7.1.4\n' "$v"
      else
        printf '%-9s VULNERABLE to CVE-2026-119977 -> update to %s or 7.1.4\n' "$v" "$safe"
      fi
    else
      printf '%-9s SAFE for CVE-2026-119977 (branch %s)\n' "$v" "$branch"
    fi
  elif lower "$v" 4.7.0; then
    printf '%-9s OUTSIDE THIS CVE, but with no security patches -> update to 7.1.4\n' "$v"
  else
    printf '%-9s ABOVE 7.1, compare it by hand with the WordPress announcement\n' "$v"
  fi
}

DIR=/var/www/html
VERSIONS=()
for x in "$@"; do
  if [ -d "$x" ]; then DIR="$x"; else VERSIONS+=("$x"); fi
done

if (( ${#VERSIONS[@]} > 0 )); then
  for v in "${VERSIONS[@]}"; do check "$v"; done
  exit 0
fi

FILE="$DIR/wp-includes/version.php"
if [ ! -r "$FILE" ]; then
  echo "Cannot read the version file: $FILE"
  echo "Give the WordPress folder as an argument, for example: sudo bash check-version-wp.sh /var/www/html"
  exit 1
fi
v=$(sed -nE "s/^\\\$wp_version *= *'([^']+)'.*/\1/p" "$FILE" | head -n1)
echo "WordPress folder: $DIR"
check "${v:-unreadable}"
# On the server: read the version from the WordPress folder
sudo bash check-version-wp.sh /var/www/html
# Check versions that you copied from other sites
bash check-version-wp.sh 6.8.11 6.9.10 7.0.8 7.1.4
# Manual check without the script
grep wp_version /var/www/html/wp-includes/version.php
cd /var/www/html && sudo -u www-data wp core version
Terminal: the version check reads WordPress 7.1.3 as VULNERABLE. Manual mode: 4.6.30 outside the CVE, 6.8.11 and 6.9.10 vulnerable, 7.0.8 and 7.1.4 safe.
The test container uses the official wordpress:7.1.3-apache image. The script only reads the version.php file. Source: Rama Digital test in a podman container with the official wordpress:7.1.3-apache and wordpress:cli images, 4 November 2026.

In the dashboard, open Dashboard > Updates. This page shows your version and the latest version.

Check the result: "VULNERABLE" means go to steps 2 and 3 today. "SAFE" still means run step 5, because your update may have fallen in the attack period.

Step 2: Check the upgrade folder and automatic updates

This script answers 2 questions: does the web serve the upgrade folder, and are core automatic updates on.

Save this script as check-upgrade-wp.sh. The script writes 2 small test text files, requests them over HTTP, then deletes both. The control file in wp-content/ confirms that the site address is correct.

#!/usr/bin/env bash
# Check 2 items for CVE-2026-119977: does the web serve the wp-content/upgrade folder, and are core automatic updates on.
# Use on the server: sudo bash check-upgrade-wp.sh /var/www/html https://example.com
# The script writes 2 small test text files, requests both over HTTP, then deletes both.

DIR="${1:-/var/www/html}"
URL="${2%/}"
if [ ! -r "$DIR/wp-config.php" ] && [ ! -r "$DIR/../wp-config.php" ]; then
  echo "No wp-config.php in $DIR. Give the WordPress folder as argument 1."
  exit 1
fi
if [ -z "$URL" ]; then
  echo "Give the site address as argument 2, for example: https://example.com"
  exit 1
fi
CONF="$DIR/wp-config.php"
[ -r "$CONF" ] || CONF="$DIR/../wp-config.php"

echo "1. Core automatic updates"
constant() { grep -E "^[^/#]*define\( *['\"]$1['\"] *, *$2" "$CONF" >/dev/null; }
off=0
if constant AUTOMATIC_UPDATER_DISABLED true; then echo "   OFF: AUTOMATIC_UPDATER_DISABLED = true"; off=1; fi
if constant WP_AUTO_UPDATE_CORE false; then echo "   OFF: WP_AUTO_UPDATE_CORE = false"; off=1; fi
if constant DISALLOW_FILE_MODS true; then echo "   OFF: DISALLOW_FILE_MODS = true"; off=1; fi
d="$(cd "$DIR" && pwd)"
while [ -n "$d" ]; do
  for vcs in .git .svn .hg .bzr; do
    if [ -e "$d/$vcs" ]; then echo "   OFF: $d/$vcs exists, WordPress does not update a version control folder automatically"; off=1; fi
  done
  [ "$d" = / ] && break
  d="$(dirname "$d")"
done
if constant DISABLE_WP_CRON true; then echo "   CHECK: DISABLE_WP_CRON = true, make sure a server cron calls wp-cron.php"; fi
(( off == 0 )) && echo "   ON according to wp-config.php (security updates run through WP-Cron)"

echo "2. The wp-content/upgrade folder over the web"
UP="$DIR/wp-content/upgrade"
made_up=0
if [ ! -d "$UP" ]; then mkdir -p "$UP" && made_up=1; fi
name="rd-cek-$(date +%s)-$RANDOM.txt"
echo "test" > "$DIR/wp-content/$name"
echo "test" > "$UP/$name"
clean() { rm -f "$DIR/wp-content/$name" "$UP/$name"; (( made_up == 1 )) && rmdir "$UP" 2>/dev/null; }
trap clean EXIT
control=$(curl -sk -o /dev/null -w '%{http_code}' --max-time 15 "$URL/wp-content/$name")
code=$(curl -sk -o /dev/null -w '%{http_code}' --max-time 15 "$URL/wp-content/upgrade/$name")
echo "   Control wp-content/$name: HTTP $control"
echo "   Test wp-content/upgrade/$name: HTTP $code"
if [ "$control" != 200 ]; then
  echo "   NOT SURE: the control file does not open. Check the site address or the WordPress folder."
elif [ "$code" = 200 ]; then
  echo "   OPEN: the web serves the upgrade folder. Add the block rule in step 4."
else
  echo "   BLOCKED: the web does not serve the upgrade folder (HTTP $code)."
fi
sudo bash check-upgrade-wp.sh /var/www/html https://example.com
Terminal: the upgrade folder check prints automatic updates ON, control file HTTP 200, test file in wp-content/upgrade HTTP 200, result OPEN.
This is the default state of the official WordPress image. The site address in the command is a sample. Our test requested the files through the local container address. Source: Rama Digital test in a podman container with the official wordpress:7.1.3-apache and wordpress:cli images, 4 November 2026.

The official WordPress image serves the upgrade folder with HTTP 200. The script also reads AUTOMATIC_UPDATER_DISABLED, WP_AUTO_UPDATE_CORE, DISALLOW_FILE_MODS, and version control folders such as .git. Each of these items turns off core automatic updates.

Check the result: "OPEN" means add step 4. "OFF" means the site does not update itself, so step 3 must be manual.

Step 3: Update to 7.1.4 or the safe release on your branch

Only the update closes this flaw. When you can, add the step 4 rule first, then update.

# 1. Make a backup in the hosting panel or a VPS snapshot first.
cd /var/www/html
# 2. Update core to the latest version (7.1.4)
sudo -u www-data wp core update
# 3. Check the core files against the official checksums. Read every Warning line.
sudo -u www-data wp core verify-checksums
# 4. Check the version again
sudo bash ~/cek/check-version-wp.sh /var/www/html
Terminal: wp core update downloads the 7.1.4 partial package, verify-checksums prints Success, and the version check prints 7.1.4 SAFE.
WP-CLI runs in the official wordpress:cli container as the www-data user. The update ran with the step 4 rule on. Source: Rama Digital test in a podman container with the official wordpress:7.1.3-apache and wordpress:cli images, 4 November 2026.
  1. Make a backup of the database and files first.
  2. Update through Dashboard > Updates, or through WP-CLI.
  3. Run wp core verify-checksums. Read every Warning line, not only the Success line.
  4. Run check-version-wp.sh again. The correct result: "SAFE".
  5. Repeat on every site, including staging and old sites.

In our test, the update from 7.1.3 to 7.1.4 used the partial package wordpress-7.1.4-partial-3.zip. A reinstall with --force used the full package. Both updates worked with the step 4 rule on.

To stay on an old branch, for example 6.8.12, WP-CLI has the wp core update --minor option. We did not test this option. The old branch release closes this flaw, but plan a move to 7.1 in your next maintenance slot.

A 5-row ladder: check the version, check the upgrade folder and automatic updates, update to 7.1.4 that closes the flaw, block the folder labelled not a fix, then look for signs.
(1) to (3): the fix order. (4): an extra layer. The red label reminds you that only the update closes the flaw. Source: WordPress.org, the Wordfence CVE record, and Rama Digital analysis.

Check the result: the version script prints "SAFE" on every site, verify-checksums has no Warning line, and the home page opens normally.

Step 4: Block the upgrade folder over the web

This step does not close the flaw. This step closes the web path to the upgrade folder as an extra layer, before and after the update.

The WordPress 7.1.4 announcement and the CVE record give no official workaround. Rama Digital analysis: we made this rule from the patch contents. Condition: the web server reads rules from .htaccess or from the server configuration. Limit: this rule does not clean a site that attackers already entered.

4a. Apache and hosting with .htaccess

Put these 5 lines on the first lines of the main .htaccess, outside the # BEGIN WordPress block. WordPress rewrites the contents of that block, but it does not touch the lines outside it.

# BEGIN Block upgrade folder
<IfModule mod_alias.c>
RedirectMatch 403 /wp-content/upgrade/
</IfModule>
# END Block upgrade folder
# Save the 5 lines above as ~/cek/upgrade-rule.txt, then:
cd /var/www/html && sudo cp .htaccess .htaccess.bak
cat ~/cek/upgrade-rule.txt .htaccess.bak | sudo tee .htaccess >/dev/null
head -n 6 .htaccess
sudo bash ~/cek/check-upgrade-wp.sh /var/www/html https://example.com
# Undo: restore the copy
# sudo cp .htaccess.bak .htaccess
Terminal: the RedirectMatch 403 rule goes at the top of .htaccess, above the WordPress block. The upgrade folder check prints HTTP 403 BLOCKED, the home page stays HTTP 200.
In our test, this rule stayed after a partial update and a full package reinstall. The site address in the command is a sample. Source: Rama Digital test in a podman container with the official wordpress:7.1.3-apache and wordpress:cli images, 4 November 2026.

Do not put the rule inside the wp-content/upgrade/ folder. In our test, the core update deleted the .htaccess file in that folder. The rule in the main .htaccess stayed after a partial update and after a full package reinstall.

When you have access to the Apache configuration, this block also gave HTTP 403 in our test. Replace the path with your site folder.

# /etc/apache2/conf-enabled/block-upgrade.conf (Debian and Ubuntu)
<Directory /var/www/html/wp-content/upgrade>
    Require all denied
</Directory>

# Check the syntax, then reload Apache with no dropped connections
sudo apache2ctl -t && sudo apache2ctl -k graceful

4b. nginx

Use the ^~ modifier. In our test, a plain prefix rule blocked .txt files, but .php files stayed open, because the location ~ \.php$ regex block wins. The rule with ^~ blocked both.

# In the server block of the site, before the location ~ \.php$ block
location ^~ /wp-content/upgrade/ {
    deny all;
}

# Check the syntax, then reload nginx
sudo nginx -t && sudo nginx -s reload

We did not test LiteSpeed and OpenLiteSpeed. On any server, check the result with the step 2 script.

Check the result: the step 2 script prints "BLOCKED", and the home page stays HTTP 200.

Step 5: Look for signs of use

Attackers used this flaw before many sites updated, so look for signs from 28 October to today.

Save this script as check-signs-wp.sh. The script reads the web access log, the upgrade folder, wp-config.php, and the wp-content/uploads folder. The script prints request counts and IPs, not request contents.

#!/usr/bin/env bash
# Look for signs of CVE-2026-119977 use in the web access log, the upgrade folder, wp-config.php, and the uploads folder.
# Use: sudo bash check-signs-wp.sh /var/www/html [other log files ...]
# The script only reads files. The script prints request counts and IPs, not request contents.

DIR="${1:-/var/www/html}"
shift
LOGS=("$@")
if (( ${#LOGS[@]} == 0 )); then
  for f in /var/log/apache2/access.log* /var/log/nginx/access.log* /var/log/httpd/access_log*; do
    [ -e "$f" ] && LOGS+=("$f")
  done
fi
signs=0
# n WORD WORDS: print the count with the singular or the plural word.
n() { if (( $1 == 1 )); then echo "$1 $2"; else echo "$1 $3"; fi; }
top_ips() { awk '{print $1}' | sort | uniq -c | sort -rn | head -n 5 | awk '{printf "   IP %s: %s\n", $2, $1}'; }

echo "1. Requests to /wp-content/upgrade/ in the web access log"
if (( ${#LOGS[@]} == 0 )); then
  echo "   No access log. Give the log path as an argument."
  echo "2. Requests to setup-config.php inside the upgrade folder"
  echo "   No access log."
else
  # Skip the test files from the step 2 script.
  lines=$(zcat -f "${LOGS[@]}" 2>/dev/null | grep -F '/wp-content/upgrade/' | grep -v 'rd-cek-')
  count=$(printf '%s' "$lines" | grep -c .)
  if (( count > 0 )); then
    ok=$(printf '%s\n' "$lines" | awk '$9 == 200' | grep -c .)
    echo "   SIGN: $(n $count request requests), $ok with HTTP 200"
    printf '%s\n' "$lines" | top_ips
    signs=$((signs + 1))
  else
    echo "   0 requests"
  fi
  echo "2. Requests to setup-config.php inside the upgrade folder"
  count=$(printf '%s' "$lines" | grep -c 'setup-config\.php')
  if (( count > 0 )); then
    echo "   SIGN: $(n $count request requests)"
    printf '%s\n' "$lines" | grep 'setup-config\.php' | top_ips
    signs=$((signs + 1))
  else
    echo "   0 requests"
  fi
fi

echo "3. Contents of wp-content/upgrade (other than index.php)"
UP="$DIR/wp-content/upgrade"
if [ -d "$UP" ]; then
  left=$(find "$UP" -mindepth 1 -maxdepth 1 ! -name index.php ! -name 'rd-cek-*' | wc -l)
  echo "   $(n $left item items)"
  find "$UP" -mindepth 1 -maxdepth 1 ! -name index.php ! -name 'rd-cek-*' -printf '   %TY-%Tm-%Td %TH:%TM  %f\n' | head -n 5
  stray=$(find "$UP" -name wp-config.php 2>/dev/null | wc -l)
  if (( stray > 0 )); then
    echo "   SIGN: $(n $stray "wp-config.php file" "wp-config.php files") inside the upgrade folder"
    signs=$((signs + 1))
  fi
else
  echo "   No folder"
fi

echo "4. wp-config.php"
CONF="$DIR/wp-config.php"
[ -r "$CONF" ] || CONF="$DIR/../wp-config.php"
if [ -r "$CONF" ]; then
  echo "   Last changed: $(date -r "$CONF" '+%Y-%m-%d %H:%M')"
  for k in DB_HOST DB_NAME DB_USER; do
    value=$(grep -E "define\( *['\"]$k['\"]" "$CONF" | head -n1 | sed -E "s/.*define\( *['\"]$k['\"] *, *//; s/ *\);.*//")
    echo "   $k: ${value:-none}"
  done
  echo "   Make sure that these 3 values belong to your own database."
else
  echo "   Cannot read wp-config.php"
fi

echo "5. PHP files in wp-content/uploads"
if [ -d "$DIR/wp-content/uploads" ]; then
  php=$(find "$DIR/wp-content/uploads" -type f -iname '*.php*' ! -name index.php 2>/dev/null)
  count=$(printf '%s' "$php" | grep -c .)
  if (( count > 0 )); then
    echo "   SIGN: $(n $count file files)"
    printf '%s\n' "$php" | head -n 5 | sed 's/^/   /'
    signs=$((signs + 1))
  else
    echo "   0 files"
  fi
else
  echo "   No folder"
fi

echo
if (( signs > 0 )); then
  echo "Result: $(n $signs SIGN SIGNS). Go to incident response."
else
  echo "Result: no SIGNS from these 5 checks. Continue with the checksum and admin account checks."
fi
# Check the access log, the upgrade folder, wp-config.php, and uploads
sudo bash check-signs-wp.sh /var/www/html
# Logs in another location, for example a log per site
sudo bash check-signs-wp.sh /var/www/html /var/log/nginx/example.com.access.log*
# Check the core files and the admin list
cd /var/www/html
sudo -u www-data wp core verify-checksums
sudo -u www-data wp user list --role=administrator --fields=user_login,user_registered
# Core update time: the change time of version.php
date -r wp-includes/version.php '+%Y-%m-%d %H:%M'
Terminal: the signs check finds 5 requests to the upgrade folder, 3 to setup-config.php, a wp-config.php in the upgrade folder, and 1 PHP in uploads. Admin wp-support exists.
The log, the wp-support account, and the sign files are dummy data that we wrote to test the script. The IPs use RFC 5737 documentation blocks. The container clock uses UTC. Source: Rama Digital test in a podman container with the official wordpress:7.1.3-apache and wordpress:cli images, 4 November 2026.
Two columns of signs: web log (requests to the upgrade folder, setup-config.php, HTTP 200 from unknown IPs) and server (wp-config.php in that folder, PHP in uploads, new admin).
Amber column: signs in the web log. Red column: signs on the server. Bottom box: the steps when you find even 1 sign. Source: the Wordfence CVE record, the WordPress 7.1.4 code, and Rama Digital analysis.

Normal visitors and the WordPress update process do not request the upgrade folder over the web. So check every request to /wp-content/upgrade/ from an outside IP. The name setup-config.php comes from the code references in the CVE-2026-119977 record.

To find the core update time, look at the change time of wp-includes/version.php. According to a comment in update-core.php, the update copies this file last. Compare that time with the request times in the log.

Check the result: the script prints "no SIGNS", verify-checksums has no Warning, and the admin list has only people you know. Attackers can delete logs, so no sign does not prove that the site is safe.

When you find even 1 sign, put the site in maintenance mode, then keep the web log, the upgrade folder contents, and wp-config.php as evidence. Change the admin, database, hosting, and SFTP passwords, then change the salts with wp config shuffle-salts so that all login sessions end. Restore from a clean backup from before 28 October, then update to 7.1.4 before the site goes back online.

When the site holds customer data, Indonesian Law 27/2022 on Personal Data Protection requires a written notice within 3 times 24 hours after a personal data leak. Also read the Magento PolyShell flaw CVE-2026-48356.

Example: 1 morning of response for 3 WordPress sites

This simulation uses dummy data. The site names, times, and results are not customer data.

Time (WIB)Dummy siteWhat we checkResultAction
08.00shop-a (7.1.4, automatic update on 28 October)Steps 1 and 5SAFE, but 3 requests to the upgrade folder from an unknown IPIncident response
08.20office-b (6.8.11, automatic updates off)Steps 1 and 2VULNERABLE, upgrade folder OPENStep 4, then update
08.40old-blog (4.6.30, no maintenance)Step 1Outside this CVE, no patchesBack up, then move to 7.1.4 or shut it down
09.10All sites on the nginx serverStep 4The rule uses a plain prefixChange to ^~, check again

The lesson from the simulation: a safe version does not prove a clean site. Shop-a already runs 7.1.4, but step 5 still finds signs from the update window.

Who installs the patch

Patch responsibility changes with the type of service, so this table shows who must act today.

Service typePatch ownerWhat you do
Shared hosting with cPanel or another panelYou, especially when automatic updates are offCheck the version in the dashboard, update, and ask the provider for the access log
Managed WordPress hostingThe providerAsk for written proof of the version and the log check result
Your own VPSYour teamSteps 1 to 5
A site built by an agency or a freelancerAs in the maintenance contractAsk for the version today, then ask for the update schedule

Rama Digital recommendation: do not turn off core automatic updates with no replacement. When you must turn them off, schedule a weekly version check and patch KEV flaws within 24 hours. Patch the server kernel too, for example for the Linux kernel TCP flaw CVE-2026-98432.

Checklist for today

  1. List all WordPress sites. Owner: the web team. Evidence: a list of sites and versions.
  2. Check the version with step 1. Owner: the site admin. Evidence: the script output.
  3. Check the upgrade folder and automatic updates with step 2. Owner: the site admin. Evidence: the script output.
  4. Add the step 4 rule. Owner: the server admin. Evidence: the step 2 script prints "BLOCKED".
  5. Make a backup, then update. Owner: the site admin. Evidence: the version script output after the update.
  6. Look for signs with step 5. Owner: the site admin and the security team. Evidence: the script output, verify-checksums, and the admin list.
  7. Turn core automatic updates back on when they are off with no written reason.

Questions and answers

Do attackers already use this flaw?

Yes. CISA added CVE-2026-119977 to KEV on 3 November 2026 with active exploitation. Wordfence showed 13,330 blocked attacks in 24 hours on 4 November 2026.

My site updated automatically to 7.1.4. Am I safe?

The flaw is closed, but your update window fell in the attack period. Run step 5 and compare the update time with the access log.

The score is only 8.1. Why does this article give a critical warning?

The flaw is in KEV, needs no login, and ends in code execution. CISA records the technical impact as "total".

Can a firewall plugin replace the update?

No. A firewall can block some attacks, but only the update closes the flaw in the core code. Use both.

I use nginx. Which rule is correct?

Use location ^~ /wp-content/upgrade/ { deny all; }. In our test, a plain prefix with no ^~ kept .php files open.

Does the block rule stop WordPress updates?

Not in our test. The update reads the upgrade folder through the file system, not over the web. A partial update and a full package reinstall both worked with the rule on.

My WordPress is below 4.7. Does this flaw affect me?

The CVE record does not list those versions. But those versions get no security patches, so make a backup and move to 7.1.4.

Next step

The limits that still apply: step 4 does not close the flaw, and an update does not remove access that an attacker already used. When your site shows a sign in step 5, or you need help with an audit and recovery, use Vibe Code Rescue. For 1 urgent issue today, book a 30-minute session.

Sources