WordPress Security Research Series: WordPress Security Architecture

Welcome to Part 2 of the WordPress Security Research Beginner Series! If you haven’t had a chance, please review the series introduction blog post for more details on the goal of this series and what to expect as well as Part 1, which covers WordPress Request Architecture and Hooks.

In WordPress Request Architecture and Hooks, we reviewed how requests are handled in WordPress, how those requests relate to hooks, which hooks are interesting to vulnerability researchers, how plugins and themes are loaded, and how they might handle direct requests.

Now that you have a good understanding on how to access and trigger the code you’ll need to test, we are going to review how to identify code that has been appropriately (and inappropriately) protected by developers.

We hope that by providing this beginner series on WordPress vulnerability research, you’ll use the knowledge you’ve gained to participate in the Wordfence Bug Bounty Program, where you can earn up to $31,200 for each vulnerability reported.

By finding and reporting these vulnerabilities, you play a direct role in strengthening the layered defenses that help keep the WordPress ecosystem secure.



Why Is Understanding WordPress Security Architecture Important?

As a vulnerability researcher, you will likely review a lot of code before finding a vulnerability. While you may be familiar with how web applications work, WordPress has its own unique architecture and conventions that require a different approach.

Unlike typical MVC (Model-View-Controller) frameworks with a clear separation of concerns and predictable routing, WordPress is built around a hook-based system. Code executes procedurally, with actions and filters running at various points during a request.

There are no controllers handling specific routes or directories dedicated to application logic. Instead, plugins, themes, and core all share the same global environment and hook into the same execution flow. This can make static analysis and data flow tracking more difficult, as code execution paths are often indirect and spread across multiple files and components.

If you haven’t spent time developing WordPress applications, you might encounter unfamiliar functions and design patterns. This guide will help you quickly recognize WordPress security mechanisms, allowing you to filter out properly secured code and focus on areas more likely to contain vulnerabilities.

Beyond understanding what security functions exist, you will also learn how to test their implementation to see whether they’re applied correctly and whether bypasses are possible.

WordPress vulnerabilities usually involve missing or incomplete use of built-in security functions, and recognizing these patterns—whether through static code review or dynamic testing—will help you quickly identify vulnerabilities.


WordPress Security Ethos

In their official documentation, WordPress presents a set of fundamental ideas to developers, encouraging them to embrace a security mindset during the development process:

  • Don’t trust any data.
  • Rely on the WordPress API.
  • Keep your code up to date.

Additionally, a set of more specific guiding principles are offered:

  1. Never trust user input.
  2. Escape as late as possible.
  3. Escape everything from untrusted sources (e.g., databases and users), third-parties (e.g., Twitter), etc.
  4. Never assume anything.
  5. Sanitization is okay, but validation/rejection is better.

We can take these high level principles and reverse them to develop a vulnerability researcher mindset when analyzing WordPress code:

  • Does this WordPress application (plugin/theme) consume user-supplied data, how is it handled, and for what purpose is it used?
  • Does this WordPress application use WordPress functions and is it using them properly? Does it attempt to roll its own validation, sanitization, escaping, or authorization functionality instead of using the functions provided by WordPress?
  • Does this WordPress application incorporate third-party dependencies that are out-of-date?
  • Are WordPress escaping functions being used throughout? Are they being used correctly?
  • Are assumptions made on how a user will supply input?
  • Are denylists used instead of allowlists?

This is just a short sample of questions you can ask yourself as a security researcher focusing on WordPress.


Core Security Features in WordPress

WordPress provides the Security API to developers, which includes the suite of functions that WordPress core uses to secure its own code. These functions encompass data sanitization, validation, escaping, safe database interaction functions, nonces, and user role and capability checks.

As vulnerability researchers, it’s important to understand what these functions are, their specific purposes, and how developers are expected to use them. By gaining an in-depth knowledge of these functions, we can identify their usage during static analysis.

This enables us to determine if developers are using them correctly or if there are instances where these functions should have been used but were overlooked. This understanding is key to discovering potential vulnerabilities, knowing when to move on to analyzing another piece of code, or fixing security issues in WordPress applications.


Static Analysis Fundamentals

Before we jump into the specifics of the Security API, we’re going to review the fundamentals of static code analysis – this is the act of looking for vulnerabilities in the source code of an application without actually executing it. In other words, you’re gaining an understanding of the code’s logic by reading it and working through what each file, class, and function are used for.

Sometimes, you may only get a “loose” understanding of the code during static analysis and you may need to perform dynamic analysis – which involves executing the code by loading pages, pressing buttons, submitting test input, and setting breakpoints with a debugger and walking through the code’s functionality step-by-step to get a complete picture.


Sources, Sinks, and Data Flow

When performing static analysis, there are three main industry standard concepts you can use during your vulnerability research.

Sources: Sources are essentially where user-controllable inputs come from. They are the entry points into the application. Sources can be things like HTTP method parameters, cookies, HTTP headers, database inputs, and third-party integrations like embedded content or RSS feeds.

Sinks: Sinks are “dangerous” functions, or functions that should not be executed using user-controllable input without authorization, validation, or sanitization. For example, in WordPress, functions like add_user_meta(), add_option(), move_uploaded_file(), file_put_contents(), unlink(), and wp_set_auth_cookie() would be considered sinks. General PHP functions like eval() would also be considered a sink. Refer to the Appendix for a list of sinks.

Data Flow: Data flow is the path that user-supplied data takes from a source to a sink. It’s important to map this data flow to identify potential vulnerabilities. We’ll talk more about how to do this later on in this article.

💡Check out this interactive reference created by one of the top WordPress vulnerability researchers, Stealthcopter, for PHP and WordPress sources, sanitizers, and sinks.


Understanding Input Handling and Data Flow

Proper handling of input is the cornerstone for ensuring the security of WordPress applications. Think of it this way – the functions of a plugin or theme are in a closed system until the mechanisms which interact with the “external world” are implemented into it. If, for example, a plugin developer created a plugin that simply added a static banner to every WordPress post and did nothing else, it would be safe from external influence.

Therefore, to understand where vulnerabilities lie, we must identify any point of external influence – or every single way in which WordPress consumes input. Inputs can be categorized into four main types: Superglobals, Database Inputs, Third-Party Integrations, and Alternative Input Streams.

Superglobals

