Understanding XML-RPC in WordPress: Abuse, Detection, and Protection

Introduction

XML-RPC has been part of WordPress core for many years. It is enabled by default and provides a remote interface that allows external clients to interact with a WordPress site programmatically.

Despite this, XML-RPC is frequently treated as if it were a vulnerability. Many security guides recommend disabling it outright, and automated scanners often flag its presence as a risk. This reaction usually stems from how XML-RPC has been abused in the past, not from what it actually is.

XML-RPC is not malicious. It is an alternate interface to WordPress functionality. It allows authenticated users to create posts, upload media, edit content, and perform other actions without using the browser-based wp-admin interface. In that sense, it is simply another entry point into the application.

The key issue is not the existence of the endpoint itself. The real concern is authentication. XML-RPC accepts credentials and processes requests outside of the traditional login form. If authentication is weak, poorly monitored, or left unprotected, XML-RPC becomes a convenient surface for automated abuse. If authentication is properly enforced, it behaves like any other legitimate interface.

In this article, we will examine what XML-RPC is, why it is often considered risky, how plugins such as Jetpack interact with it, what authenticated XML-RPC requests look like in practice, how the endpoint can be abused at scale, and how it can be protected effectively without disrupting legitimate functionality.

The objective is not to promote disabling XML-RPC by default. The objective is to understand it well enough to secure it appropriately.

What XML-RPC Is and How WordPress Uses It

XML-RPC is a remote procedure call protocol that allows one system to invoke functions on another system over HTTP. Requests are formatted in XML and sent to a single endpoint. In WordPress, that endpoint is: /xmlrpc.php.

When a request is sent to this file, WordPress parses the XML payload, determines which method is being called, validates authentication if required, and executes the corresponding internal function.

At a high level, XML-RPC provides a way to interact with WordPress without using the browser-based admin interface. Instead of submitting forms in wp-admin, a client sends structured HTTP requests that describe the action to perform.

Historically, XML-RPC existed to support:

  • Remote publishing tools
  • Mobile applications
  • Desktop blog editors
  • Pingbacks and trackbacks

Before the REST API was introduced, XML-RPC was the primary method for programmatic interaction with WordPress.

How Authentication Works

XML-RPC uses a stateless authentication model. When a method requires authentication, the username and password are included in the request itself. Each request stands alone. There is no login session and no browser cookie involved.

For example, creating a post through XML-RPC requires:

  • A method name such as wp.newPost
  • A blog identifier
  • A username
  • A password
  • A structured set of post fields

If the credentials are valid and the authenticated user has sufficient capability, the request succeeds. If not, the server returns an error.

This is an important point. XML-RPC does not bypass WordPress capability checks. It ultimately routes through the same core functions that wp-admin uses. The difference is the interface, not the permission model.

The Endpoint Itself

The presence of /xmlrpc.php does not imply compromise or misconfiguration. It is part of WordPress core. When accessed without a proper XML payload, it typically returns a message indicating that it only accepts POST requests.

The risk arises when this endpoint is left exposed without strong authentication controls, monitoring, or rate limiting.

Why XML-RPC Has a Reputation for Risk

XML-RPC is often treated as a security liability, but that reputation comes from how it has been abused, not from any inherent flaw in the protocol itself.

XML-RPC abuse is rarely the initial compromise vector. It is typically leveraged after credential theft, password reuse, or application password exposure. Its value lies in automation and scale, not in bypassing authentication controls.

There are several reasons why XML-RPC attracts attention from attackers and defenders alike.

It Accepts Direct Credentials

Unlike the REST API, which relies on cookie-based sessions or application passwords, XML-RPC historically accepted a username and password directly in each request.

This makes it straightforward to automate. A script can send repeated POST requests to /xmlrpc.php with different credential combinations and observe the responses. If authentication fails, WordPress returns a structured error. If authentication succeeds, the requested method executes.

The endpoint itself does not limit how frequently this can occur. Rate limiting, lockouts, or two-factor enforcement must be implemented separately.

It Is Stateless

Each XML-RPC request is self-contained. There is no browser session, no CSRF token, and no login page interaction. From an automation perspective, this makes it efficient.

