InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties…

Follow publication

The Buffer Curse

Felix Alexander
InfoSec Write-ups
Published in
10 min readJul 16, 2023

Security perimeter is always a matter in an application’s components. How many paranoid developers out there who put all of the best practices already in patching out known vulnerabilities in their SDLC but suddenly realizes a certain poor design would bring a devastating impact to their beloved assets?

Prelude

Have you ever developed an application before? No matter what medium that you built, either it’s a mobile, desktop/GUI or web application, each requires a good and precise planning to create it. As a software developer, you are obligated to use a managed framework toolkits and good code practices in order to produce a nexus-quality application.

The next thing you do to qualify the software testing may also require certain key metrics. Most of the key metrics often come with the business strategy plan if the software is related to the corporate’s mission, and one of the metric which you shouldn’t be blind of is security.

There are numerous security frameworks which may assist you to fulfill the prominent metrics especially when it is related into dealing with a “crown-jewels” data involvement. Moreover, some application framework does have a built-in security-centric components such as their middleware itself. This makes your application as a perfect example that places those aspects at the top level. But are you sure that you’ve done enough?

Code’s Maestro

There are multiple prerequisites that yield a good code. In this scenario, we’ll dive into PHP. Is PHP still a thing in 2023? I guess it is since there’s still a framework maintained until now, such as Laravel.

There are many vulnerabilities which may be exposed to the world once the developer has done a poor implementation in making the backend logic structure. As for the most common ones start with an unfiltered user’s input, insecure deserialization, and et cetera. Those common causes are easily to be prevented and mostly nowadays, those code architects are already aware and they have done a lot of basic secure code reviews for the one who’s always ready to commit.

But sometimes, an output of the code may become a security concern, which yields a unique attack vector, as we can consider this for an unsafe memory handling in a web page.

HTTP Buffering

There’s a term called “Output Buffering” in programming. The functionality is to store a data inside a temporary buffer before it yields and printed out. The buffer itself acts as a middle-layer. For example, in Python, we might’ve come up with a buffer control like the following lines.

class Unbuffered(object):
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def writelines(self, datas):
self.stream.writelines(datas)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)

In this case, user may use the Unbuffered class to be assigned to the stdout handler, and every data which was in a form of casted object will be moved to the output when the flush is called.

The concept goes the same like Output Buffering especially in PHP. The concept controls how much the data output which PHP should store internally before the data itself is pushed out to the client. This doesn’t include headers & cookies.

Without output buffering, the schema would be like this:

The data source refers to the PHP script, and once it calls the function which has a behavioral intention to output something to the client’s browser, it’ll immediately be showed.

But with output buffering, the schema is quite different. The PHP script “logistics” will bring out the data into a container-like middle layer first which we’ve called the output buffer.

Those stored data inside the buffer (truck) will be delivered into the client’s browser once the application execution flow meets flush() or ob_flush() function call(s). This is what the user should see in the internal process when the output buffering is used.

By default, the default storage of output buffer holds up to 4KB or to be exact, 4096 bytes as this is written in the php.ini directory. If the buffer exceeds it, PHP will send those data back so it is a forced buffer flush. It’ll be either to the PHP FastCGI or any PHP backend used.

Attacker’s Exordium

Overall, it doesn’t expose a security threat but it can be leveraged to be an exploitation vector once attacker sees a spot of the code which poorly designed without a security concern.

Inspired from justCTF 2020, I modified the challenge that shares the same concept on bypassing a modern security layer called Content-Security Policy (CSP).

A quick glance on CSP, as from the MDN Web Docs states,

Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft, to site defacement, to malware distribution.

Developers will see this as one of the defense approach to tackle a classic yet effective client-side attack such as XSS since CSP consists of a directives (a rules to/not to load a resources/script specifically) and a sources (from which resources can be loaded).

How do they implement CSP? Before implementing them, developer needs to be aware of their expectation on how to load a resources safely. If they want to load a resources from the same domain only, they could use default-src directive with a 'self' source. There’s an exception for image loading since they also want to whitelist a domain that has a certain image to be loaded from (for example, medium.com) and also the same domain, so they could use img-src directive with a multiple sources which are 'self' medium.com .