These are the primary sources of user input in PHP web applications, including WordPress. They cover various types of data, including form submissions, cookies, HTTP headers, and third-party data sources. You can search for these using your IDE (Integrated Development Environment) or a tool like grep to identify where in the code input from users is consumed.

  • $_POST
    • Form Submissions: Typically from HTML forms submitted via HTTP POST.
    • AJAX Requests: Data sent via AJAX to admin-ajax.php via HTTP POST.
    • JSON Payloads: JSON data sent in the body of POST requests.
    • Multipart Form Data: File uploads and other form data.
    • WP Admin Inputs: Data submitted through admin forms, settings pages, and media uploader.
  • $_GET
    • URL Parameters: Data appended to URLs in query strings.
    • AJAX Requests: Data sent via AJAX to admin-ajax.php using GET.
    • Custom Endpoints: Parameters passed to custom REST API endpoints.
  • $_REQUEST
    • Combined GET and POST, and COOKIE Data: Any data sent via GET or POST or stored in cookies. NOTE: Some web hosts remove $_COOKIE from $_REQUEST.
  • $_COOKIE
    • Cookies: Data stored in cookies, often used for session management and preferences.
  • $_FILES
    • File Uploads: Data from file inputs in forms, accessible via the $_FILES array.
  • $_SERVER
    • Server and Environment Data: Information about the server and request environment, including HTTP headers and script paths.
    • HTTP Headers: User-Agent, Referer, X-Forwarded-For, Authorization, Custom Headers.
  • $_SESSION
    • Session Data: Data stored in sessions, if sessions are used by the application.

Searching for the $_GET superglobal in Visual Studio Code
Searching for the $_GET superglobal in Visual Studio Code

Database Inputs

Data stored and retrieved from the WordPress database.

  • Options Table
    • Site Options: Data stored in the wp_options table.
  • User Meta Table
    • User Metadata: Data stored in the wp_usermeta table.
  • Post Meta Table
    • Post Metadata: Data stored in the wp_postmeta table.
  • Custom Tables
    • Plugin/Theme Specific Tables: Data stored in custom database tables.

Using AdminerEvo to view the wp_options WordPress database table
Using AdminerEvo to view the wp_options WordPress database table

Third-Party Integrations, RSS Feeds, and Embedded Content

Data from external sources integrated into WordPress.

  • External APIs
    • API Responses: Data received from external APIs.
  • Webhooks
    • Incoming Webhooks: Data received from webhook calls.
  • Embedded Content
    • oEmbed Data: Content embedded from third-party sources.
    • External Embeds: Content embedded from external sources.
  • RSS Feeds
    • Feed Content: Data imported from RSS feeds.

Alternative Input Streams

Some WordPress application developers may prefer to use alternative input streams for various reasons (e.g., accessing raw input data) to consume user-supplied input.

  • php://
    • PHP offers the ability to access a variety of raw input/output streams via php://. Generally, we’ll see WordPress developers use the php://input read-only stream that allows access to raw POST data. It’s particularly useful for reading JSON, XML, or other payloads where access to raw input data is needed.
  • filter_input()
    • A PHP function that fetches a specific external variable (like a GET parameter) by name and optionally filters it (e.g., ensuring the value of the GET parameter is a string).

Data Validation and Sanitization

The WordPress Security API offers a plethora of data validation and sanitization functions. These functions were created with the intention of providing WordPress application developers with a simple, consistent, and time-tested way of adhering to WordPress security principles.


Validation

Validation is the act of checking input to ensure it’s in the format that is expected. As the WordPress documentation puts it, we want to evaluate a user-supplied value against a pattern and return a valid or invalid result. A simple example is checking an email address supplied via an E-mail Address form field to ensure it contains the correct syntax specified in RFC 5322. That means it can’t be blank, it can’t contain two @ characters, and so on. Validation is one way of filtering out untrusted user-supplied input.

Common Validation Functions

The following are some commonly used validation functions available in the WordPress Security API.

  • is_email(): Checks if an email address is valid.
  • tag_escape(): Despite its name, this function validates a user-supplied string for output as an HTML tag.
  • is_numeric(): Validates that the input is a number.
  • in_array(): Checks whether a value is in an array.

Proper use of WordPress validation functions can help prevent attacks. Vulnerability Researchers will want to follow the data flow from a source to a sink and take note of what validation functions are in use, if any.


Sanitization

Sanitization is less-specific validation. Sanitizing data is the act of removing or filtering out unwanted data from user-supplied input. For example, the sanitize_email() sanitization function strips out characters that are not allowed in email addresses.

This is a good function to dissect in order to compare and better understand validation vs. sanitization. Remember the is_email() function checks a user-supplied email address to see if it is valid and returns a valid/invalid or true/false. On the other hand, the sanitize_email() function takes a user-supplied string (that is presumably supposed to be an email address) and strips out characters that shouldn’t belong, returning a string value without those stripped characters.

Common Sanitization Functions