This design was practical for remote publishing tools. It is also convenient for credential abuse.

system.multicall

XML-RPC supports a method called system.multicall. This allows multiple method calls to be bundled into a single HTTP request.

From a legitimate perspective, this improves efficiency. From an abuse perspective, it allows many authentication attempts or actions to be packed into one request. Basic request-counting defenses can miss the fact that several method calls were executed inside a single POST.

It Exposes Powerful Methods

XML-RPC can:

  • Create and edit posts
  • Upload media
  • Modify comments
  • Retrieve user information
  • Assign taxonomy
  • Schedule content

All of these actions are subject to capability checks. However, once authentication succeeds, the interface allows significant control over the site.

In other words, XML-RPC is not dangerous because it allows unauthenticated access. It is dangerous when an attacker successfully authenticates.

It’s Predictably Present

Because XML-RPC is bundled with WordPress core and enabled by default, it is predictable. Attackers do not need to guess the endpoint. They can assume /xmlrpc.php exists unless explicitly disabled.

That predictability makes it a common target in automated scans.

The common recommendation to disable XML-RPC entirely comes from these historical abuse patterns. However, disabling the endpoint is not the only option. The real issue is ensuring that authentication through XML-RPC is properly protected, monitored, and rate-limited.

How Jetpack Interacts with XML-RPC

One of the most common concerns about disabling XML-RPC is whether it will break functionality provided by plugins. Jetpack is frequently mentioned in this context.

Jetpack uses XML-RPC, but it does not use it in the same way that a human user or a script would.

Jetpack Does Not Authenticate as a User

When a normal XML-RPC request is made to create a post or upload media, it includes a username and password. WordPress validates those credentials and then executes the requested action on behalf of that user. Jetpack does not operate this way.

Instead of authenticating as a WordPress user, Jetpack establishes a site-level trust relationship with WordPress.com during its connection process. Once connected, requests are authenticated using signed tokens and shared keys. WordPress verifies the signature and the site identity before executing the request.

In practical terms, this means:

  • Jetpack does not send a username and password.
  • Jetpack does not trigger the standard user authentication flow.
  • Jetpack is not subject to user-level two-factor authentication challenges.

Jetpack requests are authenticated, but they are not user-authenticated.

Why This Matters for Security Controls

Security plugins such as Wordfence can enforce two-factor authentication for XML-RPC login attempts. These protections are designed to intercept user authentication attempts made through XML-RPC.

Because Jetpack does not authenticate as a user, those controls do not apply. From the perspective of WordPress’ user authentication system, no login attempt is occurring. This distinction is important.

If XML-RPC is disabled entirely, Jetpack functionality that depends on it may stop working. If XML-RPC remains enabled but user authentication through it is protected with two-factor authentication and rate limiting, Jetpack can continue functioning normally while user-level abuse is mitigated.

A Separate Authentication Surface

It is also important to understand that Jetpack’s authentication model does not exist in core WordPress without the plugin. Jetpack introduces its own site-level authentication mechanism. That mechanism operates independently of XML-RPC username and password authentication.

As a result, XML-RPC two-factor enforcement does not interfere with Jetpack, because Jetpack does not rely on that authentication path.

The takeaway is straightforward. XML-RPC can be protected at the user authentication layer without disrupting legitimate, token-based service integrations.

Demonstration: Creating a Post via XML-RPC

To understand why XML-RPC is powerful, it helps to see a practical example. The following demonstrates how an authenticated request can create a post directly through /xmlrpc.php using a simple cURL command.

This is not an exploit. It is legitimate remote publishing functionality.

Assume the following:

  • The site is accessible at https://example.com
  • The user has permission to publish posts

The request below uses the wp.newPost method.