These can be written in:

  • HTTP Headers
Content-Security-policy: default-src 'self'; img-src 'self' medium.com;
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' medium.com;">

Once done, developer can try to evaluate their CSP through https://csp-evaluator.withgoogle.com/.

Suppose the developer tries to implement a secure CSP inside the HTTP header, and the developer would only allow the resource loading from the same domain only. If it’s necessary for a Javascript to run in the application, the developer uses a cryptographic approach as a side attribute of the script tag(s) called nonce . It’s often used in authentication as well.

Image References: https://en.wikipedia.org/wiki/Cryptographic_nonce

This nonce will be a one-time-use random generated number so that it should not be statically assigned. There should be a random generated value each time since the developer wants no attackers can predict the value in order for them to use the script tag.

Below is attached a PHP script which takes a maximum of 2 GET parameter inputs from the user and renders it back so the user’s input is reflected.

<?php

function secure_nonce($sbox){
$rm = hash('sha512',random_bytes(16));
$rn = hash('sha512',random_bytes(16));
$rv = hash('sha512',random_bytes(16));
$tnonce = array($rm=>random_int(8,16), $rn => random_int(8,16), $rv => random_int(8,16));
$newcollider = "";
$x = 0;

while( $x < 4){
if(substr(htmlspecialchars($sbox,ENT_QUOTES), 0, 6) !== "nonce-"){
trigger_error(htmlspecialchars($sbox,ENT_QUOTES) . " should've had a nonce.");
}
$newcollider .= $sbox;
$newcollider .= $tnonce[$rm];
$newcollider .= $tnonce[$rn];
$newcollider .= $tnonce[$rv];
$x++;
}
return $newcollider;
}

$rand = openssl_random_pseudo_bytes(random_int(0xff,0xffff));
$rand_nonce = hash('sha512',bin2hex($rand));
$temp = $rand_nonce;

if(isset($_GET['sbox'])){
$additional_data = secure_nonce($_GET['sbox']);
try{
$rand_nonce = hash('sha512',(hash('sha512',$rand_nonce) . hash('sha512',$additional_data)));
} catch(Exception $e){
$rand_nonce = $temp;
}
}


if(isset($_GET['canvas'])){
header("Content-Security-Policy: default-src 'none'; object-src 'none'; base-uri 'none'; script-src 'nonce-$rand_nonce'");
header("X-XSS-Protection: 1");
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
header("Pragma: no-cache");
header("Cache-Control: no-cache, no-store, must-revalidate, max-age=0, s-maxage=0");
echo "<p>Free Sandbox Canvas, insert your whatever fancy HTML here!<p></br><h3>{$_GET['canvas']}</h3>";

}else{
show_source(__FILE__);
}

?>

What are some key points that you may state from the attached PHP script above? Shouldn’t there be an attack vector to trigger the Cross-Site Scripting (XSS)?

Bypassing CSP

There are some workarounds especially for an attacker who has some advanced techniques and knowledges to bypass the CSP header, and only if the CSP itself might be controllable or the developer puts a lot of trusts on some sources which may have a JSON Callback, since this callback may eventually triggers a Javascript Execution as well.

However, it is a little bit different in this scenario. Let’s have a closer look.

 header("Content-Security-Policy: default-src 'none'; object-src 'none'; base-uri 'none'; script-src 'nonce-$rand_nonce'");
header("X-XSS-Protection: 1");
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
header("Pragma: no-cache");
header("Cache-Control: no-cache, no-store, must-revalidate, max-age=0, s-maxage=0");
echo "<p>Free Sandbox Canvas, insert your whatever fancy HTML here!<p></br><h3>{$_GET['canvas']}</h3>";

The application accepts a GET parameter which is controlled by the user to input, and common attack vector is also spotted since there’s an improper input validation whereas the result of the GET parameter is reflected directly, enabling all characters are accepted including those dangerous ones, like >, ;, ',",., #, % and et cetera.

