Vulnerability research 05.08.2026 Arni Hardarson & Damian Gomez 7 min read

Pre-auth stored XSS in Formidable Forms.

During a client engagement, we found that an unauthenticated visitor could upload active SVG content through Formidable Forms Pro and have it served inline from the WordPress origin. Formidable Forms 6.33 added SVG sanitisation and safer file delivery.

Some client findings belong only to the environment where they were discovered. Others expose a problem in a shared dependency and deserve to travel further. This was the second kind.

The assessed site used Formidable Forms Pro for public forms with file-upload fields. While testing the boundary between an anonymous form visitor and the site's uploaded-file handling, we found a path from unauthenticated SVG upload to stored JavaScript on the site's own origin.

The client remains anonymous in this write-up. Form IDs, field IDs, attachment IDs, hostnames, and the weaponised payload have also been removed. The technical behaviour was reproduced with a benign proof of concept and reported privately to the vendor.

Why the product reach matters

On 5 August 2026, WordPress.org listed 300,000+ active installations for Formidable Forms, while BuiltWith identified 265,613 live websites using what it classifies as Formidable Pro Form. BuiltWith uses its own fingerprinting and counting methods, so its figure is an external estimate rather than vendor telemetry.

Those figures describe product reach, not vulnerable exposure. Our finding required Formidable Pro, a public form containing a file-upload field, that field being configured to accept SVG, and a privileged user opening the uploaded file URL. We did not measure how many installations met those conditions.

Root cause: extension validation is not content safety

SVG is an XML-based image format, but it is not necessarily passive image data. An SVG document can contain scripts, event handlers, external references, and other active content. If a site accepts an untrusted SVG and later renders it as a document on the application's origin, the upload becomes code.

The public upload handler was not itself the root cause: accepting files from anonymous form visitors was an intended feature. The failure was treating an allowed extension and MIME type as sufficient evidence that the file was safe to render. The SVG's active markup survived upload unchanged, and the delivery path later treated the stored attachment as an ordinary image.

Three controls were missing from the observed chain:

  • Content sanitisation: executable SVG elements and attributes were not removed before storage.
  • Safe delivery: the active document was returned with Content-Disposition: inline instead of being forced to download.
  • Origin containment: the response came from the WordPress origin without a restrictive CSP, giving surviving JavaScript the application's same-origin authority.

This analysis is based on the shipped behaviour we measured and the vendor's published remediation. We did not have the commercial Formidable Pro PHP source needed to attribute the failure to a specific server-side function or line of code.

On the affected configuration, an anonymous visitor could send a multipart upload to the Formidable Dropzone action:

POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: multipart/form-data; boundary=...

action=frm_submit_dropzone
form_id=<PUBLIC_FORM_ID>
field_id=<SVG_UPLOAD_FIELD_ID>
file=@proof.svg;type=image/svg+xml

The handler accepted the SVG and returned a WordPress attachment ID. The uploaded file could then be retrieved through Formidable's protected-file route:

/frm_file/<base64("id:ATTACHMENT_ID|filename:proof.svg")>

Base64 encoding made the route syntactically opaque, but it did not make the attachment reference secret. The uploader knew both the attachment ID returned by the server and the filename it had supplied.

From uploaded image to same-origin script

The file-delivery response completed the chain. In our testing, Formidable returned the stored SVG with:

HTTP/2 200
Content-Type: image/svg+xml
Content-Disposition: inline; filename=proof.svg

<svg xmlns="http://www.w3.org/2000/svg">
  ... attacker-controlled active content ...
</svg>

There was no restrictive Content Security Policy on the response. Because the browser rendered the SVG inline from the same scheme, host, and port as WordPress, JavaScript inside it executed in the WordPress origin.

For controlled validation, we first used a benign SVG that replaced a text element with document.domain. That established script execution without changing application state:

<svg xmlns="http://www.w3.org/2000/svg" width="300" height="120">
  <text x="10" y="30">SVG proof of concept</text>
  <script>
    document.querySelector('text').textContent =
      'Script executed on: ' + document.domain;
  </script>
</svg>

This was a stored-XSS condition: the attacker placed active content on the server, received a stable URL, and could deliver that URL to another user. Uploading and storing the payload required no account.

Impact in an administrator's browser session

The browser's same-origin policy normally prevents one site from reading or controlling another. Here, the attacker-controlled document was delivered by the WordPress site itself. When a logged-in administrator opened the SVG URL, same-origin requests carried the administrator's session automatically, including when the authentication cookie was not readable by JavaScript.