curl -s -X POST https://example.com/xmlrpc.php \
  -H "Content-Type: text/xml" \
  --data '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
  <methodName>wp.newPost</methodName>
  <params>

    <param>
      <value><int>1</int></value>
    </param>

    <param>
      <value><string>USERNAME</string></value>
    </param>

    <param>
      <value><string>PASSWORD</string></value>
    </param>

    <param>
      <value>
        <struct>

          <member>
            <name>post_title</name>
            <value><string>SPAMMY POST</string></value>
          </member>

          <member>
            <name>post_content</name>
            <value><string>This is the spammy post content.</string></value>
          </member>

          <member>
            <name>post_status</name>
            <value><string>publish</string></value>
          </member>

          <member>
            <name>post_date</name>
            <value>
              <dateTime.iso8601>20111111T00:00:00</dateTime.iso8601>
            </value>
          </member>

          <member>
            <name>terms_names</name>
            <value>
              <struct>
                <member>
                  <name>category</name>
                  <value>
                    <array>
                      <data>
                        <value><string>Spam</string></value>
                      </data>
                    </array>
                  </value>
                </member>
              </struct>
            </value>
          </member>

        </struct>
      </value>
    </param>

  </params>
</methodCall>'
Bash

If authentication succeeds and the user has sufficient capability, WordPress will create and publish the post immediately. The server responds with a structured XML payload containing the newly created post ID:

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <params>
    <param>
      <value>
      <string>60</string>
      </value>
    </param>
  </params>
</methodResponse>
Bash

Several important observations follow from this example:

  • The entire operation occurs without accessing wp-admin.
  • The request is stateless. Credentials are supplied directly in the payload.
  • The post date can be assigned freely, enabling posts to appear months or years in the past. While modification timestamps remain accurate, this can make timeline-based review and manual cleanup significantly harder for less experienced administrators.
  • Taxonomy can be assigned at creation time.
  • The request can be automated easily.

From a functionality standpoint, this is efficient and legitimate. From a security standpoint, it demonstrates why XML-RPC becomes a high-impact surface when authentication is compromised.

Abuse Scenarios: When XML-RPC Becomes a High-Impact Surface

The previous example demonstrated that XML-RPC can create and publish content remotely with full control over fields such as title, content, taxonomy, and publish date. That functionality is legitimate. The risk emerges when authentication is compromised and the interface is used at scale.

The issue is not that XML-RPC allows post creation. The issue is that it allows automated, scriptable post creation once valid credentials are obtained.

Credential Abuse

Because XML-RPC accepts credentials directly in the request payload, it has historically been used in brute force and credential stuffing campaigns.

If an attacker obtains:

  • A reused password
  • A leaked application password
  • Or valid credentials through phishing

They do not need access to wp-admin. They can authenticate directly against /xmlrpc.php and begin issuing method calls.

Unlike a browser login flow, XML-RPC authentication is stateless. Each request includes credentials. This makes it straightforward to automate both authentication attempts and post-authentication actions.

Automation at Scale

The cURL example shown earlier can be converted into a simple script in Python, Bash, or another language. Once scripted, it can:

  • Generate hundreds or thousands of posts
  • Randomize titles and content
  • Assign categories
  • Backdate posts across months or years
  • Repeat until detected

Because the endpoint is predictable and the request format is structured, this process can be fully automated.

For an inexperienced site owner reviewing posts chronologically, backdated spam may blend into older content. While post_modified timestamps remain accurate, many users rely on publish date sorting during cleanup. Automated distribution across the timeline increases the likelihood that malicious content will persist unnoticed.

Media Upload Abuse

XML-RPC also exposes methods such as wp.uploadFile. With valid credentials, an attacker can upload media directly to the site.

Depending on server configuration and allowed file types, this could include:

  • Spam images
  • Phishing content
  • Malicious SVG files if enabled
  • Content intended to trigger stored cross-site scripting in misconfigured themes or plugins

Multicall Amplification

The system.multicall method allows multiple method invocations in a single HTTP request. This can increase efficiency for legitimate clients. It can also amplify abuse.

For example, multiple post creation attempts or authentication attempts can be bundled into one request. Basic request-counting defenses may see a single POST while multiple actions were executed internally.

This feature does not bypass authentication. It increases the impact of weak authentication controls.

XML-RPC becomes dangerous when authentication is weak, credentials are reused, or application passwords are poorly managed. It is a high-leverage interface. Once access is obtained, automation multiplies its impact.

Detection and Visibility: What XML-RPC Looks Like in Logs