Unfortunately, it’s not that easy since the CSP header declares that the Javascript execution needs an exact nonce , stored in a $rand_nonce variable. Other than that, is a pretty strict resource loading since default-src only allows the resource from the same domain to be loaded.

$rand = openssl_random_pseudo_bytes(random_int(0xff,0xffff));
$rand_nonce = hash('sha512',bin2hex($rand));
$temp = $rand_nonce;

if(isset($_GET['sbox'])){
$additional_data = secure_nonce($_GET['sbox']);
try{
$rand_nonce = hash('sha512',(hash('sha512',$rand_nonce) . hash('sha512',$additional_data)));
} catch(Exception $e){
$rand_nonce = $temp;
}
}

The $rand_nonce is calculated by a generated number from openssl_random_pseudo_bytes and it’s hashed by SHA512 hash algorithm. At first, you might think that somehow this random generated number might yields a 0 or NULL since from the PHP 8 according to the documentation, the second parameter might be nullable. Unfortunately, no flags is passed as a second parameter.

There’s also another user-controlled GET parameter (sbox) , and it is processed to the secure_nonce function. The final product of the returned value function would be appended with another SHA512-hashed $rand_nonce and hashed again.

function secure_nonce($sbox){
$rm = hash('sha512',random_bytes(16));
$rn = hash('sha512',random_bytes(16));
$rv = hash('sha512',random_bytes(16));
$tnonce = array($rm=>random_int(8,16), $rn => random_int(8,16), $rv => random_int(8,16));
$newcollider = "";
$x = 0;

while( $x < 4){
if(substr(htmlspecialchars($sbox,ENT_QUOTES), 0, 6) !== "nonce-"){
trigger_error(htmlspecialchars($sbox,ENT_QUOTES) . " should've had a nonce.");
}
$newcollider .= $sbox;
$newcollider .= $tnonce[$rm];
$newcollider .= $tnonce[$rn];
$newcollider .= $tnonce[$rv];
$x++;
}
return $newcollider;
}

This function is pretty absurd as well since the sbox will be concatenated with a hashed random data. Unlike canvas parameter, sbox parameter is filtered by htmlspecialchars and there’ll be an output error if the prefix sbox doesn’t start with nonce- .

No nonce- prefix specified
Nonce- prefix used

Now remember how the output buffer handles the PHP script and transfers it back to the client’s browser? Since we can control our input, we can control the output of the script that is reflected to the client’s browser as well. The attack vector is from the error especially from trigger_error function.

The bug here is that the application’s response/output will not be sent first, but the header should. The body data request will be sent to the output buffer before the HTTP header since there’s no data returned before the header. This means the CSP inside the header will be ignored if we provide enough data more than the default PHP output_buffer size (4096 bytes), and sbox parameter is the perfect spot for the attacker to control to trigger the error.

We’ll be using BurpSuite to intercept the request and visualize the response from the application. We can confirm that the CSP is really in the header.

Now we’ll try to populate the sbox parameter with a junk buffer data so that the CSP header would be ignored.

This is not enough, let’s try to add more data so that the buffer is more than 4096 bytes (from the warning triggered by the sbox ).

As we can see, the CSP is not reflected in the application’s response and our request including the Javascript execution is triggered. This can be shown from the client’s browser.

Further Research

Although modern security layer is keep getting updated and more security-perimeter innovation will be born, developer should also consider to prioritize a good code building by not only relying to those security-centric middleware or any. Thus, this will make them to think more on what would happen later on.

Love my content? You can support me on Trakteer/BMC 😁. Thank you for reading this writings and I hope you gain a new knowledge from here!

https://trakteer.id/felix-alexander-swfnt/tip?open=true

References

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in InfoSec Write-ups

A collection of write-ups from the best hackers in the world on topics ranging from bug bounties and CTFs to vulnhub machines, hardware challenges and real life encounters. Subscribe to our weekly newsletter for the coolest infosec updates: https://weekly.infosecwriteups.com/

Written by Felix Alexander

Penetration Tester, DFIR & Reverse Engineering Enthusiast

No responses yet

Write a response