We demonstrated that the SVG could request a WordPress REST nonce from /wp-admin/admin-ajax.php?action=rest-nonce and then use the WordPress REST API with the administrator's capabilities. In controlled testing, that was sufficient to create a new administrator account.

The new administrator account was a durable privilege gain. We stopped testing at that point: we did not attempt plugin installation, server-side code execution, or host compromise, and do not claim those as demonstrated outcomes. We are also not publishing the administrator-creation JavaScript here.

Impact and constraints

We are not assigning a public severity score in this write-up. The concrete result is clearer than a label: an unauthenticated attacker could store same-origin JavaScript, but exploitation required a privileged user to open its URL. With a logged-in WordPress administrator as the victim, we demonstrated creation of a new administrator account.

  • Attacker access: no WordPress account was required to upload the SVG through the public form.
  • Configuration required: a public Formidable Pro file-upload field had to permit SVG files.
  • User interaction: a logged-in privileged user had to open the uploaded SVG URL.
  • Observed privilege: administrator account creation was demonstrated when the victim was a WordPress administrator.
  • Confirmed version: Formidable Forms and Formidable Pro 6.31. We did not claim untested intermediate versions were affected.
  • Availability impact: none was demonstrated directly by the XSS.

“Pre-auth” describes the attacker's ability to place the payload, not a zero-interaction compromise. Execution still depended on a logged-in privileged user opening the stored SVG URL.

The vendor fix

Formidable Forms 6.33 introduced two complementary controls. According to the vendor's 6.33 security changelog:

  • uploaded SVG files are filtered to remove unsafe HTML during upload; and
  • protected SVG file URLs are downloaded rather than displayed in a new browser tab.

The first control removes active content before storage. The second changes the dangerous rendering behaviour that allowed any surviving script to execute as a same-origin document. Together they address both sides of the chain we reported.

The important part of the fix is that it does not depend on a single check being perfect. Sanitisation addresses the content, while attachment delivery addresses the browser execution context. An unsafe construct would need to survive the first control and then overcome the second before the original same-origin XSS path could return.

We did not perform a source-level review of the remediation. Formidable's public GitHub repository contains the Lite plugin, and the public 6.32.1-to-6.33 comparison does not expose the SVG security changes. Our description of the fix therefore follows the vendor's published changelog rather than attributing it to a public commit.

Serving active formats from a separate cookieless origin and applying a restrictive CSP, such as a sandbox with scripts disabled, can add further defence in depth. Neither should replace content sanitisation and safe download behaviour.

Disclosure timeline

  • 27 June 2026: the issue was confirmed during an authorised client engagement and reproduced with benign and impact-validation payloads.
  • 7 July 2026: we contacted Formidable Forms support to ask for the appropriate security-reporting channel. The team replied that day.
  • 8 July 2026: the full disclosure package was delivered privately via WeTransfer. The vendor replied in the disclosure thread that day.
  • 14 July 2026: Formidable Forms 6.33 was released with SVG sanitisation and protected-file download hardening.
  • 4 August 2026: the vendor sent a further reply in the disclosure thread.
  • 5 August 2026: follow-up correspondence continued, and this write-up was prepared after a patched release was publicly available.

Thank you to the Formidable Forms team for addressing the issue.

What operators should do

Upgrade Formidable Forms and Formidable Pro to 6.33 or later. Review public file-upload fields and remove SVG from their permitted extensions unless the business requirement is clear. Treat previously uploaded SVG files as active content and review or replace them where practical.

If an immediate upgrade is not possible, disable SVG uploads on public forms and ensure uploaded SVGs are forced to download rather than rendered inline. A web application firewall may reduce commodity probing, but it is not a dependable substitute for correcting storage and delivery behaviour.

For maintainers, file validation needs to follow what a browser can do with the content, not merely whether the extension belongs to an “image” format. Upload-time sanitisation, attachment delivery for active formats, restrictive response policy, and origin separation each remove a different assumption from this chain.

An uploaded file is not inert just because its extension says “image”.

References

What we took from the engagement

The initial question was local: could an anonymous visitor safely upload a file through this client's form? Following the file beyond the upload handler—to storage, retrieval, browser rendering, and privileged same-origin actions—turned a configuration test into a product vulnerability.

That is the value of testing the whole trust boundary. The interesting security property was not simply whether the server accepted .svg. It was what the application and browser did with the file afterwards.

Research

More from the team.

Browse all research or contact us about a security assessment.