Detection can be more difficult than many administrators expect. XML-RPC activity is often not surfaced in common WordPress activity log plugins such as Jetpack activity logs, WP Activity Log, or similar tools. Those plugins typically record content changes, user logins, and administrative actions, but they may not clearly indicate that those actions originated from XML-RPC.

As a result, a post created via XML-RPC can appear indistinguishable from one created through wp-admin, and may not appear in an activity log at all.

This is why web server logs become critical.

Because XML-RPC is a programmatic interface, abuse often appears as ordinary HTTP traffic unless you know what to look for.

At the web server level, XML-RPC activity is straightforward. Every request targets the same endpoint:

POST /xmlrpc.php
Bash

A typical access log entry may look like this:

172.18.0.5 - - [15/Feb/2026:08:15:17 -0800] "POST /xmlrpc.php HTTP/1.1" 200 188 "-" "curl/8.7.1"
Bash

Several characteristics stand out:

  • The method is always POST.
  • The path is always /xmlrpc.php.
  • The response size can vary significantly depending on the method invoked.
  • The user agent may indicate automation such as curl, Python requests, or other scripting libraries.
  • Successful requests often return HTTP 200 even when the XML payload contains an application-level error.

It is important to note that HTTP status codes alone do not tell the full story. An authentication failure may still return a 200 response with an XML <fault> element in the body. Detection systems that rely only on HTTP status codes may miss repeated login failures.

Distinguishing Legitimate and Suspicious Activity

Not all XML-RPC traffic is malicious. Legitimate usage may come from:

  • Mobile applications
  • Remote publishing tools
  • Jetpack or other service integrations

However, suspicious patterns often include:

  • High frequency POST requests to /xmlrpc.php
  • Repeated authentication failures within short intervals
  • Large request payloads when file uploads are involved
  • User agents that do not resemble browsers or known integrations
  • Bursts of activity followed by long idle periods

If system.multicall is used, multiple method executions may be hidden within a single HTTP request. From the access log perspective, this appears as one request. Application-level logging or deeper inspection may be required to see the true volume of actions.

Interaction with WP-Cron

In many environments, a request to XML-RPC may trigger WordPress to spawn a background request to /wp-cron.php. In logs, this can appear immediately before or after the XML-RPC entry:

POST /wp-cron.php?doing_wp_cron=...
POST /xmlrpc.php
Bash

This behavior is normal. WordPress checks for scheduled tasks during request handling and may fire cron asynchronously. It should not be mistaken for compromise, but it can add noise during analysis.

What to Monitor

At a minimum, administrators should monitor:

  • Volume of POST requests to /xmlrpc.php
  • Frequency of authentication failures
  • Use of application passwords
  • Sudden spikes in content creation
  • Media uploads occurring outside expected workflows

Correlating access logs with WordPress activity logs provides much better visibility than relying on one source alone.

Detection is often where XML-RPC security efforts fall short. Blocking the endpoint is one option, but understanding how it is being used provides stronger defensive control.

Scenario: Cleaning Up Backdated XML-RPC Spam

To demonstrate how XML-RPC abuse can complicate cleanup, we intentionally created severals hundred posts using authenticated XML-RPC requests. Each post was randomly backdated, even though the attack occurred in 2026.

+-----+-----------------------+--------------------------+---------------------+-------------+
| ID  | post_title            | post_name                | post_date           | post_status |
+-----+-----------------------+--------------------------+---------------------+-------------+
| 422 | Free crypto wallet    | free-crypto-wallet-27    | 2025-04-27 08:30:27 | publish     |
| 192 | Discount inside       | discount-inside-14       | 2025-04-27 08:30:04 | publish     |
| 380 | Discount inside       | discount-inside-31       | 2025-04-22 08:30:23 | publish     |
| 336 | Winner selected       | winner-selected-29       | 2025-04-20 08:30:18 | publish     |
| 80  | Buy Cheap meds        | buy-cheap-meds-3         | 2025-04-20 08:29:53 | publish     |
| 180 | Discount inside       | discount-inside-12       | 2025-04-19 08:30:03 | publish     |
| 260 | Free crypto wallet    | free-crypto-wallet-15    | 2025-04-17 08:30:11 | publish     |
Bash