The following are some commonly used sanitization functions available in the WordPress Security API.

  • sanitize_email(): Removes invalid characters from an email address before it is used or stored in a database.
    • Whitespace (spaces): Removed
    • Control characters (non-printable characters): Removed
    • Special characters not allowed in the local part of an email address: Stripped (e.g., () , : ; < > [ \ ])
    • Invalid characters in domain part: Stripped or corrected (e.g., removing spaces, invalid punctuation)
  • sanitize_file_name(): This function has two objectives: (1) to ensure the filename is compatible with a wide range of operating systems and (2) does not contain certain special characters or character sequences that are considered invalid or require escaping.
    • Whitespace (spaces): Replaced with - (hyphen)
      • Input: my file name.jpg
      • Output: my-file-name.jpg
    • Non-ASCII characters: Converted to ASCII equivalents or removed
      • Input: café.png
      • Output: cafe.png
    • Percent signs (%): Removed
      • Input: 100%free.pdf
      • Output: 100free.pdf
    • Control characters (–31): Removed
      • Input: filex00name.txt
      • Output: filename.txt
    • Special characters (&, #, ?, *, <, >, |, “, ;, :, ,, +, =): Stripped or replaced
      • Input: file&name#1.jpg
      • Output: filename1.jpg
    • Dots (.): Reduced to a single dot (for example, .. is reduced to .)
      • Input: my..file.name.jpg
      • Output: my.file_.name_.jpg
    • Path Traversal sequences:
      • Input: ../../etc/passwd
      • Output: etcpasswd
  • sanitize_text_field(): Strips potentially harmful characters from text fields.
    • HTML tags: Completely removed
      • Input: <script>alert('XSS');</script>
      • Output: (Empty string)
    • Control characters: Removed
      • Input: Hellox00World (where x00 is a null byte)
      • Output: HelloWorld
    • Invalid UTF-8 characters: Completely removed
      • Input: HelloxC0x80World (overlong UTF-8 encoding for null byte)
      • Output: (Empty string)
    • Extra whitespace (spaces): Reduced (trims leading/trailing whitespace and collapses multiple spaces)
    • Newlines, tabs (n, t): Converted to single spaces
      • Input: HellonWorldtTest
      • Output: Hello World Test
  • wp_kses(): A sanitization function that can be customized to only allow a specified set of HTML elements and attributes to pass through. This function is typically used when developers want to allow limited HTML in user inputs, such as in comments or custom fields, while stripping out any potentially harmful tags or attributes. Note that wp_kses() expects at least two function parameters. The first is the input to sanitize and the second is the allowed tags or protocols.
    • Disallowed HTML Tags
      • Input: <div>Hello <strong>World</strong></div>
      • Output: Hello <strong>World</strong>
      • Explanation: The <div> tag is stripped because it’s not included in the allowed tags array, but <strong> is allowed.
    • Disallowed Attributes
      • Input: <a href="http://example.com" style="color:red;">Link</a>
      • Output: <a href="http://example.com">Link</a>
      • Explanation: The style attribute is removed because it’s not in the allowed attributes list for the <a> tag.
    • JavaScript Event Handlers
      • Input: <a href="http://example.com" onclick="alert('XSS')">Link</a>
      • Output: <a href="http://example.com">Link</a>
      • Explanation: The onclick attribute is stripped to prevent XSS attacks.
    • Script/Style Tags
      • Input: <style>body {background-color: red;}</style><p>Text</p>
      • Output: <p>Text</p>
      • Explanation: The <style> tag and its contents are removed because they are considered unsafe.
  • wp_kses_post(): A specialized version of wp_kses() designed to sanitize user-generated content for posts. It allows a broader range of HTML tags and attributes that are typically used in post content, making it ideal for fields requiring rich text (e.g. bold, italic, underline, etc.) while still preventing potentially harmful code from being included.
    • Unsafe HTML Tags
      • Input: <h1>Title</h1><iframe src="http://example.com"></iframe>
      • Output: <h1>Title</h1>
      • Explanation: The <iframe> tag is stripped out because it’s not allowed in standard post content.
    • Unsafe Attributes
      • Input: <img src="image.jpg" onerror="alert('XSS')">
      • Output: <img src="image.jpg">
      • Explanation: The onerror attribute is removed because it could introduce XSS.

Escaping

Escaping is manipulating or filtering out unwanted data prior to using that data in output. It’s similar in concept to sanitization, but instead of acting on input, we’re acting on output. For example, some data might be retrieved from the WordPress database and displayed on a page. Escaping such data prevents vulnerabilities like Cross-Site Scripting.

Common Escaping Functions

The following are some commonly used escaping functions available in the WordPress Security API.

  • esc_html(): Escapes HTML to prevent it from being interpreted as code and should be used when outputting data within HTML elements (e.g., inside a <div>).
    • HTML Tags
      • Input: <strong>Hello World</strong>
      • Output: &lt;strong&gt;Hello World&lt;/strong&gt;
      • Explanation: HTML tags are replaced with their HTML entity equivalents to prevent them from being interpreted as HTML.
    • Special Characters
      • Input: 5 > 3 & 2 < 4
      • Output: 5 &gt; 3 &amp; 2 &lt; 4
      • Explanation: Special characters like >, <, and & are converted to HTML entities (&gt;, &lt;, &amp;).
    • Quotes
      • Input: "Hello" 'World'
      • Output: &quot;Hello&quot; 'World'
      • Explanation: Double and single quotes are replaced with &quot; and &amp#039; respectively.
  • esc_attr(): Escapes data in HTML attributes and should be used when outputting data inside these attributes (e.g., title, alt).
    • HTML Tags
      • Input: <div class="example">Hello World</div>
      • Output: &lt;div class=&quot;example&quot;&gt;Hello World&lt;/div&gt;
      • Explanation: HTML tags and attributes are replaced with their HTML entity equivalents to prevent execution within attributes.
    • Special Characters
      • Input: a & b
      • Output: a &amp; b
      • Explanation: Special characters like & are converted to &amp; to ensure they are safe within HTML attributes.
    • Quotes
      • Input: John's "book"
      • Output: John's &quot;book&quot;
      • Explanation: Single and double quotes are replaced with ' and &quot; respectively to prevent them from breaking the attribute value.
    • Angle Brackets
      • Input: 2 < 3 > 1
      • Output: 2 &lt; 3 &gt; 1
      • Explanation: Angle brackets are replaced with &lt; and &gt; to prevent them from being interpreted as part of an HTML tag.
  • esc_url(): Escapes URLs to ensure they are safe for use in href or src attributes.
    • Unsafe Protocols
      • Input: javascript:alert('XSS')
      • Output: (Empty string, as it removes the entire URL)
      • Explanation: Protocols like javascript: are stripped out entirely.
    • Special Characters
      • Input: http://example.com/test?name=John&age=30
      • Output: http://example.com/test?name=John&age=30
      • Explanation: The & character is converted to & to ensure the URL is safe in HTML.
    • Spaces and Other Non-URL Characters
      • Input: http://example.com/space test
      • Output: http://example.com/space%20test
      • Explanation: Spaces and other invalid characters are percent-encoded.
  • esc_js(): Escapes data for safe inclusion within JavaScript. This function should be used prior to outputting data inside <script> tags or inline JavaScript.
    • HTML Tags
      • Input: <script>alert('XSS')</script>
      • Output: &lt;script&gt;alert('XSS')&lt;/script&gt;
      • Explanation: HTML tags are converted to their entity equivalents, ensuring they are not interpreted as executable code.
    • Quotes
      • Input: "Hello 'World'"
      • Output: &quot;Hello 'World'&quot;
      • Explanation: Double quotes are converted to HTML entities and single quotes are escaped with backslashes.
    • Newline Characters and Carriage Returns
      • Input: HellonWorld
      • Output: Hello\nWorld
      • Explanation: Newlines are escaped to ensure they don’t break the JavaScript string or introduce unwanted behavior.
  • wp_kses() and wp_kses_post(): These functions were mentioned in the Sanitization section. They can also be used for escaping.

WordPress functions involved in validation, sanitization, and escaping are generally used to prevent Cross-Site Scripting vulnerabilities. Check out our blog on How To Find XSS (Cross-Site Scripting) Vulnerabilities in WordPress Plugins and Themes.


Pro-Tip: Dynamically Evaluating Code with WP Shell

Using wp shell, a part of WP-CLI, you can interactively evaluate PHP code from within your WordPress environment. This is a great way for vulnerability researchers to familiarize themselves with WordPress’s core security features. We recommend all new researchers spend some time experimenting with wp_shell and the core security features we’re outlining.

💡Join the Wordfence Bug Bounty Program Discord channel to get access to our WordPress Docker container that includes WP-CLI.

In the following wp shell session, the value of ../../etc/passwd is passed to sanitize_file_name(), which is wrapped in PHP’s var_dump() function. The resulting value as well as its type is returned and shown in the terminal.

Using wp shell and var_dump() to visualize how sanitize_file_name() handles a path traversal sequence.
Using wp shell and var_dump() to visualize how sanitize_file_name() handles a path traversal sequence.


Database Interactions and SQL

The WordPress API makes a large number of database functions available to use so developers can avoid passing untrusted user-supplied input directly into database queries. These are usually either prefixed with add_, update_, delete_, or get_ (e.g., add_option(), update_user_meta(), delete_comment_meta(), or get_comment_meta()) or with a wp_insert, wp_update, or wp_delete (e.g., wp_insert_post(), wp_update_comment(), wp_delete_user()). However, developers often fail to use these functions or use them correctly.

This is evident by the multitude of SQL injection CVEs in WordPress plugins and themes. As a vulnerability researcher, you’ll want to understand what proper use of these functions looks like when performing static analysis.

When it comes to database interactions, WordPress emphasizes a single rule to prevent SQL injection: When there’s a WordPress function, use it.

Though, this isn’t always straightforward to developers. If a predefined function doesn’t fit the developer’s use-case, WordPress makes the $wpdb methods available.

The $wpdb class is a database abstraction layer that simplifies database interactions and ensures developers follow best practices from a security perspective, but they are often used in a way that makes them vulnerable to SQL injection.

The following is an overview of some of the main $wpdb methods you’re likely to see used in production.

Inserts a new row into a custom table.

$wpdb->insert($table, $data, $format);

global $wpdb;
$wpdb-&gt;insert('wp_users', array(
	'name' =&gt; 'John',
	'age' =&gt; 30
), array('%s', '%d'));

Updates existing rows in a custom table based on conditions.

$wpdb->update($table, $data, $where, $format, $where_format)

global $wpdb;
$wpdb-&gt;update('wp_users', array(
	'age' =&gt; 35
), array(
	'name' =&gt; 'John'
), array('%d'), array('%s'));

Deletes rows from a custom table based on conditions.

$wpdb->delete($table, $where, $where_format);

global $wpdb;
$wpdb-&gt;delete('wp_users', array(
	'name' =&gt; 'John'
), array('%s'));

Runs a custom SQL query and returns the results as an array of objects. Note, without the use of $wpdb -> prepare() this method could be vulnerable to SQL Injection.

$wpdb->get_results($query, $output_type);

global $wpdb;
$results = $wpdb-&gt;get_results("SELECT * FROM wp_users WHERE age &gt; 30", OBJECT);

Executes a custom SQL query where the supplied values are parameterized and securely inserted into a SQL query. This is required for the following methods when user-supplied input is accepted to prevent SQL Injection: $wpdb->get_results, $wpdb->query, $wpdb->get_row, and $wpdb->get_col.

$wpdb->prepare($query, $value1, $value2, ...);

global $wpdb;

// Preparing a complex custom query with user input
$age = 30;
$name = 'John';
$query = $wpdb-&gt;prepare(
	"SELECT * FROM wp_users WHERE age = %d AND name = %s",
	$age, $name
);

// Run the query
$results = $wpdb-&gt;get_results($query);

SQL Injection – What to Look For

SQL Injections can be hard to find as there may exist multiple layers of abstraction from the point user input is consumed to where it is used in a SQL query. Additionally, many SQL queries exist that don’t use user-supplied input, but may use variables that need to be traced back to their assignment locations to verify. Because WordPress SQL functions can be used in a variety of ways, there isn’t a single search or regular expression you can use to find these vulnerabilities.

Most SQL queries used in WordPress plugins and themes should be parameterized with $wpdb->prepare(). If you see a pattern like the following, it’s unlikely to be vulnerable to SQL injection.

$post_id = $_GET['post_id'];
...
$wpdb-&gt;prepare( "SELECT * FROM wp_posts WHERE ID = %d", $post_id );

However, $wpdb->prepare() can be used incorrectly (without parameterization). This function only works to prevent SQL injection if placeholders like %d or %s are used, as seen above.

$post_id = $_GET['post_id'];
...
$query = $wpdb-&gt;prepare( "SELECT * FROM wp_posts WHERE ID = $post_id" );
$wpdb-&gt;get_results( $query );

Queries created by concatenating user input with a fixed SQL statement are often vulnerable if no other protections are in place.

// Vulnerable concatenation with user-supplied input
$post_id = $_GET['post_id'];
...
$query = "SELECT * FROM wp_posts WHERE ID = " . $post_id;
$wpdb-&gt;get_results( $query );

Sometimes code can look vulnerable to SQL injection, but other mechanisms are put into place to prevent arbitrary user input from making its way into the query. In the following example, the user-supplied post ID is cast to an integer, preventing arbitrary values from being passed into the SQL query.

// Using (int) cast
$post_id = (int) $_GET['post_id'];
...
$query = "SELECT * FROM wp_posts WHERE ID = " . $post_id;
$wpdb-&gt;get_results( $query );

// --- or ---

// Using intval()
$post_id = intval( $_GET['post_id'] );
...
$query = "SELECT * FROM wp_posts WHERE ID = " . $post_id;
$wpdb-&gt;get_results( $query );

In addition, WordPress utilizes wp_magic_quotes() which automatically adds single quotes () to input from superglobals, so while a SQL query like the one below may initially appear vulnerable to SQL Injection, it would not be. In the following example, the post_id value would be automatically wrapped in single quotes leaving you with a query that looks like SELECT * FROM wp_posts WHERE ID = ''1''.

// Vulnerable concatenation with user-supplied input
$post_id = $_GET['post_id'];
...
$query = "SELECT * FROM wp_posts WHERE ID = ' " . $post_id . "'";
$wpdb-&gt;get_results( $query );

What is esc_sql()?

WordPress provides the esc_sql() function to escape characters in strings that would be used in SQL queries. Think of it as a “string preparation” function, not a security function. In other words, it prepares a string to be placed into a SQL query in a way that doesn’t break the query (for example, by converting single-quotes () into escaped single quotes ().

Using wp shell to visualize how esc_sql() escapes characters within strings.
Using wp shell to visualize how esc_sql() escapes characters within strings.

This function is often used incorrectly by developers to “sanitize” user-supplied values before being used in SQL queries instead of $wpdb->prepare(). While it may mitigate some attacks by escaping these characters, it’s not intended to thwart SQL injection attacks like $wpdb->prepare().

If a user-supplied value is not wrapped in single quotes in a query, esc_sql() as the only form of protection could easily be bypassed considering there are no single quotes needed to escape out of.

The following example would easily be bypassed with a $post_id value of 1 OR 1=1 resulting in a query of SELECT * FROM wp_posts WHERE ID = 1 OR 1=1.

$query = "SELECT * FROM wp_posts WHERE ID = " . esc_sql($post_id);
$wpdb-&gt;get_results( $query );

Authentication, User Roles, Capabilities, and Authorization

WordPress Authentication in a Nutshell

Like most web applications, WordPress allows users, who are defined in the Users section of the administration panel, to authenticate and generate a session for subsequent requests.

The authentication process starts with a user submitting a username and password to wp-login.php. This form submission gets passed to the wp_signon() function, which then calls the wp_authenticate() function and checks the submitted username and password against stored user data in the database. If authentication is successful, the wp_set_auth_cookie() function is called to set the user’s cookie and store the user’s session details. The cookie and session data is then used to authenticate subsequent requests via the wp_validate_auth_cookie() function.

Each unique user session is managed through the wp_usermeta database table. Cookies are cleared and sessions are destroyed via the wp_clear_auth_cookie() function when a user logs out.

Passwords are stored in the database using the wp_hash_password() function.


User Roles

A user role is a set of capabilities. Capabilities define individual types of access a user has within the WordPress environment. There are five default roles that are available within WordPress.

Administrator: Full access to all administrative features and settings.
Editor: Can manage and publish posts, including those of other users.
Author: Can write, edit, and publish their own posts.
Contributor: Can write and edit their own posts but cannot publish them.
Subscriber: Can manage their own profile.

As mentioned above, a user role is a set of capabilities. Here are each of the roles and their associated capabilities.

Capability Administrator Editor Author Contributor Subscriber
read Yes Yes Yes Yes Yes
edit_posts Yes Yes Yes Yes No
publish_posts Yes Yes Yes No No
edit_others_posts Yes Yes No No No
delete_posts Yes Yes Yes No No
manage_options Yes No No No No
moderate_comments Yes Yes No No No
upload_files Yes Yes Yes No No
edit_pages Yes Yes No No No
publish_pages Yes Yes No No No
edit_themes Yes No No No No
install_plugins Yes No No No No
delete_plugins Yes No No No No
activate_plugins Yes No No No No
edit_users Yes No No No No
remove_users Yes No No No No
edit_files Yes No No No No
edit_plugins Yes No No No No
delete_themes Yes No No No No
manage_categories Yes Yes No No No
unfiltered_html Yes Yes No No No
import Yes No No No No

Capabilities, by their names, are self explanatory on the type of access they allow. In the table above, you can see that the Administrator role has all capabilities by default*, while we see fewer and fewer capabilities applied as we go from the Editor role (the 2nd highest role) to the Subscriber role (the lowest-level role).

* WordPress Multisite is a feature that allows a single WordPress instance to run multiple websites. In a Multisite setup, the concept of Super Admin is introduced. In the Multisite case, users with the Administrator role lose capabilities, such as installing plugins or managing themes.


Custom Roles

Custom roles can be defined by themes or plugins by invoking the add_role() function. Custom roles are useful when a set of capabilities not defined by a default role is needed. Capabilities for a given role can be modified using the add_cap() or remove_cap() functions.

Example of Adding a Custom Role:

add_role('custom_role', 'Custom Role', array(
	'read' =&gt; true,
	'edit_posts' =&gt; true,
	'delete_posts' =&gt; false,
));

This code adds a new role, custom_role, with the ability to read and edit posts but not delete them.


Custom Capabilities

Custom capabilities in WordPress allow developers to define specific permissions that are not covered by the default roles and capabilities.

register_post_type( 'my_custom_post', array(
	'label' =&gt; 'Custom Posts',
	'public' =&gt; true,
	'capability_type' =&gt; 'post',
	'capabilities' =&gt; array(
    		'delete_posts' =&gt; 'delete_my_custom_posts',
	),
	'map_meta_cap' =&gt; true,
));

In the above example, a custom post type is registered and given a delete_my_custom_posts capability for the purpose of deleting a post of this specific type. This means that even a user with the standard delete_posts capability will not be able to delete this post.


Authorization

In WordPress, Authorization is done by checking capabilities against the request that is being made by the user. For example, if a request is made to activate a plugin, but the request is sent by a subscriber-level user (defined by the value supplied in the Cookie: HTTP header that is sent in the request), then WordPress will deny the request.

A request is sent by a subscriber-level user to activate a plugin.
A request is sent by a subscriber-level user to activate a plugin.

WordPress denies the request and sends a 403 Forbidden response
WordPress denies the request and sends a 403 Forbidden response

WordPress core authorization check for activating plugins.
WordPress core authorization check for activating plugins on https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-admin/plugins.php#L49

WordPress uses contextual authorization. This means that plugin and theme developers must implement authorization checks within the specific areas of code (e.g., within the function) that need authorization checks before an action is performed by that function.

In the previous section, we provided an example where a plugin implemented a custom capability. In the following code example, we see the current_user_can authorization check being implemented to verify the user’s capabilities before the post is deleted. If the user does not have the delete_my_custom_posts capability, the function exits with wp_die().

function my_plugin_delete_custom_post( $post_id ) {
	// Check if the user can delete this specific custom post
	if ( ! current_user_can( 'delete_my_custom_posts', $post_id ) ) {
    	wp_die( __( 'You do not have sufficient permissions to delete this post.' ) );
	}

	// Perform the deletion
	wp_delete_post( $post_id );
}

The Wordfence Vulnerability Database showcases a number of Missing Authorization vulnerabilities. These vulnerabilities usually stem from a lack of capability check in a function that performs an operation that would result in a loss of confidentiality, integrity, or availability if triggered by an unauthorized user. You can find these by looking at functions within the code (e.g. delete_users()) and checking to see if they have a current_user_can() capability check.


Authorization Flow

No Authorization Check

Authorization Flow - No Authorization

Authorization Check Only

Authorization Flow - Authorization Only

Authorization Check + Nonce Check

Authorization Flow - Authorization and Nonce Check


Nonces

Nonce stands for “number used once”. It is a pseudo-random alphanumeric string that is generated using one of WordPress’s nonce generation functions and embedded within a page or a link from which a request will originate. It can be viewed within the page’s source HTML.

Log Out nonce displayed at the bottom of a browser window.
The Log Out link appends an action-specific nonce in the _wpnonce parameter to ensure that requests to log out are valid and are not forged.

Including a nonce in a request and verifying it at the receiving function ensures that the request is intentional. If implemented and validated correctly, nonces can prevent Cross-Site Request Forgery. However, developers often misuse this feature (or don’t use it at all).

Nonces in WordPress aren’t actually a “number used once” and instead last for up to 24 hours, meaning they can be reused within that time period. In the above example, A nonce is passed in a Log Out request and this nonce is verified before the logout action is executed. This ensures that a threat-actor cannot link http://localhost:8081/wp-login.php?action=logout within their own internet-accessible attacker-controlled page, trick a WordPress user into clicking the link (e.g. via a phishing email), and trigger the logout action on the targeted user’s behalf.

It’s important to understand the correct use of nonces in WordPress because they are often incorrectly implemented. For example, plugin developers often use nonces as an authorization mechanism in functions instead of the current_user_can() function described in the previous section. In the Wordfence blog post titled Authorization vs. Intent: Why You Should Always Verify Both, we detail why this is a bad practice and how it can be leveraged by a threat-actor to perform unauthorized actions.


Nonce Functions and Parameters

WordPress provides developers with a number of nonce generation and verification functions. To prevent Cross-Site Request Forgery, nonces should be generated and placed into requests that perform actions on the WordPress instance (e.g., changing settings or deleting posts) and they should be verified within the function that performs the action before it is performed.

Vulnerability researchers can search for these functions within plugins and themes to see if they are used in conjunction with an authorization mechanism. If they are not, the researcher can search the code to see if the nonce generation function is used within a page that is accessible by a lower-level user. For example, an AJAX action might be hooked to a function that modifies plugin settings and checks for and verifies a nonce, but does not perform an authorization check.

This nonce might be generated and printed on a subscriber-level user’s profile page. This means that a subscriber-level user could modify plugin settings by passing a valid nonce that they have access to.


Nonce Generation Functions

wp_create_nonce(): This function generates a nonce token and returns it as a string. It takes one optional parameter, $action, which is a string that identifies the action being performed.

wp_nonce_field(): This function generates a hidden input field with a nonce token and returns it as a string. It takes two parameters, $action and $name. $action is a string that identifies the action being performed, while $name is the name of the input field.

wp_nonce_url(): This function generates a URL with a nonce added as a query string parameter. It takes two parameters, $actionurl and $action. $actionurl is the URL to which the user will be directed, while $action is a string that identifies the action being performed.


Nonce Verification Functions

wp_verify_nonce(): This function checks the referer for a nonce value. It takes two parameters: $nonce and $action. $nonce is the nonce value, while $action is a string that identifies the action being performed.

check_admin_referer(): This function checks the referer for a WordPress admin screen against the nonce value. It takes two parameters, $action and $query_arg. $action is a string that identifies the action being performed, while $query_arg is the name of the URL parameter that contains the nonce value.

check_ajax_referer(): This function checks the referer for an AJAX request against the nonce value. It takes two parameters, $action and $query_arg. $action is a string that identifies the action being performed, while $query_arg is the name of the URL parameter that contains the nonce value.


REST API Authentication and Authorization

The WordPress REST API handles authentication a bit differently from its standard login-based authentication. It supports not only cookie-based authentication, but also application passwords, and OAuth. Custom REST API endpoints are registered via the register_rest_route() function.

Cookie-Based Authentication: When a user is logged in, REST API requests can be authenticated via the same cookies used for normal WordPress operations. This means a registered rest route (e.g., http://example-wordpress-site.com/wp-json/{rest_route}) is accessible in the same way as any other route.

However, if a valid X-WP-Nonce for that request is not provided, then WordPress will consider the request unauthenticated despite supplying valid authentication cookies in the request. This is because the WordPress REST API implementation has built-in Cross-Site Request Forgery (CSRF) protection. The automated checking of X-WP-Nonce is only relevant for cookie-based authentication.

💡 If a user logged in with the administrator role sends a request to a REST API endpoint, and the request does not contain a valid X-WP-Nonce value, the request will be treated as coming from user ID 0 – or unauthenticated.

Application Passwords: Introduced in WordPress 5.6, application passwords allow users to create unique passwords for external applications accessing the site via REST API. This method doesn’t require creating a user account and sharing the password.

OAuth: OAuth can be implemented to provide secure, token-based authentication for certain applications or third-party integrations.

Every registered REST API endpoint has a request method (e.g. GET, POST, etc.), a callback function (the logic that determines how to handle the request to this endpoint) as well as a permission callback (i.e. permission_callback()) that should return true or false based on authorization logic defined by the developer.

Vulnerabilities in REST API callback functions usually stem from a lack of authorization (i.e. the route’s permission callback returns true and the callback function itself does not perform an authorization check either).

register_rest_route( 'myplugin/v1', '/delete-users', array(
	'methods' =&gt; 'GET',
	'callback' =&gt; 'delete_users_function',
	'permission_callback' =&gt; '__return_true',
) );
An unauthenticated GET request to http://example-wordpress-site.com/wp-json/myplugin/v1/delete-users will delete all users.
register_rest_route( 'myplugin/v1', '/delete-users', array(
	'methods' =&gt; 'GET',
	'callback' =&gt; 'delete_users_function',
	'permission_callback' =&gt; function () {
    	return current_user_can( 'manage_options' );
	},
) );
A GET request to http://example-wordpress-site.com/wp-json/myplugin/v1/delete-users will be denied access unless the supplied cookie and X-WP-Nonce is from a user who has the manage_options capability.

It is also possible for developers to implement their own custom authorization mechanism for REST API routes that should be closely examined for potential bypasses. Here is an example of a poorly implemented authorization check that was easily bypassed.


REST API Authentication and Authorization Flow Logic

REST API Request
   	│
   	├── Using Application Password, OAuth, or Basic Auth?
   	│    	└── Yes → Authenticated User (based on credentials)
   	│
   	├── Using Cookies?
   	│    	└── Yes → Is X-WP-Nonce valid?
   	│            	├── No → User ID 0 (unauthenticated)
   	│            	└── Yes → Authenticated User (from cookie)
   	│
   	└── None of the above? → User ID 0 (unauthenticated)
   	│
Check permission_callback
   	│
   	├── Does permission_callback exist?
   	│    	├── No → ❌ Public endpoint (allow everyone)
   	│    	│
   	│    	└── Yes → Execute permission_callback()
   	│            	│
   	│            	├── Does it perform a capability check?
   	│            	│    	├── Yes (e.g., current_user_can())
   	│            	│    	│   	├── Pass → ✅ Allow access
   	│            	│    	│   	└── Fail → ❌ Deny access (401/403)
   	│            	│
   	│            	│    	└── No (returns true unconditionally)
   	│            	│            	→ ✅ Public endpoint (allow everyone)
   	│
   	└── Proceed to callback function if allowed

Deserialization and JSON Methods

WordPress offers developers the maybe_serialize() and maybe_unserialize() functions, which are simply wrappers around the PHP serialize() and unserialize() functions with an additional check for serialized data before the serialization or deserialization process occurs.

Since WordPress doesn’t provide any security guidance around the usage of these functions, it’s common to see developers who need to work with serialized data use these or their respective PHP functions instead of safe alternatives like PHP’s json_encode() and json_decode() functions.

Deserializing untrusted user input could lead to PHP Object Injection. If a POP chain exists, exploitation of this vulnerability could lead to severe consequences.


Unserialized allowed_classes Option

PHP’s unserialize() has an allowed classes option. Developers who are aware of PHP Object Injection vulnerabilities will often set the allowed_classes option to false to prevent the instantiation of objects. However, this option does not always mitigate PHP Object Injection.

If a serialized payload is passed to unserialize() with the array(‘allowed_classes' => false) option, the resulting object will be an instance of __PHP_Incomplete_Class, which is a placeholder object. This object contains the original malicious property values but isn’t unserialized. If this instance of __PHP_Incomplete_Class is passed to some function that serializes data (like the WordPress update_option() function, which uses maybe_serialize()) it will re-serialize __PHP_Incomplete_Class effectively generating the original payload, and in the update_option() case, the serialized payload will be stored in the database. If that value is later unserialized, you have PHP Object Injection.


Deserialization Flow

User Input (malicious serialized payload)
   	│
   	▼
Stored in database (options, post meta, user meta, etc.)
   	│
   	▼
Retrieved from database
   	│
   	▼
maybe_unserialize()
   	│
   	▼
unserialize()
   	│
   	├── With allowed_classes =&gt; false
   	│   	│
   	│   	▼
   	│   __PHP_Incomplete_Class object created
   	│   	│
   	│   	▼
   	│   Stored again (e.g., via update_option())
   	│   	│
   	│   	▼
   	│   Retrieved and unserialized (without allowed_classes)
   	│   	│
   	│   	▼
   	│   🚨 POP Chain executes → Remote Code Execution (or other impact)
   	│
   	└── Without allowed_classes
           	│
           	▼
    	POP Chain executes:
      	├── __destruct()
      	├── __wakeup()
      	├── __call()
      	▼
    	🚨 Remote Code Execution (or other impact)

Finding PHP Object Injection Vulnerabilities

The unserialize() and maybe_unserialize() functions are sinks. Searching for these functions and following the data flow backwards to a potential source is the best way to find these vulnerabilities in plugin and theme code.


Uploading Files

Allowing the upload of arbitrary files can result in PHP code execution or Cross-Site Scripting (e.g. via SVG files).

WordPress provides the wp_check_filetype() and wp_check_filetype_and_ext() functions to help WordPress plugin and theme developers secure file uploads. Additionally, developers can leverage the upload_mimes filter to modify the WordPress global array of allowed MIME types, which can be retrieved using the get_allowed_mime_types() function.

Finally, the wp_handle_upload() function handles the full file upload process, including sanitizing file names, checking for MIME type (except when the test_type override is set to false), and moving the file to the appropriate directory within the uploads directory.


wp_check_filetype()

The wp_check_filetype() function will return the file extension (string) or false if the MIME type is not allowed.

$filetype = wp_check_filetype( $_FILES['file']['name'] );
if ( ! $filetype['ext'] ) {
	wp_die( 'Invalid file type.' );
}

wp_check_filetype_and_ext()

The wp_check_filetype_and_ext() function checks the actual content of the file (via tmp_name) using PHP’s finfo_file() and the file extension (via name) against get_allowed_mime_types().

$checked = wp_check_filetype_and_ext(
	$_FILES['file']['tmp_name'], // Actual file contents on disk (temporary location)
	$_FILES['file']['name'] // Original filename (for extension checking)
);
if ( ! $checked['ext'] ) {
	wp_die( 'Invalid file type.' );
}

Finding File Upload Vulnerabilities

The omission of these functions in the presence of file upload functionality is a good indicator that an arbitrary file upload vulnerability might be present. Developers often roll their own file upload functionality and with that, their own validation (if any).

In many cases, these validations may be flawed and bypassable. Refer to the Functions Related to File Upload Vulnerabilities section in the Appendix to view sinks related to arbitrary file upload.


Path Traversals and File Inclusions

WordPress does not provide specialized functions solely for preventing path traversal or file inclusion vulnerabilities. Instead, these attacks are typically mitigated through proper sanitization of user-supplied filenames and paths.

The most important function in this context is sanitize_file_name(), which ensures that user-provided filenames are stripped of dangerous characters and path traversal sequences. Additionally, the basename() function can be used to strip directory paths from a filename.


Finding Path Traversal and File Inclusion Vulnerabilities

Researchers should look for the following:

  • User input passed directly to filesystem sink functions (e.g., include(), require(), fopen(), file_get_contents(), etc.) without sanitization.
  • Use of $_GET['file'] or similar superglobals to build file paths.
  • Missing or improper use of sanitize_file_name().
  • Dynamic includes or file access that rely on user-supplied paths.
  • Missing use of basename() when the application expects only a filename but concatenates it into a file path.
// Vulnerable
$file = $_GET['file'];
include( ABSPATH . '/themes/mytheme/templates/' . $file );

// Safe
$file = sanitize_file_name( $_GET['file'] );
$path = ABSPATH . '/themes/mytheme/templates/' . $file;
if ( file_exists( $path ) ) {
	include( $path );
}

Server-Side Request Forgery (SSRF)

WordPress provides a set of HTTP request functions via its HTTP API. These are used by plugin and theme developers to make outbound HTTP requests. There are a set of “normal” functions that do not have any built-in restrictions along with a set of matching safe functions that contain host and protocol restrictions.


Unsafe Functions (no URL restrictions)

These functions do not restrict requests to destinations like internal IPs or localhost. Developers using these functions must implement their own validation.

  • wp_remote_get()
  • wp_remote_post()
  • wp_remote_request()
  • wp_remote_head()

Safe Functions (with host and protocol restrictions):

For each unsafe HTTP request function, there exists a safe function. These functions prevent requests to localhost, private IP ranges (e.g., 192.168.x.x, 10.x.x.x, 172.16.x.x), loopback addresses (e.g., 127.0.0.1), IPv6 private and local ranges, and non-http:// or https:// URL schemes.

  • wp_safe_remote_get()
  • wp_safe_remote_post()
  • wp_safe_remote_request()
  • wp_safe_remote_head()

Finding Server-Side Request Forgery (SSRF) Vulnerabilities

Researchers should look for the use of wp_remote_*() functions to find SSRF vulnerabilities.

// Vulnerable
$response = wp_remote_get( $_GET['url'] );

// Safe
$url = esc_url_raw( $_GET['url'] ); 
$response = wp_safe_remote_get( $url );

💡WordPress is aware that the wp_safe_remote_*() functions do not block requests to the 169.254.0.0/16 link-local range, which is often used by cloud service providers for metadata services.


Logic Flaws

WordPress provides a multitude of security functions to help developers protect their code from well known web application vulnerabilities. But regardless of how many security functions are used, some vulnerabilities can’t be solved with validation, sanitization, or escaping. Sometimes developers introduce mistakes in the logic of their code, a.k.a. logic flaws.

These flaws occur when a developer writes code that unintentionally allows unauthorized actions even if WordPress security functions are present.

Consider the following example. Can you spot the flaw?

function arm_check_user_cap( $arm_capabilities = '', $is_ajax_call = '', $nonce_required = 0 ) {
	global $arm_global_settings;

	$errors  = array();
	$message = '';
	if ( $is_ajax_call == true || $is_ajax_call == '1' || $is_ajax_call == 1 ) {
    	if ( ! current_user_can( $arm_capabilities ) ) {
        	$errors[]            	= esc_html__( 'Sorry, You do not have permission to perform this action.', 'armember-membership' );
        	$return_array        	= $arm_global_settings-&gt;handle_return_messages( @$errors, @$message );
        	$return_array['message'] = $return_array['msg'];

        	echo json_encode( $return_array );
        	exit;
    	}
	}

	$wpnonce           	= isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field($_REQUEST['_wpnonce']) : '';
	$arm_verify_nonce_flag = wp_verify_nonce( $wpnonce, 'arm_wp_nonce' );
	if ( ! $arm_verify_nonce_flag &amp;&amp; ( !empty( $wpnonce ) || !empty($nonce_required) ) ) {
    	$errors[]            	= esc_html__( 'Sorry, Your request can not process due to security reason.', 'armember-membership' );
    	$return_array        	= $arm_global_settings-&gt;handle_return_messages( @$errors, @$message );
    	$return_array['message'] = $return_array['msg'];
    	echo json_encode( $return_array );
    	exit;
	}
}

This function is responsible for checking whether a user has the capability to perform an action and verifying a nonce. But notice how these checks are scoped:

  • Authorization (current_user_can()) only happens if $is_ajax_call is true.
  • The nonce check only happens if $nonce_required is set (or a nonce is present).

This creates a situation where:

  • If $is_ajax_call is false, the capability check is skipped entirely.
  • If $nonce_required is false (or unset), the nonce check may also be bypassed.

As a result, if a developer or attacker can invoke this function with $is_ajax_call = false and $nonce_required = 0, neither the capability check nor the nonce check will run—meaning the function provides no meaningful protection in that scenario.


Conclusion

In this post, we reviewed the security architecture of WordPress from a vulnerability research perspective. We covered the functions WordPress provides to secure plugins and themes, how they’re intended to work, and where developers often use them incorrectly—or forget to use them at all. We also explored what to look for when reviewing code for vulnerabilities.

Combining this knowledge with WordPress Request Architecture and Hooks, we understand that WordPress doesn’t follow the same design patterns as typical MVC web applications.

Instead, it relies on a hook-based system where code executes procedurally across plugins, themes, and core.

Understanding both the request and security architecture of WordPress helps you, as a vulnerability researcher, follow the flow of data, identify where protections should be applied, and spot code that’s more likely to contain vulnerabilities.

Vulnerabilities in WordPress aren’t always caused by missing sanitization, escaping, or validation. Sometimes they’re the result of logic flaws or broken assumptions.

As a researcher, your job is to recognize these patterns, filter out secure code, and focus your attention on the code that matters so you can net the biggest value for your time with the Wordfence Bug Bounty Program.

Stay tuned for Part 3 of this series, where we will guide you through setting up your WordPress vulnerability research lab, applying a step-by-step research methodology, and using a variety of tools and resources to help you find your first (or next) vulnerability.

Before Part 3 is released, we encourage you to get started with a local WordPress installation and apply this knowledge with existing plugins and themes.

Stay tuned for the next part in our beginner vulnerability research series, and don’t forget to register as a researcher for our Wordfence Bug Bounty Program and join our Discord to engage with the Wordfence Threat Intelligence team and other security researchers!


Definitions

Authentication: The process of verifying the identity of a user.

Authorization: The process of verifying if a user has permission to perform an action.

Cookies: Data stored on the client side to manage user sessions and other information.

Data Flow: The movement of data through the application from sources to sinks.

Escaping: The process of cleaning, filtering, or encoding data to ensure it is treated as content, not as code.

Pseudo-random: A number or string that appears to be random, but has been generated using deterministic and repeatable methods.

Sanitization: The process of cleaning, filtering, or encoding input data to ensure it is safe to use.

Sources: Points where data enters the application (e.g., user input).

Sinks: Points where data is used in a potentially unsafe manner.


Appendix

Typical WordPress Sinks

Functions Related to Privilege Escalation Vulnerabilities

  • add_user_meta()
  • update_user_meta()
  • wp_insert_user()
  • wp_update_user()
  • wp_set_password()
  • reset_password()
  • $user->add_role()
  • $user->set_role()

Functions Related to Arbitrary Option Update to Privilege Escalation Vulnerabilities

  • add_option()
  • update_option()

Functions Related to File Upload Vulnerabilities

  • copy()
  • move_uploaded_file()
  • file_put_contents()
  • $wp_filesystem->put_contents()
  • unzip_file()
  • wp_handle_upload() with test_type => false

Functions Related to File Deletion Vulnerabilities

  • unlink()
  • wp_delete_file()
  • $wp_filesystem->delete()

Functions Related to Authentication Bypass Vulnerabilities

  • wp_set_auth_cookie()
  • wp_set_current_user()

Functions Related to Remote Code Execution Vulnerabilities

  • eval()
  • exec()
  • system()
  • shell_exec()

The post WordPress Security Research Series: WordPress Security Architecture appeared first on Wordfence.

Leave a Comment