{"componentChunkName":"component---src-templates-post-js","path":"/resources/blog/cve-2020-10560-ossn-arbitrary-file-read/","result":{"data":{"wordpress":{"post":{"id":"cG9zdDo3MTY3","title":"CVE-2020-10560 – OSSN Arbitrary File Read","slug":"cve-2020-10560-ossn-arbitrary-file-read","date":"2020-04-16T12:46:29","modifiedGmt":"2021-01-10T12:53:56","content":"\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"939\" height=\"310\" src=\"https://cms.immersivelabs.com/content/uploads/2020/06/picture-1-1.png\" alt=\"\" class=\"wp-image-8097\" srcset=\"https://cms.immersivelabs.com/content/uploads/2020/06/picture-1-1.png 939w, https://cms.immersivelabs.com/content/uploads/2020/06/picture-1-1-300x99.png 300w, https://cms.immersivelabs.com/content/uploads/2020/06/picture-1-1-768x254.png 768w\" sizes=\"(max-width: 939px) 100vw, 939px\" /><figcaption>Source: https://www.businessinsider.com/coronavirus-email-scam-covid-19-phishing-false-information-who-cdc-2020-2?r=US&amp;IR=T#check-the-senders-email-domain-and-see-if-it-matches-the-website-of-the-organization-they-say-they-work-for-then-check-the-urls-included-in-the-email-1</figcaption></figure>\n\n\n\n<p>Open source platforms, although extremely useful and popular, can sometimes be prone to vulnerabilities. By allowing users to access the code that creates the platforms, these free software resources open themselves up for easier vulnerability testing by cyber professionals and hobbyists, as well as malicious actors.</p>\n\n\n\n<p>Immersive Labs’ Director of Cyber Threat Research, Kev Breen, recently uncovered a weakness in a tool dubbed the Open Source Social Network. This vulnerability would enable “arbitrary file read” – allowing an attacker to read any file in the system, whether permitted or not. There were a couple of obstacles to overcome during this process, which eventually involved writing a custom crypto cracking tool… but more on that later.</p>\n\n\n\n<h2>What is the Open Source Social Network?</h2>\n\n\n\n<p>The Open Source Social Network (OSSN) is pretty self-explanatory: it’s an open source social media platform that a user hosts themself. Reasonably easy to download and get running, it’s written in PHP, uses a MySQL backend, and has just shy of half a million downloads listed on its <a href=\"https://www.opensource-socialnetwork.org/\" target=\"_blank\" rel=\"noreferrer noopener\">main site</a>.</p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"939\" height=\"362\" src=\"https://cms.immersivelabs.com/content/uploads/2020/06/picture-2-1.png\" alt=\"\" class=\"wp-image-8098\" srcset=\"https://cms.immersivelabs.com/content/uploads/2020/06/picture-2-1.png 939w, https://cms.immersivelabs.com/content/uploads/2020/06/picture-2-1-300x116.png 300w, https://cms.immersivelabs.com/content/uploads/2020/06/picture-2-1-768x296.png 768w\" sizes=\"(max-width: 939px) 100vw, 939px\" /><figcaption>Source: https://www.businessinsider.com/coronavirus-email-scam-covid-19-phishing-false-information-who-cdc-2020-2?r=US&amp;IR=T#check-the-senders-email-domain-and-see-if-it-matches-the-website-of-the-organization-they-say-they-work-for-then-check-the-urls-included-in-the-email-1</figcaption></figure>\n\n\n\n<h2>The scanner</h2>\n\n\n\n<p>The source code for OSSN is free to download either from the main site or from the OSSN GitHub. As it’s a PHP application, it’s relatively easy to read and keep track of what’s happening. That being said, there is a lot of code here. We ran a quick pass over it using an open source static code analyser to see if we could find any bugs.</p>\n\n\n\n<h2>The results</h2>\n\n\n\n<p>A progpilot (static PHP analyzer) scan only takes a couple of minutes to complete, so we immediately had some pretty interesting results. Check it out.</p>\n\n\n<p style=\"font-family: courier;\">{<br><span style=\"padding-left: 25px;\">&#8220;source_name&#8221;: [</span><br><span style=\"padding-left: 50px; color: #880000;\">&#8220;$file_get_contents_return&#8221;</span><br><span style=\"padding-left: 25px;\">],</span><br><span style=\"padding-left: 25px;\">&#8220;source_line&#8221;: [</span><br><span style=\"padding-left: 50px; color: #880000;\">316</span><br><span style=\"padding-left: 25px;\">],</span><br><span style=\"padding-left: 25px;\">&#8220;source_column&#8221;: [</span><br><span style=\"padding-left: 50px; color: #880000;\">10492</span><br><span style=\"padding-left: 25px;\">],</span><br><span style=\"padding-left: 25px;\">&#8220;source_file&#8221;: [</span><br><span style=\"padding-left: 50px; color: #880000;\">&#8220;\\/tmp\\/ossn\\/components\\/OssnComments\\/ossn_com.php&#8221;</span><br><span style=\"padding-left: 25px;\">],</span><br><span style=\"padding-left: 25px;\">&#8220;sink_name&#8221;:<span style=\"color: #880000;\"> &#8220;echo&#8221;</span>,</span><br><span style=\"padding-left: 25px;\">&#8220;sink_line&#8221;: <span style=\"color: #880000;\">316</span>,</span><br><span style=\"padding-left: 25px;\">&#8220;sink_column&#8221;: <span style=\"color: #880000;\">10492 </span>,</span><br><span style=\"padding-left: 25px;\">&#8220;sink_file&#8221;: <span style=\"color: #880000;\">&#8220;\\/tmp\\/ossn\\/components\\/OssnComments\\/ossn_com.php&#8221;</span>,</span><br><span style=\"padding-left: 25px;\">&#8220;vuln_name&#8221;:<span style=\"color: #880000;\"> &#8220;xss&#8221;</span>,</span><br><span style=\"padding-left: 25px;\">&#8220;vuln_cwe&#8221;:<span style=\"color: #880000;\"> &#8220;CWE_79&#8221;</span>,</span><br><span style=\"padding-left: 25px;\">&#8220;vuln_id&#8221;: <span style=\"color: #880000;\">&#8220;bd6478779f63431a516932bc0fe193e97ff73a84e6710cb644d88ba036f5bbbe&#8221;</span>,</span><br><span style=\"padding-left: 25px;\">&#8220;vuln_type&#8221;:<span style=\"color: #880000;\"> &#8220;taint-style&#8221;</span><br>},</span></p>\n<p>The scan threw up a potential XSS (cross-site) vulnerability in a component that seemed to be related to comments in the network. It tells us it’s on line 316 of the source code, so we headed there to take a closer look. What was going on?</p>\n<p style=\"font-family: courier;\"><b>case</b> <span style=\"color: #880000;\">&#8216;staticimage&#8217;</span>:<br><span style=\"padding-left: 25px;\">$image = base64_decode(input(<span style=\"color: #880000;\">&#8216;image&#8217;</span>));</span><br><span style=\"padding-left: 25px;\"><b>if</b>(!<b>empty</b>($image)) {</span><br><span style=\"padding-left: 50px;\">$file = ossn_string_decrypt(base64_decode($image));</span><br><span style=\"padding-left: 50px;\">header(&#8216;<span style=\"color: #880000;\">content-type: image/jpeg</span>&#8216;);</span><br><span style=\"padding-left: 50px;\">$file = rtrim(ossn_validate_filepath($file), &#8216;/&#8217;);</span><br><span style=\"padding-left: 50px;\"><b>if</b>(is_file($file)) {</span><br><span style=\"padding-left: 75px;\"><b>echo</b> file_get_contents($file);</span><br><span style=\"padding-left: 50px;\">} <b>else</b> {</span><br><span style=\"padding-left: 75px;\">ossn_error_page();</span><br><span style=\"padding-left: 50px;\">}</span><br><span style=\"padding-left: 25px;\">} <b>else</b> {</span><br><span style=\"padding-left: 50px;\">ossn_error_page();</span><br><span style=\"padding-left: 25px;\">}</span><br><span style=\"padding-left: 25px;\"><b>break</b>;</span></p>\n<p>From the source code we discovered that there wasn’t in fact an XSS vulnerability, but an echo that was reading data from a file. It was setting image content types and seemed to be validating file paths. For the keen-eyed amongst you, there’s also an encryption function here, which we’ll get to later.</p>\n<p>First, we needed to see if it was possible to control the file path <span style=\"font-family: courier; color: #880000;\">$file</span>. If that could be done then we were looking at local file inclusion or, at the very least, file read. From reading the source code, there is a case statement with different options. We identified that the case option ‘staticimage’ needs to evaluate to TRUE for the vulnerable code to be reached. Let’s dig deeper.</p>\n<h2>‘staticimage’</h2>\n<p>Using a docker-compose template, we started a new instance with a clean database and some generic accounts. Once the application got up and running, we needed to find where this function and case were called in the code. The above script gave us a few clues to work with.</p>\n<ul>\n<li>It’s in a comment</li>\n<li>It’s image related</li>\n<li>The path contains ‘staticimage’</li>\n<li>There is a Base64 string</li>\n</ul>\n<p>Burp felt a little heavy-handed for this fairly straightforward task, so we used Dev Tools on Chrome instead, setting a filter for ‘staticimage’.</p>\n<p>At this stage, there was just one demo post on the social network. We knew the bug was something to do with comments, so we took a look at the comments section on the post.</p>\n<p>There’s an option to add an image to a comment. We selected an image to upload to see what would happen, and before posting it…</p>\n<p>Before even hitting send on our comment, we hit all the requirements on the above list. This is just a preview that has sent a request to the server, stored the image, and rendered the preview in the front end.</p>\n<p>Once it’s been previewed, the image is stored on the server and can be accessed directly without being authenticated to the OSSN platform. Bingo.</p>\n<p>From here, we knew we could control the Base64 string from the attacker&#8217;s side and that it was somehow being used to construct a file path that was then being echoed out to the page.</p>\n<h2>The crypto</h2>\n<p>So what was that ‘ossn_string_decrypt’ we referenced right at the outset actually doing? The application took the Base64 string from the URL image parameter and Base64 decoded it twice. The resulting output was then passed to this function:</p>\n<p style=\"font-family: courier;\"><b>function <span style=\"color: #880000;\">ossn_string_decrypt</span></b>($string = <span style=\"color: #880000;\">&#8216; &#8216;</span>, $key = <span style=\"color: #880000;\">&#8216; &#8216;</span>) {<br><span style=\"padding-left: 25px;\"><b>if</b> (<b>empty</b>($string)) {</span><br><span style=\"padding-left: 50px;\"><b>return false</b>;</span><br><span style=\"padding-left: 25px;\">}</span><br><span style=\"padding-left: 25px;\"><b>if</b> (<b>empty</b>($key)) {</span><br><span style=\"padding-left: 50px;\">$key = ossn_site_settings(&#8216;<span style=\"color: #880000;\">site_key</span>&#8216;);</span><br><span style=\"padding-left: 25px;\">}</span><br><span style=\"padding-left: 25px;\">$key = ossn_string_encrypt_key_cycled($key);</span></p>\n<p><span style=\"padding-left: 25px;\">$size = openssl_cipher_iv_length(&#8216;<span style=\"color: #880000;\">bf-ecb</span>&#8216;);</span><br><span style=\"padding-left: 25px;\">$mcgetvi = openssl_random_pseudo_bytes($size);</span><br><span style=\"padding-left: 25px; color: #999999;\">//note mcrypt and now this acting mcrpyt adds the spaces to make 16 bytes if its less then 16 bytes</span><br><span style=\"padding-left: 25px; color: #999999;\">//you can use trim() to get orignal data without spaces</span><br><span style=\"padding-left: 25px;\"><b>return</b> openssl_decrypt($string, &#8220;<span style=\"color: #880000;\">bf-ecb</span>&#8220;, $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $mcgetvi);</span><br>}</p>\n<p>That all looked reasonably standard, so we now needed to grab the ‘site_key’ and find out what was in that Base64 encoded value. Getting the site key was easy from our side, as it was stored in the database.</p>\n<p style=\"font-family: courier;\">mysql&gt; select value from ossn_site_settings <span style=\"color: #6cc45a;\">where</span> name = <span style=\"color: #880000;\">&#8220;site_key&#8221;</span>;<br>+&#8212;&#8212;&#8212;-+<br>| value |<br>+&#8212;&#8212;&#8212;-+<br>| 94bf7ac1 |<br>+&#8212;&#8212;&#8212;-+<br>1 row <b>in</b> <span style=\"color: #6cc45a;\">set</span> (0.00 sec)</p>\n<p>mysql&gt;</p>\n<p>That&#8217;s not a very long key! However, at this moment, we were only interested in seeing what was in that BLOB (binary large object: these can hold a variable amount of data) to make it a little easier for us. We copied these functions out to a standalone PHP script to play around with all the values and put some debug lines in there.</p>\n<p>Once we had enough information, we also created a couple of CyberChef recipes to replicate the functions. All that was needed now was to replace the placeholder site key that we were examining with our own, repeated to a length of 20 characters.</p>\n<p><a href=\"https://cyberchef.immersivelabs.online/#recipe=From_Base64('A-Za-z0-9+/=',true)From_Base64('A-Za-z0-9+/=',true)Blowfish_Decrypt({'option':'Latin1','string':'d548d76cd548d76cd548'},{'option':'Hex','string':''},'ECB','Raw','Raw')&amp;input=TDNaRVdGVTRRMUp0WkZSblptZ3pTRmhLVDBWaGJtRmlNa0ZRVDJ0cEwyOXNSMVZ1Vm1kSU1FRlRRMXBVUVUxaWEzWXJaVFJzUW14Q2FWTnRlbGRCT0hoQk5HOVVSbkpFWmpkalBRPT0\" target=\"_blank\" rel=\"noopener\">OSSN Blowfish Decryption</a><br><a href=\"https://cyberchef.immersivelabs.online/#recipe=Blowfish_Encrypt({'option':'Latin1','string':'e894e328e894e328e894'},{'option':'Hex','string':''},'ECB','Raw','Raw')To_Base64('A-Za-z0-9+/=')To_Base64('A-Za-z0-9+/=')&amp;input=L2V0Yy9wYXN3ZA\" target=\"_blank\" rel=\"noopener\">OSSN Blowfish Encryption</a></p>\n<h2>Arbitrary file read</h2>\n<p>Now that we knew how to generate the Base64 encoded value, it was time to find out whether we could read any files from the server that we shouldn’t have been able to.<br>There was another function that was doing some file path validation too. It was trying to replace directory traversal attempts by replacing any <span style=\"font-family: courier; color: #880000;\">../</span> in the strings. This was easy to ignore, as we specified the full path instead of using a relative path.</p>\n<p>To make things a bit more portable, we created a proof of concept (PoC) Python script that took a ‘site_key’, a ‘file_path’, and a target URL, and attempted to read the file.</p>\n<p>You can get the full code and the docker-compose images on <a href=\"https://github.com/kevthehermit/CVE-2020-10560\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a>.</p>\n<h2>Getting the site key</h2>\n<p>It was pretty easy to get to this point with knowledge of the site key – but was there a way for an attacker to get hold of it? At this point, Alex Seymour, Content Engineer, helped to trace all of the crypto bits and pieces, jumping with both feet into the deep end of C and PHP Blowfish implementations.</p>\n<p>We knew the site key used for the encryption was a mere eight characters in length. We could predict some of the plaintext, so was there a way to brute force the key from the outside in a reasonable timeframe?</p>\n<p>The key seemed to be in lowercase hex, which gives us 16 possible options per character. At eight characters, that gave us around <b>281, 474, 976, 710, 656</b> possible site keys. Certainly that wouldn’t be easy, but we weren’t deterred. We decided to take a look at the function that actually created the site key to see if there was anything that could be used. Turns out, there was.</p>\n<p>Here’s the function that created the unique site key:</p>\n<p style=\"font-family: courier;\"><b>function <span style=\"color: #880000;\">ossn_generate_site_secret</span></b>() {<br><span style=\"padding-left: 25px;\"><b>return</b> substr(md5(&#8216;<span style=\"color: #880000;\">ossn</span>&#8216; . rand()), <span style=\"color: #880000;\">3, 8</span>);</span></p>\n<p>This function:</p>\n<ul>\n<li>Starts with the string &#8216;ossn&#8217;</li>\n<li>Calculates a random number with ‘PHP rand()’</li>\n<li>Appends the number to the first string</li>\n<li>Calculates the md5 hash of this new string</li>\n<li>Takes characters 3-11 as the ‘site_key’</li>\n</ul>\n<p>The use of ‘rand()’ here is interesting, as the rand function is not cryptographically secure, and warns you as such in the PHP docs.<img src=\"/wp-content/uploads/2020/04/Picture-8.png\" alt=\"\"></p>\n<p>Using ‘rand()’ in PHP7 or higher, the maximum possible value is <b>2, 147, 483, 647</b>. In terms of cracking the key, we’ve reduced the number of possible keys from around 282 trillion to a measly two billion.</p>\n<p>Instead of trying every possible permutation of the eight character key, we calculated every md5 sum for each of the two billion possible values of ‘rand’.<br>That’s <span style=\"font-family: courier; color: #880000;\">ossn1</span> to <span style=\"font-family: courier; color: #880000;\">ossn2147483647</span>.</p>\n<h2>Breaking the crypto</h2>\n<p>Having identified the weak key generation routine, we needed to start writing code to recover the site key using that encrypted BLOB containing an image&#8217;s file path.<br>First, we had to identify a successful decryption attempt. We examined the source code and plaintext file path, which revealed the value <span style=\"font-family: courier; color: #880000;\">tmp/photos</span> to be a safe, known plaintext value as it was hard-coded into the source code.</p>\n<p>The initial PoC was a quick Python script that spawned a handful of threads to distribute the generation of all the possible keys. The generation threads, in turn, spawned yet more threads to handle each decryption attempt. Each of these threads then simply executed a PHP subprocess and checked the output for the known value <span style=\"font-family: courier; color: #880000;\">tmp/photos</span>. Unfortunately, the performance of this approach wasn&#8217;t anywhere near good enough. We saw average speeds of around 2000 attempts per second, which would have taken around two weeks to work through all the possibilities.</p>\n<p>The next iteration of the PoC was written in C, which unsurprisingly provided significant performance improvements. The C version follows pretty much the same structure as the Python PoC: a fixed number of generator threads were spawned and the available keyspace was split across them. In this case, we ran four generator threads, so split 2,147,483,647 into quarters, and passed each to a different generator thread.</p>\n<p style=\"font-family: courier;\"><b>void <span style=\"color: #880000;\">start_generators</span></b>(<b>long</b> max_value, <b>int</b> thread_count) {<br><span style=\"padding-left: 25px;\"><b>pthread_t</b> threads[thread_count];</span><br><span style=\"padding-left: 25px;\"><b>struct</b> <span style=\"color: #880000;\"><b>generator_args args</b></span>[<span style=\"color: #880000;\"><b>thread_count</b></span>];</span></p>\n<p><span style=\"padding-left: 25px;\"><b>while</b> (max_value % thread_count != <span style=\"color: #880000;\">0</span>) {</span><br><span style=\"padding-left: 50px;\">max_value++;</span><br><span style=\"padding-left: 25px;\">}</span></p>\n<p><span style=\"padding-left: 25px;\"><b>for</b> (<b>int</b> i = <span style=\"color: #880000;\">0</span>; i &lt; thread_count; i++) {</span><br><span style=\"padding-left: 50px;\">args[i].max = (max_value / thread_count) * (i + <span style=\"color: #880000;\">1</span>);</span><br><span style=\"padding-left: 50px;\">args[i].min = (max_value / thread_count) * i;</span></p>\n<p><span style=\"padding-left: 50px;\"><b>if</b> (args[i].min &gt; 0) {</span><br><span style=\"padding-left: 75px;\">args[i].min++;</span><br><span style=\"padding-left: 50px;\">}</span></p>\n<p><span style=\"padding-left: 50px;\">pthread_create(&amp;threads[i], <span style=\"color: #6cc45a;\">NULL</span>, attempt_generator, &amp;args[i]);</span><br><span style=\"padding-left: 25px;\">}</span></p>\n<p><span style=\"padding-left: 25px;\"><b>for</b> (<b>int</b> i = 0; i &lt; thread_count; i++) {</span><br><span style=\"padding-left: 50px;\">pthread_join(threads[i], <span style=\"color: #6cc45a;\">);</span><br><span style=\"padding-left: 25px;\">}</span><br>}</span></p>\n<p>Each generator thread enumerated its assigned value range in order to construct the key for each decryption attempt, according to the OSSN source code. Each value was appended to the string <span style=\"font-family: courier; color: #880000;\">ossn</span>, and the result was then md5 hashed. Characters 3-11 of the resulting hash are extracted, and passed to a new thread to handle further key preparation and the actual decryption attempt.</p>\n<p style=\"font-family: courier;\"><b>char</b> *<span style=\"color: #6cc45a;\">string</span> = <span style=\"color: #6cc45a;\">malloc</span>(<span style=\"color: #880000;\">256</span>);<br><b>unsigned char</b> hash[MD5_DIGEST_LENGTH];<br><b>char</b> *attempt = <span style=\"color: #6cc45a;\">malloc</span>(SUBSTR_LENGTH + <span style=\"color: #880000;\">1</span>);<br><b>char</b> *hash_hex = <span style=\"color: #6cc45a;\">malloc</span>(<span style=\"color: #880000;\">32</span>);</p>\n<p><span style=\"color: #6cc45a;\">memset</span>(<span style=\"color: #6cc45a;\">string</span>, <span style=\"color: #880000;\">0</span>, <span style=\"color: #880000;\">256</span>);<br><span style=\"color: #6cc45a;\">memset</span>(attempt, <span style=\"color: #880000;\">0</span>, SUBSTR_LENGTH + <span style=\"color: #880000;\">1</span>);<br><span style=\"color: #6cc45a;\">memset</span>(hash_hex, <span style=\"color: #880000;\">0</span>, <span style=\"color: #880000;\">32</span>);<br><span style=\"color: #6cc45a;\">sprintf</span>(<span style=\"color: #6cc45a;\">string</span>, <span style=\"color: #880000;\">&#8220;ossn%ld&#8221;</span>, i);<br>MD5(<span style=\"color: #6cc45a;\">string</span>, <span style=\"color: #6cc45a;\">strlen</span>(<span style=\"color: #6cc45a;\">string</span>), hash);</p>\n<p><b>for</b> (<b>int</b> j = <span style=\"color: #880000;\">0</span>; j &lt; MD5_DIGEST_LENGTH; j++) { <span style=\"padding-left: 25px;\">hash_hex += <span style=\"color: #6cc45a;\">sprintf(hash_hex, <span style=\"color: #880000;\">&#8220;%02x&#8221;</span>, hash[j]);</span><br>}</span></p>\n<p>hash_hex -= <span style=\"color: #880000;\">32</span>;<br><span style=\"color: #6cc45a;\">strncpy</span>(attempt, hash_hex + SUBSTR_START, SUBSTR_LENGTH);<br>pthread_create(&amp;threads[thread_index], <span style=\"color: #6cc45a;\">NULL</span>, test_attempt, attempt);</p>\n<p>During this process we made an interesting discovery; we realised that Blowfish’s key expansion is not correctly implemented in PHP&#8217;s OpenSSL extension. As detailed in <a href=\"https://bugs.php.net/bug.php\" target=\"_blank\" rel=\"noopener noreferrer\">this bug report</a>, keys that are made up of less than 128 bits (16 bytes) are zero-padded but should use key cycling according to the algorithm&#8217;s inventor. The PHP developers added a new constant, <span style=\"font-family: courier; color: #880000;\">OPENSSL_DONT_ZERO_PAD_KEY</span>, which instructs calls to <span style=\"font-family: courier; color: #880000;\">openssl_encrypt()</span> to use key cycling instead of zero-padding. The default implementation, however, still uses zero-padding and, at the time of writing, the constant is undocumented. OSSN contains its own key cycling function that is used to cycle a key up to 20 characters in length (so <span style=\"font-family: courier; color: #880000;\">test1234</span> would become <span style=\"font-family: courier; color: #880000;\">test1234test1234test</span>).</p>\n<p></p>\n\n\n<p>During this process we made an interesting discovery; we realised that Blowfish’s key expansion is not correctly implemented in PHP&#8217;s OpenSSL extension. As detailed in <a rel=\"noreferrer noopener\" href=\"https://bugs.php.net/bug.php\" target=\"_blank\">this bug report</a>, keys that are made up of less than 128 bits (16 bytes) are zero-padded but should use key cycling according to the algorithm&#8217;s inventor. The PHP developers added a new constant, OPENSSL_DONT_ZERO_PAD_KEY, which instructs calls to openssl_encrypt() to use key cycling instead of zero-padding. The default implementation, however, still uses zero-padding and, at the time of writing, the constant is undocumented. OSSN contains its own key cycling function that is used to cycle a key up to 20 characters in length (so test1234 would become test1234test1234test).</p>\n\n\n\n<p>Now, all that was left was to implement key cycling, start decrypting the ciphertext block by block, and checking the output for the known plaintext tmp/photos.<br>The C implementation performed an average of around 45,000 attempts per second, meaning it would take just over 13 hours to try every possible key. Cursory testing showed even faster speeds on higher-spec machines.</p>\n\n\n\n<p>During the creation of this PoC, the OSSN developers released an update that changed their encryption process to use AES instead of Blowfish. We modified our PoC to create an AES version as well. Both can be found on <a href=\"https://github.com/LucidUnicorn/CVE-2020-10560-Key-Recovery/releases\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub</a>.</p>\n\n\n\n<h2>Disclosure</h2>\n\n\n\n<p>Disclosing the vulnerability to Soft Lab 24, the company that develops OSSN, was a fairly smooth process. We reached out to their team via email, and they were quick to respond and push updates. After a few days of back and forth, we were unable to read any more files.</p>\n\n\n\n<p>If you&#8217;d like to get hands on with this vulnerability in a safe, secure environment, log in to your Immersive Labs account and head over to Cyber Threat Intelligence.</p>\n\n\n\n\n","excerpt":"<p>Open source platforms, although extremely useful and popular, can sometimes be prone to vulnerabilities. By allowing users to access the code that creates the platforms, these free software resources open themselves up for easier vulnerability testing by cyber professionals and hobbyists, as well as malicious actors. Immersive Labs’ Director of Cyber Threat Research, Kev Breen,&#8230;</p>\n","blocks":[{"__typename":"WordPress_CoreImageBlock","name":"core/image","attributes":{"__typename":"WordPress_CoreImageBlockAttributes","url":"https://cms.immersivelabs.com/content/uploads/2020/06/picture-1-1.png","href":"","alt":"","caption":"Source: https://www.businessinsider.com/coronavirus-email-scam-covid-19-phishing-false-information-who-cdc-2020-2?r=US&amp;IR=T#check-the-senders-email-domain-and-see-if-it-matches-the-website-of-the-organization-they-say-they-work-for-then-check-the-urls-included-in-the-email-1","imageAlign":null,"widthPercentage":100,"imageFile":{"publicURL":"/static/3d0bf2674c46efb77bb7425c37bca642/picture-1-1.png","childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAABcSAAAXEgFnn9JSAAAA5klEQVQoz42RSW7DMBAE9Q6tiA+Co0iOuA5J6f/f6kzTiC8B5BwG5KWLXcMmhghrHYwxKDnDO48YI5IIJAqcc1iWBevXimkcMQzD5TTWWuz7jpwLUhJYBROYU9ZJmOcZXde9Bb2ADBMqCjhKqY2KnhzCQwj6UKqNp2l6D6TqvhuIPPVENKxw73/VE87zxOf9/r+GTvdHUM5JAUHvXu+5tgoK9T5UfT6wbVsNjRe7bKIqUYtQ7pBN2eypKq+WhHHX1G7b9vpTGOanlJJr2CqcmmxtjK1QzlEOPB7fWNcNt9sH+r7/A/wB+dbRW7A6gCEAAAAASUVORK5CYII=","aspectRatio":3.0303030303030303,"src":"/static/3d0bf2674c46efb77bb7425c37bca642/154ff/picture-1-1.png","srcSet":"/static/3d0bf2674c46efb77bb7425c37bca642/2abd0/picture-1-1.png 200w,\n/static/3d0bf2674c46efb77bb7425c37bca642/94dc4/picture-1-1.png 400w,\n/static/3d0bf2674c46efb77bb7425c37bca642/154ff/picture-1-1.png 800w,\n/static/3d0bf2674c46efb77bb7425c37bca642/1515f/picture-1-1.png 939w","srcWebp":"/static/3d0bf2674c46efb77bb7425c37bca642/2459c/picture-1-1.webp","srcSetWebp":"/static/3d0bf2674c46efb77bb7425c37bca642/f73af/picture-1-1.webp 200w,\n/static/3d0bf2674c46efb77bb7425c37bca642/852ff/picture-1-1.webp 400w,\n/static/3d0bf2674c46efb77bb7425c37bca642/2459c/picture-1-1.webp 800w,\n/static/3d0bf2674c46efb77bb7425c37bca642/6474e/picture-1-1.webp 939w","sizes":"(max-width: 800px) 100vw, 800px"}}}}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"Open source platforms, although extremely useful and popular, can sometimes be prone to vulnerabilities. By allowing users to access the code that creates the platforms, these free software resources open themselves up for easier vulnerability testing by cyber professionals and hobbyists, as well as malicious actors.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"Immersive Labs’ Director of Cyber Threat Research, Kev Breen, recently uncovered a weakness in a tool dubbed the Open Source Social Network. This vulnerability would enable “arbitrary file read” – allowing an attacker to read any file in the system, whether permitted or not. There were a couple of obstacles to overcome during this process, which eventually involved writing a custom crypto cracking tool… but more on that later.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreHeadingBlock","name":"core/heading","attributes":{"__typename":"WordPress_CoreHeadingBlockAttributes","textAlign":null,"content":"What is the Open Source Social Network?","level":2,"textColor":null,"anchor":""}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"The Open Source Social Network (OSSN) is pretty self-explanatory: it’s an open source social media platform that a user hosts themself. Reasonably easy to download and get running, it’s written in PHP, uses a MySQL backend, and has just shy of half a million downloads listed on its <a href=\"https://www.opensource-socialnetwork.org/\" target=\"_blank\" rel=\"noreferrer noopener\">main site</a>.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreImageBlock","name":"core/image","attributes":{"__typename":"WordPress_CoreImageBlockAttributes","url":"https://cms.immersivelabs.com/content/uploads/2020/06/picture-2-1.png","href":"","alt":"","caption":"Source: https://www.businessinsider.com/coronavirus-email-scam-covid-19-phishing-false-information-who-cdc-2020-2?r=US&amp;IR=T#check-the-senders-email-domain-and-see-if-it-matches-the-website-of-the-organization-they-say-they-work-for-then-check-the-urls-included-in-the-email-1","imageAlign":null,"widthPercentage":100,"imageFile":{"publicURL":"/static/9d3ae7da1e69434d9fe93098593633d6/picture-2-1.png","childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAABcSAAAXEgFnn9JSAAACL0lEQVQozz2S7UtTcRTH739Qb3vda19kMUuckoOESDB7oCIqDXzjAxURgWIEQTpQM4MiUbMCk5aSut0pSqnZRBmFzad7NWlOdzc3p9v0buzqp58avfhwOHw5h/Ple6TCggKOZ5qxNNnIaurG1GAj45kNk+BU4yfOtzq42CZT2Cpz6Y2T7BfdpNft6V2c2MPagan2PSfrOzELTTJlZJCbm4tvZYVoNIau6yQSyX8k2NrWiQt00RupFJFQgGjAixEPQyIKJCGV5M+iimvILhaaTGTn5OD3+0mKgVh8i9TOzn+M3V0MwyC4EcO9rDG5GOaJrGEdXKF+JIB1LITz9xYLUz95W2s9WJhlzmZZUQh6PARmZwkvLBBSVdaUA0JC884qrKoztI2uIZULqjSkx6tI1T4u28P4lRm6mluQLBYL5/LzCQrLxsYmenidpKipzSg7sfg+u+JqdGGNbT6MrnO4UiPzpY/M1xpHnvsodkYIz3sYb3mF1N7ezh7eZS9yv4Oevh7sch/9A04GhwYYd40w/HUIhyyjTE/y0RXj0MNVzI1LnG5c42iNn5KeCJo6x7BNhJKefowzeXnMeKa4X3aDaxfyqCi5yoOKm1Teu83TqlIq7xZx68pZWhqq6JyIIZUGhFVhudqPdEfjuk1cuKQw1utASktL209ZVeZwdL+jruYRE9+cTP8YQZ12sTQ/iVd188v9hZB3iu9KhOKOAOWfNcrsGkW9Gs3uIBuhoPgUjb/jFOqP0ooU0QAAAABJRU5ErkJggg==","aspectRatio":2.5974025974025974,"src":"/static/9d3ae7da1e69434d9fe93098593633d6/154ff/picture-2-1.png","srcSet":"/static/9d3ae7da1e69434d9fe93098593633d6/2abd0/picture-2-1.png 200w,\n/static/9d3ae7da1e69434d9fe93098593633d6/94dc4/picture-2-1.png 400w,\n/static/9d3ae7da1e69434d9fe93098593633d6/154ff/picture-2-1.png 800w,\n/static/9d3ae7da1e69434d9fe93098593633d6/1515f/picture-2-1.png 939w","srcWebp":"/static/9d3ae7da1e69434d9fe93098593633d6/2459c/picture-2-1.webp","srcSetWebp":"/static/9d3ae7da1e69434d9fe93098593633d6/f73af/picture-2-1.webp 200w,\n/static/9d3ae7da1e69434d9fe93098593633d6/852ff/picture-2-1.webp 400w,\n/static/9d3ae7da1e69434d9fe93098593633d6/2459c/picture-2-1.webp 800w,\n/static/9d3ae7da1e69434d9fe93098593633d6/6474e/picture-2-1.webp 939w","sizes":"(max-width: 800px) 100vw, 800px"}}}}},{"__typename":"WordPress_CoreHeadingBlock","name":"core/heading","attributes":{"__typename":"WordPress_CoreHeadingBlockAttributes","textAlign":null,"content":"The scanner","level":2,"textColor":null,"anchor":""}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"The source code for OSSN is free to download either from the main site or from the OSSN GitHub. As it’s a PHP application, it’s relatively easy to read and keep track of what’s happening. That being said, there is a lot of code here. We ran a quick pass over it using an open source static code analyser to see if we could find any bugs.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreHeadingBlock","name":"core/heading","attributes":{"__typename":"WordPress_CoreHeadingBlockAttributes","textAlign":null,"content":"The results","level":2,"textColor":null,"anchor":""}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"A progpilot (static PHP analyzer) scan only takes a couple of minutes to complete, so we immediately had some pretty interesting results. Check it out.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreFreeformBlock","name":"core/freeform","attributes":{"oldContent":"<p style=\"font-family: courier;\">{<br><span style=\"padding-left: 25px;\">\"source_name\": [</span><br><span style=\"padding-left: 50px; color: #880000;\">\"$file_get_contents_return\"</span><br><span style=\"padding-left: 25px;\">],</span><br><span style=\"padding-left: 25px;\">\"source_line\": [</span><br><span style=\"padding-left: 50px; color: #880000;\">316</span><br><span style=\"padding-left: 25px;\">],</span><br><span style=\"padding-left: 25px;\">\"source_column\": [</span><br><span style=\"padding-left: 50px; color: #880000;\">10492</span><br><span style=\"padding-left: 25px;\">],</span><br><span style=\"padding-left: 25px;\">\"source_file\": [</span><br><span style=\"padding-left: 50px; color: #880000;\">\"\\/tmp\\/ossn\\/components\\/OssnComments\\/ossn_com.php\"</span><br><span style=\"padding-left: 25px;\">],</span><br><span style=\"padding-left: 25px;\">\"sink_name\":<span style=\"color: #880000;\"> \"echo\"</span>,</span><br><span style=\"padding-left: 25px;\">\"sink_line\": <span style=\"color: #880000;\">316</span>,</span><br><span style=\"padding-left: 25px;\">\"sink_column\": <span style=\"color: #880000;\">10492 </span>,</span><br><span style=\"padding-left: 25px;\">\"sink_file\": <span style=\"color: #880000;\">\"\\/tmp\\/ossn\\/components\\/OssnComments\\/ossn_com.php\"</span>,</span><br><span style=\"padding-left: 25px;\">\"vuln_name\":<span style=\"color: #880000;\"> \"xss\"</span>,</span><br><span style=\"padding-left: 25px;\">\"vuln_cwe\":<span style=\"color: #880000;\"> \"CWE_79\"</span>,</span><br><span style=\"padding-left: 25px;\">\"vuln_id\": <span style=\"color: #880000;\">\"bd6478779f63431a516932bc0fe193e97ff73a84e6710cb644d88ba036f5bbbe\"</span>,</span><br><span style=\"padding-left: 25px;\">\"vuln_type\":<span style=\"color: #880000;\"> \"taint-style\"</span><br>},</span></p>\n<p>The scan threw up a potential XSS (cross-site) vulnerability in a component that seemed to be related to comments in the network. It tells us it’s on line 316 of the source code, so we headed there to take a closer look. What was going on?</p>\n<p style=\"font-family: courier;\"><b>case</b> <span style=\"color: #880000;\">'staticimage'</span>:<br><span style=\"padding-left: 25px;\">$image = base64_decode(input(<span style=\"color: #880000;\">'image'</span>));</span><br><span style=\"padding-left: 25px;\"><b>if</b>(!<b>empty</b>($image)) {</span><br><span style=\"padding-left: 50px;\">$file = ossn_string_decrypt(base64_decode($image));</span><br><span style=\"padding-left: 50px;\">header('<span style=\"color: #880000;\">content-type: image/jpeg</span>');</span><br><span style=\"padding-left: 50px;\">$file = rtrim(ossn_validate_filepath($file), '/');</span><br><span style=\"padding-left: 50px;\"><b>if</b>(is_file($file)) {</span><br><span style=\"padding-left: 75px;\"><b>echo</b> file_get_contents($file);</span><br><span style=\"padding-left: 50px;\">} <b>else</b> {</span><br><span style=\"padding-left: 75px;\">ossn_error_page();</span><br><span style=\"padding-left: 50px;\">}</span><br><span style=\"padding-left: 25px;\">} <b>else</b> {</span><br><span style=\"padding-left: 50px;\">ossn_error_page();</span><br><span style=\"padding-left: 25px;\">}</span><br><span style=\"padding-left: 25px;\"><b>break</b>;</span></p>\n<p>From the source code we discovered that there wasn’t in fact an XSS vulnerability, but an echo that was reading data from a file. It was setting image content types and seemed to be validating file paths. For the keen-eyed amongst you, there’s also an encryption function here, which we’ll get to later.</p>\n<p>First, we needed to see if it was possible to control the file path <span style=\"font-family: courier; color: #880000;\">$file</span>. If that could be done then we were looking at local file inclusion or, at the very least, file read. From reading the source code, there is a case statement with different options. We identified that the case option ‘staticimage’ needs to evaluate to TRUE for the vulnerable code to be reached. Let’s dig deeper.</p>\n<h2>‘staticimage’</h2>\n<p>Using a docker-compose template, we started a new instance with a clean database and some generic accounts. Once the application got up and running, we needed to find where this function and case were called in the code. The above script gave us a few clues to work with.</p>\n<ul>\n<li>It’s in a comment</li>\n<li>It’s image related</li>\n<li>The path contains ‘staticimage’</li>\n<li>There is a Base64 string</li>\n</ul>\n<p>Burp felt a little heavy-handed for this fairly straightforward task, so we used Dev Tools on Chrome instead, setting a filter for ‘staticimage’.</p>\n<p>At this stage, there was just one demo post on the social network. We knew the bug was something to do with comments, so we took a look at the comments section on the post.</p>\n<p>There’s an option to add an image to a comment. We selected an image to upload to see what would happen, and before posting it…</p>\n<p>Before even hitting send on our comment, we hit all the requirements on the above list. This is just a preview that has sent a request to the server, stored the image, and rendered the preview in the front end.</p>\n<p>Once it’s been previewed, the image is stored on the server and can be accessed directly without being authenticated to the OSSN platform. Bingo.</p>\n<p>From here, we knew we could control the Base64 string from the attacker's side and that it was somehow being used to construct a file path that was then being echoed out to the page.</p>\n<h2>The crypto</h2>\n<p>So what was that ‘ossn_string_decrypt’ we referenced right at the outset actually doing? The application took the Base64 string from the URL image parameter and Base64 decoded it twice. The resulting output was then passed to this function:</p>\n<p style=\"font-family: courier;\"><b>function <span style=\"color: #880000;\">ossn_string_decrypt</span></b>($string = <span style=\"color: #880000;\">' '</span>, $key = <span style=\"color: #880000;\">' '</span>) {<br><span style=\"padding-left: 25px;\"><b>if</b> (<b>empty</b>($string)) {</span><br><span style=\"padding-left: 50px;\"><b>return false</b>;</span><br><span style=\"padding-left: 25px;\">}</span><br><span style=\"padding-left: 25px;\"><b>if</b> (<b>empty</b>($key)) {</span><br><span style=\"padding-left: 50px;\">$key = ossn_site_settings('<span style=\"color: #880000;\">site_key</span>');</span><br><span style=\"padding-left: 25px;\">}</span><br><span style=\"padding-left: 25px;\">$key = ossn_string_encrypt_key_cycled($key);</span></p>\n<p><span style=\"padding-left: 25px;\">$size = openssl_cipher_iv_length('<span style=\"color: #880000;\">bf-ecb</span>');</span><br><span style=\"padding-left: 25px;\">$mcgetvi = openssl_random_pseudo_bytes($size);</span><br><span style=\"padding-left: 25px; color: #999999;\">//note mcrypt and now this acting mcrpyt adds the spaces to make 16 bytes if its less then 16 bytes</span><br><span style=\"padding-left: 25px; color: #999999;\">//you can use trim() to get orignal data without spaces</span><br><span style=\"padding-left: 25px;\"><b>return</b> openssl_decrypt($string, \"<span style=\"color: #880000;\">bf-ecb</span>\", $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING, $mcgetvi);</span><br>}</p>\n<p>That all looked reasonably standard, so we now needed to grab the ‘site_key’ and find out what was in that Base64 encoded value. Getting the site key was easy from our side, as it was stored in the database.</p>\n<p style=\"font-family: courier;\">mysql&gt; select value from ossn_site_settings <span style=\"color: #6cc45a;\">where</span> name = <span style=\"color: #880000;\">\"site_key\"</span>;<br>+----------+<br>| value |<br>+----------+<br>| 94bf7ac1 |<br>+----------+<br>1 row <b>in</b> <span style=\"color: #6cc45a;\">set</span> (0.00 sec)</p>\n<p>mysql&gt;</p>\n<p>That's not a very long key! However, at this moment, we were only interested in seeing what was in that BLOB (binary large object: these can hold a variable amount of data) to make it a little easier for us. We copied these functions out to a standalone PHP script to play around with all the values and put some debug lines in there.</p>\n<p>Once we had enough information, we also created a couple of CyberChef recipes to replicate the functions. All that was needed now was to replace the placeholder site key that we were examining with our own, repeated to a length of 20 characters.</p>\n<p><a href=\"https://cyberchef.immersivelabs.online/#recipe=From_Base64('A-Za-z0-9+/=',true)From_Base64('A-Za-z0-9+/=',true)Blowfish_Decrypt({'option':'Latin1','string':'d548d76cd548d76cd548'},{'option':'Hex','string':''},'ECB','Raw','Raw')&amp;input=TDNaRVdGVTRRMUp0WkZSblptZ3pTRmhLVDBWaGJtRmlNa0ZRVDJ0cEwyOXNSMVZ1Vm1kSU1FRlRRMXBVUVUxaWEzWXJaVFJzUW14Q2FWTnRlbGRCT0hoQk5HOVVSbkpFWmpkalBRPT0\" target=\"_blank\" rel=\"noopener\">OSSN Blowfish Decryption</a><br><a href=\"https://cyberchef.immersivelabs.online/#recipe=Blowfish_Encrypt({'option':'Latin1','string':'e894e328e894e328e894'},{'option':'Hex','string':''},'ECB','Raw','Raw')To_Base64('A-Za-z0-9+/=')To_Base64('A-Za-z0-9+/=')&amp;input=L2V0Yy9wYXN3ZA\" target=\"_blank\" rel=\"noopener\">OSSN Blowfish Encryption</a></p>\n<h2>Arbitrary file read</h2>\n<p>Now that we knew how to generate the Base64 encoded value, it was time to find out whether we could read any files from the server that we shouldn’t have been able to.<br>There was another function that was doing some file path validation too. It was trying to replace directory traversal attempts by replacing any <span style=\"font-family: courier; color: #880000;\">../</span> in the strings. This was easy to ignore, as we specified the full path instead of using a relative path.</p>\n<p>To make things a bit more portable, we created a proof of concept (PoC) Python script that took a ‘site_key’, a ‘file_path’, and a target URL, and attempted to read the file.</p>\n<p>You can get the full code and the docker-compose images on <a href=\"https://github.com/kevthehermit/CVE-2020-10560\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a>.</p>\n<h2>Getting the site key</h2>\n<p>It was pretty easy to get to this point with knowledge of the site key – but was there a way for an attacker to get hold of it? At this point, Alex Seymour, Content Engineer, helped to trace all of the crypto bits and pieces, jumping with both feet into the deep end of C and PHP Blowfish implementations.</p>\n<p>We knew the site key used for the encryption was a mere eight characters in length. We could predict some of the plaintext, so was there a way to brute force the key from the outside in a reasonable timeframe?</p>\n<p>The key seemed to be in lowercase hex, which gives us 16 possible options per character. At eight characters, that gave us around <b>281, 474, 976, 710, 656</b> possible site keys. Certainly that wouldn’t be easy, but we weren’t deterred. We decided to take a look at the function that actually created the site key to see if there was anything that could be used. Turns out, there was.</p>\n<p>Here’s the function that created the unique site key:</p>\n<p style=\"font-family: courier;\"><b>function <span style=\"color: #880000;\">ossn_generate_site_secret</span></b>() {<br><span style=\"padding-left: 25px;\"><b>return</b> substr(md5('<span style=\"color: #880000;\">ossn</span>' . rand()), <span style=\"color: #880000;\">3, 8</span>);</span></p>\n<p>This function:</p>\n<ul>\n<li>Starts with the string 'ossn'</li>\n<li>Calculates a random number with ‘PHP rand()’</li>\n<li>Appends the number to the first string</li>\n<li>Calculates the md5 hash of this new string</li>\n<li>Takes characters 3-11 as the ‘site_key’</li>\n</ul>\n<p>The use of ‘rand()’ here is interesting, as the rand function is not cryptographically secure, and warns you as such in the PHP docs.<img src=\"/wp-content/uploads/2020/04/Picture-8.png\" alt=\"\"></p>\n<p>Using ‘rand()’ in PHP7 or higher, the maximum possible value is <b>2, 147, 483, 647</b>. In terms of cracking the key, we’ve reduced the number of possible keys from around 282 trillion to a measly two billion.</p>\n<p>Instead of trying every possible permutation of the eight character key, we calculated every md5 sum for each of the two billion possible values of ‘rand’.<br>That’s <span style=\"font-family: courier; color: #880000;\">ossn1</span> to <span style=\"font-family: courier; color: #880000;\">ossn2147483647</span>.</p>\n<h2>Breaking the crypto</h2>\n<p>Having identified the weak key generation routine, we needed to start writing code to recover the site key using that encrypted BLOB containing an image's file path.<br>First, we had to identify a successful decryption attempt. We examined the source code and plaintext file path, which revealed the value <span style=\"font-family: courier; color: #880000;\">tmp/photos</span> to be a safe, known plaintext value as it was hard-coded into the source code.</p>\n<p>The initial PoC was a quick Python script that spawned a handful of threads to distribute the generation of all the possible keys. The generation threads, in turn, spawned yet more threads to handle each decryption attempt. Each of these threads then simply executed a PHP subprocess and checked the output for the known value <span style=\"font-family: courier; color: #880000;\">tmp/photos</span>. Unfortunately, the performance of this approach wasn't anywhere near good enough. We saw average speeds of around 2000 attempts per second, which would have taken around two weeks to work through all the possibilities.</p>\n<p>The next iteration of the PoC was written in C, which unsurprisingly provided significant performance improvements. The C version follows pretty much the same structure as the Python PoC: a fixed number of generator threads were spawned and the available keyspace was split across them. In this case, we ran four generator threads, so split 2,147,483,647 into quarters, and passed each to a different generator thread.</p>\n<p style=\"font-family: courier;\"><b>void <span style=\"color: #880000;\">start_generators</span></b>(<b>long</b> max_value, <b>int</b> thread_count) {<br><span style=\"padding-left: 25px;\"><b>pthread_t</b> threads[thread_count];</span><br><span style=\"padding-left: 25px;\"><b>struct</b> <span style=\"color: #880000;\"><b>generator_args args</b></span>[<span style=\"color: #880000;\"><b>thread_count</b></span>];</span></p>\n<p><span style=\"padding-left: 25px;\"><b>while</b> (max_value % thread_count != <span style=\"color: #880000;\">0</span>) {</span><br><span style=\"padding-left: 50px;\">max_value++;</span><br><span style=\"padding-left: 25px;\">}</span></p>\n<p><span style=\"padding-left: 25px;\"><b>for</b> (<b>int</b> i = <span style=\"color: #880000;\">0</span>; i &lt; thread_count; i++) {</span><br><span style=\"padding-left: 50px;\">args[i].max = (max_value / thread_count) * (i + <span style=\"color: #880000;\">1</span>);</span><br><span style=\"padding-left: 50px;\">args[i].min = (max_value / thread_count) * i;</span></p>\n<p><span style=\"padding-left: 50px;\"><b>if</b> (args[i].min &gt; 0) {</span><br><span style=\"padding-left: 75px;\">args[i].min++;</span><br><span style=\"padding-left: 50px;\">}</span></p>\n<p><span style=\"padding-left: 50px;\">pthread_create(&amp;threads[i], <span style=\"color: #6cc45a;\">NULL</span>, attempt_generator, &amp;args[i]);</span><br><span style=\"padding-left: 25px;\">}</span></p>\n<p><span style=\"padding-left: 25px;\"><b>for</b> (<b>int</b> i = 0; i &lt; thread_count; i++) {</span><br><span style=\"padding-left: 50px;\">pthread_join(threads[i], <span style=\"color: #6cc45a;\">);</span><br><span style=\"padding-left: 25px;\">}</span><br>}</span></p>\n<p>Each generator thread enumerated its assigned value range in order to construct the key for each decryption attempt, according to the OSSN source code. Each value was appended to the string <span style=\"font-family: courier; color: #880000;\">ossn</span>, and the result was then md5 hashed. Characters 3-11 of the resulting hash are extracted, and passed to a new thread to handle further key preparation and the actual decryption attempt.</p>\n<p style=\"font-family: courier;\"><b>char</b> *<span style=\"color: #6cc45a;\">string</span> = <span style=\"color: #6cc45a;\">malloc</span>(<span style=\"color: #880000;\">256</span>);<br><b>unsigned char</b> hash[MD5_DIGEST_LENGTH];<br><b>char</b> *attempt = <span style=\"color: #6cc45a;\">malloc</span>(SUBSTR_LENGTH + <span style=\"color: #880000;\">1</span>);<br><b>char</b> *hash_hex = <span style=\"color: #6cc45a;\">malloc</span>(<span style=\"color: #880000;\">32</span>);</p>\n<p><span style=\"color: #6cc45a;\">memset</span>(<span style=\"color: #6cc45a;\">string</span>, <span style=\"color: #880000;\">0</span>, <span style=\"color: #880000;\">256</span>);<br><span style=\"color: #6cc45a;\">memset</span>(attempt, <span style=\"color: #880000;\">0</span>, SUBSTR_LENGTH + <span style=\"color: #880000;\">1</span>);<br><span style=\"color: #6cc45a;\">memset</span>(hash_hex, <span style=\"color: #880000;\">0</span>, <span style=\"color: #880000;\">32</span>);<br><span style=\"color: #6cc45a;\">sprintf</span>(<span style=\"color: #6cc45a;\">string</span>, <span style=\"color: #880000;\">\"ossn%ld\"</span>, i);<br>MD5(<span style=\"color: #6cc45a;\">string</span>, <span style=\"color: #6cc45a;\">strlen</span>(<span style=\"color: #6cc45a;\">string</span>), hash);</p>\n<p><b>for</b> (<b>int</b> j = <span style=\"color: #880000;\">0</span>; j &lt; MD5_DIGEST_LENGTH; j++) { <span style=\"padding-left: 25px;\">hash_hex += <span style=\"color: #6cc45a;\">sprintf(hash_hex, <span style=\"color: #880000;\">\"%02x\"</span>, hash[j]);</span><br>}</span></p>\n<p>hash_hex -= <span style=\"color: #880000;\">32</span>;<br><span style=\"color: #6cc45a;\">strncpy</span>(attempt, hash_hex + SUBSTR_START, SUBSTR_LENGTH);<br>pthread_create(&amp;threads[thread_index], <span style=\"color: #6cc45a;\">NULL</span>, test_attempt, attempt);</p>\n<p>During this process we made an interesting discovery; we realised that Blowfish’s key expansion is not correctly implemented in PHP's OpenSSL extension. As detailed in <a href=\"https://bugs.php.net/bug.php\" target=\"_blank\" rel=\"noopener noreferrer\">this bug report</a>, keys that are made up of less than 128 bits (16 bytes) are zero-padded but should use key cycling according to the algorithm's inventor. The PHP developers added a new constant, <span style=\"font-family: courier; color: #880000;\">OPENSSL_DONT_ZERO_PAD_KEY</span>, which instructs calls to <span style=\"font-family: courier; color: #880000;\">openssl_encrypt()</span> to use key cycling instead of zero-padding. The default implementation, however, still uses zero-padding and, at the time of writing, the constant is undocumented. OSSN contains its own key cycling function that is used to cycle a key up to 20 characters in length (so <span style=\"font-family: courier; color: #880000;\">test1234</span> would become <span style=\"font-family: courier; color: #880000;\">test1234test1234test</span>).</p>\n<p></p>"}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"During this process we made an interesting discovery; we realised that Blowfish’s key expansion is not correctly implemented in PHP's OpenSSL extension. As detailed in <a rel=\"noreferrer noopener\" href=\"https://bugs.php.net/bug.php\" target=\"_blank\">this bug report</a>, keys that are made up of less than 128 bits (16 bytes) are zero-padded but should use key cycling according to the algorithm's inventor. The PHP developers added a new constant, OPENSSL_DONT_ZERO_PAD_KEY, which instructs calls to openssl_encrypt() to use key cycling instead of zero-padding. The default implementation, however, still uses zero-padding and, at the time of writing, the constant is undocumented. OSSN contains its own key cycling function that is used to cycle a key up to 20 characters in length (so test1234 would become test1234test1234test).","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"Now, all that was left was to implement key cycling, start decrypting the ciphertext block by block, and checking the output for the known plaintext tmp/photos.<br>The C implementation performed an average of around 45,000 attempts per second, meaning it would take just over 13 hours to try every possible key. Cursory testing showed even faster speeds on higher-spec machines.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"During the creation of this PoC, the OSSN developers released an update that changed their encryption process to use AES instead of Blowfish. We modified our PoC to create an AES version as well. Both can be found on <a href=\"https://github.com/LucidUnicorn/CVE-2020-10560-Key-Recovery/releases\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub</a>.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreHeadingBlock","name":"core/heading","attributes":{"__typename":"WordPress_CoreHeadingBlockAttributes","textAlign":null,"content":"Disclosure","level":2,"textColor":null,"anchor":""}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"Disclosing the vulnerability to Soft Lab 24, the company that develops OSSN, was a fairly smooth process. We reached out to their team via email, and they were quick to respond and push updates. After a few days of back and forth, we were unable to read any more files.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_CoreParagraphBlock","name":"core/paragraph","attributes":{"__typename":"WordPress_CoreParagraphBlockAttributes","content":"If you'd like to get hands on with this vulnerability in a safe, secure environment, log in to your Immersive Labs account and head over to Cyber Threat Intelligence.","textAlign":null,"fontSize":null}},{"__typename":"WordPress_AcfButtonBlock","name":"acf/button","acf":{"buttonType":"link","buttonLink":{"title":"Log in to try this lab!","url":"https://immersivelabs.online/labs/cve-2020-10560-ossn/","target":"_blank"},"buttonText":null,"formIntroContent":null,"hubspotPortalId":null,"hubspotFormId":null,"ctaPost":null,"buttonStyle":"fill","buttonSize":"large"}},{"__typename":"WordPress_AcfCtaBlock","name":"acf/cta","acf":{"ctaNegativeBottomMargin":null,"ctaPost":{"__typename":"WordPress_Cta","id":"cG9zdDo3OTE3","attributes":{"ctaColor":"orangeMain","ctaTitle":"Ready to see for yourself?","ctaText":"Take a tour of our platform including the chance to get hands-on with emerging threats, CTF style challenges and playable cyber crisis simulations. ","ctaImage":{"sourceUrl":"https://cms.immersivelabs.com/content/uploads/2020/06/cta-demo-1-1300x288.png","imageFile":{"publicURL":"/static/d52c66fd6a9bf82b6641c6bac7b8ef11/cta-demo-1-1300x288.png","childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAECAYAAACOXx+WAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA4ElEQVQY062Pz0vCAACF92d614MHrx0DwRQxpFMMPAWlFkJBkWzNuVWi07lNXb+Yh67+WI6cJF7ec4e62NUHHx+8w4MncD/BnwU/WKI/8uC6Y7xHeEMPny9jyLdPSB+IyKQKyCXzyCeyKMYjYodoXNTRcT2Y/VcYzzZM3YKjmlyvfqLBRUBJ6/G4eM7SaY3iSYVOZ0D5SqZ4VKV0o7F5rbFx+UC1LFM9u6ehtNmy32g7H+y2BrQebYx0i4G/gLDZrDGZ+LiT2lB0E4pqYDr/wmw+xe+Vf3yHy8jY7RmuQmwBMJMbQYYHsSYAAAAASUVORK5CYII=","aspectRatio":4.545454545454546,"src":"/static/d52c66fd6a9bf82b6641c6bac7b8ef11/12f10/cta-demo-1-1300x288.png","srcSet":"/static/d52c66fd6a9bf82b6641c6bac7b8ef11/16ac3/cta-demo-1-1300x288.png 300w,\n/static/d52c66fd6a9bf82b6641c6bac7b8ef11/0e1f9/cta-demo-1-1300x288.png 600w,\n/static/d52c66fd6a9bf82b6641c6bac7b8ef11/12f10/cta-demo-1-1300x288.png 1200w,\n/static/d52c66fd6a9bf82b6641c6bac7b8ef11/808ee/cta-demo-1-1300x288.png 1300w","srcWebp":"/static/d52c66fd6a9bf82b6641c6bac7b8ef11/8edc8/cta-demo-1-1300x288.webp","srcSetWebp":"/static/d52c66fd6a9bf82b6641c6bac7b8ef11/fdc3b/cta-demo-1-1300x288.webp 300w,\n/static/d52c66fd6a9bf82b6641c6bac7b8ef11/47049/cta-demo-1-1300x288.webp 600w,\n/static/d52c66fd6a9bf82b6641c6bac7b8ef11/8edc8/cta-demo-1-1300x288.webp 1200w,\n/static/d52c66fd6a9bf82b6641c6bac7b8ef11/38bb9/cta-demo-1-1300x288.webp 1300w","sizes":"(max-width: 1200px) 100vw, 1200px"}}}},"ctaTitleSticky":null,"ctaTextSticky":null,"ctaButtonType":"link","ctaLink":{"target":"","title":"Get a demo","url":"https://www.immersivelabs.com/talk-to-an-expert"},"ctaButtonText":"Get a demo","ctaFormIntroContent":"<h3>Get a demo of Immersive Labs</h3>\n<p>Take just 30 minutes <span style=\"font-weight: 400;\">to embark on your journey to human cyber readiness. </span></p>\n","ctaHubspotPortalId":"3792509","ctaHubspotFormId":"7a5cd709-0200-4283-bb38-d32420a4efb7","ctaHubspotScript":"<!--[if lte IE 8]>\r\n<script charset=\"utf-8\" type=\"text/javascript\" src=\"//js.hsforms.net/forms/v2-legacy.js\"></script>\r\n<![endif]-->\r\n<script charset=\"utf-8\" type=\"text/javascript\" src=\"//js.hsforms.net/forms/v2.js\"></script>\r\n<script>\r\n  hbspt.forms.create({\r\n\tportalId: \"3792509\",\r\n\tformId: \"c008c5d5-2905-43ec-b9d0-2cfb1ee40a1d\",\r\n\tsfdcCampaignId: \"7014J000000dKYlQAM\",\r\n\tgoToWebinarWebinarKey: '3209713448464516878'\r\n});\r\n</script>"}}}}],"seo":{"title":"Open Source Platform Vulnerabilities & OSSN - Immersive Labs","metaDesc":"Learn how Immersive Labs’ Director of Cyber Threat Research, Kev Breen, recently uncovered a weakness in a tool dubbed the Open Source Social Network.","metaKeywords":"","canonical":"","opengraphType":"article","opengraphTitle":"Open Source Platform Vulnerabilities & OSSN - Immersive Labs","opengraphDescription":"Learn how Immersive Labs’ Director of Cyber Threat Research, Kev Breen, recently uncovered a weakness in a tool dubbed the Open Source Social Network.","opengraphImage":{"sourceUrl":"https://cms.immersivelabs.com/content/uploads/2020/04/shutterstock1023953008.jpg","uri":"https://www.immersivelabs.com/resources/blog/cve-2020-10560-ossn-arbitrary-file-read/shutterstock_1023953008/"}},"featuredImage":{"sourceUrl":"https://cms.immersivelabs.com/content/uploads/2020/04/shutterstock1023953008.jpg","imageFile":{"publicURL":"/static/1b0257ed0161bdc6ef812b43c67bee9f/shutterstock1023953008.jpg","childImageSharp":{"fluid":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMFAv/EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAFFKfVFxkj/xAAbEAACAQUAAAAAAAAAAAAAAAABAhEDEBITMf/aAAgBAQABBQJEGeqGAkUerb//xAAXEQADAQAAAAAAAAAAAAAAAAABAhAR/9oACAEDAQE/AVOT/8QAFxEAAwEAAAAAAAAAAAAAAAAAAQIQEf/aAAgBAgEBPwFhs//EABkQAAIDAQAAAAAAAAAAAAAAAAABESExIP/aAAgBAQAGPwJJ4VhcTx//xAAbEAADAAIDAAAAAAAAAAAAAAAAAREhUTFBcf/aAAgBAQABPyFzGXqUTKtOrNEBqngZ93wXhUbyf//aAAwDAQACAAMAAAAQJN//xAAWEQEBAQAAAAAAAAAAAAAAAAABACH/2gAIAQMBAT8QTSy3/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARIf/aAAgBAgEBPxABgRwv/8QAHBAAAgICAwAAAAAAAAAAAAAAAREAITFhcZGh/9oACAEBAAE/EB5kFlU3DWMmisEHxBJEWEPsLapiiXHHEAZ3N7uf/9k=","aspectRatio":1.5,"src":"/static/1b0257ed0161bdc6ef812b43c67bee9f/1276b/shutterstock1023953008.jpg","srcSet":"/static/1b0257ed0161bdc6ef812b43c67bee9f/6aaa1/shutterstock1023953008.jpg 300w,\n/static/1b0257ed0161bdc6ef812b43c67bee9f/05631/shutterstock1023953008.jpg 600w,\n/static/1b0257ed0161bdc6ef812b43c67bee9f/1276b/shutterstock1023953008.jpg 1000w","srcWebp":"/static/1b0257ed0161bdc6ef812b43c67bee9f/a1ba4/shutterstock1023953008.webp","srcSetWebp":"/static/1b0257ed0161bdc6ef812b43c67bee9f/fdc3b/shutterstock1023953008.webp 300w,\n/static/1b0257ed0161bdc6ef812b43c67bee9f/47049/shutterstock1023953008.webp 600w,\n/static/1b0257ed0161bdc6ef812b43c67bee9f/a1ba4/shutterstock1023953008.webp 1000w","sizes":"(max-width: 1000px) 100vw, 1000px"}}}},"tags":{"edges":[{"node":{"name":"Blog","slug":"blog","count":114,"attributes":{"colorTheme":"electricBlue"}}}]},"categories":{"edges":[{"node":{"name":"All Resources","slug":"all-resources","count":352,"attributes":{"colorTheme":"electricBlue"}}},{"node":{"name":"Blog","slug":"blog","count":189,"attributes":{"colorTheme":"electricBlue"}}}]},"attributes":{"schema":"{\r\n  \"@context\": \"https://schema.org\",\r\n  \"@type\": \"NewsArticle\",\r\n  \"mainEntityOfPage\": {\r\n    \"@type\": \"WebPage\",\r\n    \"@id\": \"https://google.com/article\"\r\n  },\r\n  \"headline\": \"$title\",\r\n  \"image\": [\r\n    \"$featuredImage\"\r\n   ],\r\n  \"datePublished\": \"$date\",\r\n  \"dateModified\": \"$modified\",\r\n  \"author\": {\r\n    \"@type\": \"Person\",\r\n    \"name\": \"$author\"\r\n  },\r\n   \"publisher\": {\r\n    \"@type\": \"Organization\",\r\n    \"name\": \"Immersive Labs\",\r\n    \"logo\": {\r\n      \"@type\": \"ImageObject\",\r\n      \"url\": \"$logo\"\r\n    }\r\n  }\r\n}","postIncognito":null},"author":{"name":"Immersive Labs","attributes":{"userAvatarImage":{"altText":"","sourceUrl":"https://cms.immersivelabs.com/content/uploads/2020/06/0bdac40f82cf9e85a5e1cc04ef4a9af2.jpg","imageFile":{"publicURL":"/static/4b4dc210573184658e94223497b65cd5/0bdac40f82cf9e85a5e1cc04ef4a9af2.jpg","childImageSharp":{"fluid":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAUEA//EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAaWTdOKwOU4KgP/EAB0QAAICAQUAAAAAAAAAAAAAAAEDAAIEERIiIzH/2gAIAQEAAQUCeSFrO1sYNaJt2S3mKOU//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAGxABAAICAwAAAAAAAAAAAAAAAQIQABEhMWH/2gAIAQEABj8C4de4BKUh73Thtta//8QAHBAAAgICAwAAAAAAAAAAAAAAAREAMRAhQVHR/9oACAEBAAE/IeRE76CNLBYFwXYgEO9wW0ALRFRz/9oADAMBAAIAAwAAABCzDwD/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAcEAEAAgIDAQAAAAAAAAAAAAABABEhQRAxUWH/2gAIAQEAAT8QcK0AC3ZUfyQBMXjhHFEs8SOcEsL0uFS6sTEcGDSdZl/k/9k=","aspectRatio":1,"src":"/static/4b4dc210573184658e94223497b65cd5/5c772/0bdac40f82cf9e85a5e1cc04ef4a9af2.jpg","srcSet":"/static/4b4dc210573184658e94223497b65cd5/07fa7/0bdac40f82cf9e85a5e1cc04ef4a9af2.jpg 150w,\n/static/4b4dc210573184658e94223497b65cd5/5c772/0bdac40f82cf9e85a5e1cc04ef4a9af2.jpg 192w","srcWebp":"/static/4b4dc210573184658e94223497b65cd5/0814e/0bdac40f82cf9e85a5e1cc04ef4a9af2.webp","srcSetWebp":"/static/4b4dc210573184658e94223497b65cd5/ef536/0bdac40f82cf9e85a5e1cc04ef4a9af2.webp 150w,\n/static/4b4dc210573184658e94223497b65cd5/0814e/0bdac40f82cf9e85a5e1cc04ef4a9af2.webp 192w","sizes":"(max-width: 192px) 100vw, 192px"}}}},"userJobTitle":"Senior Developer"}}}}},"pageContext":{"id":"cG9zdDo3MTY3","slug":"cve-2020-10560-ossn-arbitrary-file-read"}},"staticQueryHashes":["1134781590","1430943121","1691173908","2407081983","2499683418","3123027226","3518138710","3678308812","4001368598","4116960265","437961647"]}