From the front end, these posts appeared to be months old and randomly posted. For an inexperienced administrator reviewing content chronologically intermingled with legitimate posts, they might be difficult to spot.

However, logs told a different story.

Step 1: Identify Suspicious Activity in Logs

From the web server access logs, we observed XML-RPC requests around the following time:

172.18.0.5 - - [15/Feb/2026:08:30:30 -0800] "POST /xmlrpc.php HTTP/1.1" 200 189 "-" "python-requests/2.32.5"
172.18.0.5 - - [15/Feb/2026:08:30:30 -0800] "POST /xmlrpc.php HTTP/1.1" 200 189 "-" "python-requests/2.32.5"
172.18.0.5 - - [15/Feb/2026:08:30:30 -0800] "POST /xmlrpc.php HTTP/1.1" 200 189 "-" "python-requests/2.32.5"
172.18.0.5 - - [15/Feb/2026:08:30:31 -0800] "POST /wp-cron.php?doing_wp_cron=1771173031.1492769718170166015625 HTTP/1.1" 200 31 "-" "WordPress/6.9; https://mytest.site"
172.18.0.5 - - [15/Feb/2026:08:30:31 -0800] "POST /xmlrpc.php HTTP/1.1" 200 189 "-" "python-requests/2.32.5"
172.18.0.5 - - [15/Feb/2026:08:30:31 -0800] "POST /xmlrpc.php HTTP/1.1" 200 189 "-" "python-requests/2.32.5"
172.18.0.5 - - [15/Feb/2026:08:30:31 -0800] "POST /xmlrpc.php HTTP/1.1" 200 189 "-" "python-requests/2.32.5"
172.18.0.5 - - [15/Feb/2026:08:30:31 -0800] "POST /wp-cron.php?doing_wp_cron=1771173031.7595069408416748046875 HTTP/1.1" 200 31 "-" "WordPress/6.9; https://mytest.site"
172.18.0.5 - - [15/Feb/2026:08:30:31 -0800] "POST /xmlrpc.php HTTP/1.1" 200 189 "-" "python-requests/2.32.5"
172.18.0.5 - - [15/Feb/2026:08:30:31 -0800] "POST /xmlrpc.php HTTP/1.1" 200 189 "-" "python-requests/2.32.5"
Bash

Converting this to server time gave us a modification window centered around:

2026-02-15 16:30:30
Bash

That timestamp became our pivot point.

Step 2: Confirm with WP-CLI

Before touching the database, we confirmed one of the suspicious post IDs using WP-CLI:

wp post get 260
Bash

Output:

| ID              | 260                    |
| post_date       | 2025-04-17 08:30:11    |
| post_modified   | 2026-02-15 16:30:11    |
| post_author     | 2                      |
| post_title      | Free crypto wallet     |
Bash

Important observation:

  • post_date was 2025
  • post_modified was 2026

This confirms that the publish date was manipulated, but the modification timestamp accurately reflected when the record was inserted. The post_author corresponds to the user simpleadmin.

This is why post_date cannot be trusted during forensic cleanup. post_modified and post_modified_gmt are reliable.

Step 3: Locate All Posts Modified in That Window

We queried the database using the compromised author and the modification window (within +/- 5 minutes of 2026-02-15 16:30:30):

MariaDB [db]> SELECT 
    p.ID,
    p.post_title,
    p.post_date,
    p.post_modified,
    u.user_login AS author
FROM wp_posts p
JOIN wp_users u ON p.post_author = u.ID
WHERE u.user_login = 'simpleadmin'
  AND p.post_modified BETWEEN 
      '2026-02-15 16:25:00' 
      AND 
      '2026-02-15 16:35:00'
  AND p.post_type = 'post'
ORDER BY p.post_modified ASC;
Bash

Result:

