Too Much Escaping Backfires, Allows Shortcode-Based XSS Vulnerability in Contact Form Entries WordPress Plugin

🎉 Did you know we’re running a Bug Bounty Extravaganza again?

Earn over 6x our usual bounty rates, up to $10,000, for all vulnerabilities submitted through May 27th, 2024 when you opt to have Wordfence handle responsible disclosure!

On February 24th, 2024, during our second Bug Bounty Extravaganza, we received a submission for a stored Cross-Site Scripting (XSS) vulnerability in Contact Form Entries, a WordPress plugin with more than 60,000+ active installations. The vulnerability enables threat actors with contributor-level permissions or higher to inject malicious web scripts into pages using the plugin’s shortcode.

Props to Krzysztof Zając who discovered and responsibly reported this vulnerability through the Wordfence Bug Bounty Program. This researcher earned a bounty of $132.00 for this discovery during our Bug Bounty Program Extravaganza. Our mission is to Secure the Web, so we are proud to continue investing in vulnerability research like this and collaborating with researchers of this caliber through our Bug Bounty Program. This demonstrates that we are not only committed to investing in making the WordPress ecosystem more secure, but also the entire web.

All Wordfence Premium, Wordfence Care, and Wordfence Response customers, as well as those using the free version of our plugin, are protected against any exploits targeting this vulnerability by the Wordfence firewall’s built-in Cross-Site Scripting protection.

We contacted the CRM Perks Team on February 29, 2024, and received a response on March 1, 2024. After providing full disclosure details, the developer released a patch on March 6, 2024. We would like to commend the CRM Perks Team for their prompt response and timely patch, which was released on the next day.

We urge users to update their sites with the latest patched version of Contact Form Entries, which is version 1.3.4, as soon as possible.

Vulnerability Summary from Wordfence Intelligence

Description: Database for Contact Form 7, WPforms, Elementor forms <= 1.3.3 – Authenticated(Contributor+) Stored Cross-Site Scripting via shortcode
Affected Plugin: Database for Contact Form 7, WPforms, Elementor forms
Plugin Slug: contact-form-entries
Affected Versions: <= 1.3.3
CVE ID: CVE-2024-2030
CVSS Score: 6.4 (Medium)
Researcher/s: Krzysztof Zając
Fully Patched Version: 1.3.4
Bounty Award: $132.00

The Database for Contact Form 7, WPforms, Elementor forms plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin’s shortcode(s) in all versions up to, and including, 1.3.3 due to insufficient input sanitization and output escaping on user supplied attributes. This makes it possible for authenticated attackers with contributor-level and above permissions to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

Technical Analysis

Contact Form Entries is a plugin designed to allow WordPress users to save form submissions from many other popular contact form plugins to the WordPress database. It provides a shortcode ([vx-entries]) that displays form entries in a table when added to a WordPress page. However, insecure implementation of the plugin’s shortcode functionality allows for the injection of arbitrary web scripts into these pages. Examining the code reveals that the entries_shortcode() function in the vxcf_form class does not correctly sanitize the user-supplied ‘font-size’ input. This makes it possible to inject attribute-based Cross-Site Scripting payloads via the ‘font-size’ attribute.


Code snippet from the entries_shortcode() function

<table <?php echo $class.' '.$css ?> cellspacing="0" <?php echo esc_html($table_id) ?>>

Code snippet from the leads-table.php template file

The ‘font-size’ attribute value is used for id, which is not relevant to the vulnerability.

The structure of the table id html attribute is enclosed within two quotation marks as follows: id="{value}". The most significant problem and vulnerability is caused by the fact that the second esc_html() function escapes the quotation marks, meaning that the user input value will no longer be enclosed within quotation marks. In such cases, the space is interpreted as the beginning of a new attribute, allowing attribute-based XSS even though the attribute in question has been escaped. This is because esc_attr() only escapes the <, >, &, ” and ‘ (less than, greater than, ampersand, double quote and single quote) characters, but in this case a quote is not necessary to break out of the attribute because the attribute was not surrounded by quotes in the first place.

This makes it possible for threat actors to carry out stored XSS attacks. Once a script is injected into a page or post, it will execute each time a user accesses the affected page. While this vulnerability does require that a trusted contributor account is compromised, or a user be able to register as a contributor, successful threat actors could steal sensitive information, manipulate site content, inject administrative users, edit files, or redirect users to malicious websites which are all severe consequences.

An Example of How the Improper Escaping Would Render

Let’s take an example where the shortcode id attribute is used as an HTML attribute, similarly as in the plugin.

$id = 'id="' . esc_attr( $atts['id'] ) . '"';
echo '<table ' . esc_html( $id ) . '></table>';

An attacker can provide an id with the following XSS payload:

$atts['id'] = 'value onmouseover=alert(/XSS/) data-more=';

The value of $id is as follows:

id="value onmouseover=alert(/XSS/) data-more="

But after that the esc_html() function escapes the quotation marks in the $id, it results in the following value where the quotes are escaped:

id=&quot;value onmouseover=alert(/XSS/) data-more=&quot;

As a result, the following table will be displayed in the post:

<table id="&quot;value" onmouseover="alert(/XSS/)" data-more="&quot;"></table>

The original quotation marks were changed to &quot;. The attributes, including the ‘onmouseover’ attribute, would then automatically be interpreted upon accessing the page with the injected attribute. This demonstrates the potentially malicious XSS, which in our example is an alert script function which is often used for testing XSS vulnerabilities.

Disclosure Timeline

February 24, 2024 – We receive the submission of the stored Cross-Site Scripting (XSS) vulnerability in Contact Form Entries via the Wordfence Bug Bounty Program.
February 29, 2024 – We validate the report and confirm the proof-of-concept exploit.
February 29, 2024 – We initiate contact with the plugin vendor asking that they confirm the inbox for handling the discussion.
March 1, 2024 – The vendor confirms the inbox for handling the discussion.
March 5, 2024 – We send over the full disclosure details. The vendor acknowledges the report and begins working on a fix.
March 6, 2024 – The fully patched version of the plugin, 1.3.4, is released.


In this blog post, we detailed a stored Cross-Site Scripting (XSS) vulnerability within the Contact Form Entries plugin affecting versions 1.3.3 and earlier. This vulnerability allows authenticated threat actors with contributor-level permissions or higher to inject malicious web scripts into pages that execute when a user accesses an affected page. The vulnerability has been fully addressed in version 1.3.4 of the plugin.

We encourage WordPress users to verify that their sites are updated to the latest patched version of Contact Form Entries.

All Wordfence users, including those running Wordfence Premium, Wordfence Care, and Wordfence Response, as well as sites running the free version of Wordfence, are fully protected against this vulnerability.

If you know someone who uses this plugin on their site, we recommend sharing this advisory with them to ensure their site remains secure, as this vulnerability poses a significant risk.

Did you enjoy this post? Share it!


1 Comment
  • Just because something is "good practice" and "you should do it" doesn't mean you're free from understanding what the abstraction abstracts away - you must understand it, because it's a requirement for building the correct mental model of the functionality. Also, testing is king - the best way to verify that your security holds is to try and break through it (i.e. prove it's true by earnestly trying to falsify it - and failing). You'd think this sort of thing should've been immediately obvious in the output........ But to be fair and temper this a bit? I've ALSO forgotten that you can DO THAT via the regex syntax. So - throwing SMALL stones, I guess? :D I'd like my windows are REINFORCED glass, but they're still glass in the end... Thank you for publishing these analysies - if nothing else, it's a great reminder that it can be VERY EASY to miss things, and that I'm just as human.