+-----+-----------------------+---------------------+---------------------+-------------+
| ID  | post_title            | post_date           | post_modified       | author      |
+-----+-----------------------+---------------------+---------------------+-------------+
|  64 | Free crypto wallet    | 2025-08-24 08:29:26 | 2026-02-15 16:29:26 | simpleadmin |
|  66 | Buy Cheap meds        | 2025-03-22 08:29:51 | 2026-02-15 16:29:52 | simpleadmin |
|  68 | Buy Cheap meds        | 2025-11-01 08:29:52 | 2026-02-15 16:29:52 | simpleadmin |
|  70 | Limited time offer    | 2025-12-27 08:29:52 | 2026-02-15 16:29:52 | simpleadmin |
|  72 | Free crypto wallet    | 2026-01-12 08:29:52 | 2026-02-15 16:29:52 | simpleadmin |
|  74 | Limited time offer    | 2025-06-21 08:29:52 | 2026-02-15 16:29:52 | simpleadmin |
.
.
.

201 rows in set (0.001 sec)
Bash

All 201 posts were backdated randomly, but clearly modified in 2026 within a short burst. This pattern strongly indicates automation.

Step 4: Move Posts to Trash First

Rather than immediately deleting records, we safely moved them to the trash:

MariaDB [db]> UPDATE wp_posts p
    JOIN wp_users u ON p.post_author = u.ID
    SET p.post_status = 'trash'
    WHERE u.user_login = 'simpleadmin'
      AND p.post_type = 'post'
      AND p.post_modified BETWEEN 
          '2026-02-15 16:25:00' 
          AND 
          '2026-02-15 16:35:00';
Bash

Output:

Query OK, 201 rows affected (0.008 sec)
Rows matched: 201  Changed: 201  Warnings: 0
Bash

We verify by running the query again with the status column:

MariaDB [db]> SELECT 
    p.ID,
    p.post_title,
    p.post_date,
    p.post_modified,
    p.post_status,
    u.user_login AS author
FROM wp_posts p
JOIN wp_users u ON p.post_author = u.ID
WHERE u.user_login = 'simpleadmin'
  AND p.post_modified BETWEEN 
      '2026-02-15 16:25:00' 
      AND 
      '2026-02-15 16:35:00'
  AND p.post_type = 'post'
ORDER BY p.post_modified ASC;
Bash

Output confirms 201 posts are in trash:

This step allows review before permanent deletion.

+-----+-----------------------+---------------------+---------------------+-------------+-------------+
| ID  | post_title            | post_date           | post_modified       | post_status | author      |
+-----+-----------------------+---------------------+---------------------+-------------+-------------+
|  64 | Free crypto wallet    | 2025-08-24 08:29:26 | 2026-02-15 16:29:26 | trash       | simpleadmin |
|  66 | Buy Cheap meds        | 2025-03-22 08:29:51 | 2026-02-15 16:29:52 | trash       | simpleadmin |
|  68 | Buy Cheap meds        | 2025-11-01 08:29:52 | 2026-02-15 16:29:52 | trash       | simpleadmin |
|  70 | Limited time offer    | 2025-12-27 08:29:52 | 2026-02-15 16:29:52 | trash       | simpleadmin |
|  72 | Free crypto wallet    | 2026-01-12 08:29:52 | 2026-02-15 16:29:52 | trash       | simpleadmin |
|  74 | Limited time offer    | 2025-06-21 08:29:52 | 2026-02-15 16:29:52 | trash       | simpleadmin |
|  76 | Winner selected       | 2026-01-16 08:29:52 | 2026-02-15 16:29:53 | trash       | simpleadmin |
|  78 | Free crypto wallet    | 2025-09-18 08:29:53 | 2026-02-15 16:29:53 | trash       | simpleadmin |
.
.
.
201 rows in set (0.003 sec)
Bash

Step 5: Permanently Delete the Trashed Posts

After confirming the scope, we removed them along with associated metadata:

MariaDB [db]> DELETE p, pm
    FROM wp_posts p
    LEFT JOIN wp_postmeta pm ON pm.post_id = p.ID
    JOIN wp_users u ON p.post_author = u.ID
    WHERE u.user_login = 'simpleadmin'
      AND p.post_status = 'trash'
      AND p.post_type = 'post'
      AND p.post_modified BETWEEN 
          '2026-02-15 16:25:00' 
          AND 
          '2026-02-15 16:35:00';
Bash

Output:

Query OK, 403 rows affected (0.006 sec)
Bash

The additional affected rows were associated wp_postmeta records.

Takeaways

If cleanup had relied on post_date, there likely would have been missed during chronological review.

By pivoting on:

  • Access log timestamps
  • post_modified
  • Known compromised accounts

We were able to:

  • Identify the full scope of the event
  • Confirm manipulation
  • Remove affected records safely
  • Avoid deleting legitimate historical content

This is the operational reality of XML-RPC abuse.

Protecting XML-RPC Without Breaking Functionality

At this point, the pattern should be clear. XML-RPC itself is not the problem. Weak or unprotected authentication is.

The correct response is not necessarily to disable the endpoint, but to enforce stronger authentication controls on it.

Several security plugins provide mechanisms to do this, with different approaches and tradeoffs.

Enforcing Two-Factor Authentication on XML-RPC

Plugins such as Wordfence and Wordfence Login Security provide the ability to enforce two-factor authentication for XML-RPC login attempts.

When enabled, any XML-RPC method that requires user authentication will require a valid one-time password in addition to the username and password.

If the request does not include a valid 2FA code, it fails.

For example, attempting to create a post via cURL with XML-RPC 2FA enforcement enabled produces a structured XML fault:

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
  <fault>
    <value>
      <struct>
        <member>
          <name>faultCode</name>
          <value><int>403</int></value>
        </member>
        <member>
          <name>faultString</name>
          <value><string>Incorrect username or password.</string></value>
        </member>
      </struct>
    </value>
  </fault>
</methodResponse>
Bash

From the server’s perspective:

  • HTTP status remains 200.
  • The failure is embedded in the XML response.
  • The request does not execute.

This prevents credential-only abuse while preserving the endpoint itself.

Comparing Two Approaches: Wordfence vs Two-Factor

The official two-factor plugin from WordPress.org takes a more restrictive stance. When two-factor authentication is enabled for a user, XML-RPC authentication is blocked entirely for that user, unless an application password is used.

Application passwords function as long-lived API keys. They do not require two-factor authentication at the time of use and can be reused indefinitely until revoked. If one is exposed through logs, source code, backups, or version control, it grants persistent access without triggering an OTP challenge. In practice, a leaked application password tied to a privileged account can be just as dangerous as a compromised primary password.

In practical terms:

  • Username and password authentication via XML-RPC fails.
  • Only application passwords are permitted for remote access.

This approach is simple and effective. However, it limits flexibility.

Wordfence provides a more granular option. XML-RPC can still be used with standard credentials as long as a valid one-time password is appended to the password field. In other words, a custom application can authenticate using:

password + current OTP
Bash

This allows:

  • Remote publishing
  • Custom automation
  • Scripted integrations

without requiring an application password.

From an operational standpoint, this can be advantageous in controlled environments where custom tools are used.

Conclusion: Secure the Surface, Do Not Fear It

XML-RPC is not a hidden vulnerability. It is an alternate authentication surface built into WordPress core. It exposes powerful capabilities, but it does not bypass permission checks or create new privilege paths.

The risk arises when authentication is weak or unmanaged.

If XML-RPC is left unprotected, an attacker with a stolen password or leaked application password can bypass the browser login flow and operate entirely through /xmlrpc.php. Automation magnifies that impact. A single compromised account can generate thousands of posts, uploads, or changes without ever touching wp-admin.

The correct response is not reflexive removal. It is governance.

Strong defenses include:

  • Enforcing two-factor authentication for XML-RPC logins
  • Restricting and auditing application passwords
  • Monitoring POST traffic to /xmlrpc.php
  • Rate-limiting repeated authentication attempts

Jetpack and other token-based integrations continue functioning because they do not rely on user-level XML-RPC authentication. Properly configured controls protect against credential abuse without breaking legitimate use cases.

XML-RPC should be treated like any other entry point into your application. When monitored and protected, it behaves predictably. When ignored, it becomes a convenient automation interface for attackers.

The endpoint is not the problem. Unprotected authentication is.

Comments

Leave a Reply