02-07-2019 / From 0 to pentesting hero

Don't use assert in PHP

In today's episode of "from 0 to pentesting hero" we'll talk about why you shouldn't pass variables to assert function in php.

Today we are going to take a close look at one of tasks from Hack.lu CTF 2018.

We get the source code of the application. Our goal is to pass such parameters that will cause the last line to show - it is the so-called flag.

Source code

Let's try from the very beginning.

We will copy the file to our local server.

We will also enable error handling - by commenting the line with error_reporting.

Thanks to this, if the script returns an error - we will know about it.

First, we need to bypass the file_get_contents function.

It is responsible for downloading the contents of files and returning them as a string.

The result of this function is compared to the Hello challenge string.

So in order to go further, we would need to know the name of the file that contains this content.

However, the probability that such file exists on the attacked server is zero.

Therefore, there must be some other way of attack.

In PHP we have so-called filters1 - that when passed as a parameter to the function, make it behave a bit differently.

Filters

One of such filters is data://text/plain, when passed as parameter, instead of opening a file with the given name, it displays the content encoded in base64.

We must therefore encode the Hello challenge into base64 and then append it to the filter.

data://text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==

Now we encode everything to the encoding recognized by the browser and pass it as the msg parameter.

As you can see, we are one step ahead.

The next task is to bypass the intval function, that returns int from the given string.

We are dealing here with a boolean operator or.

So in order to go further, the result of the intval function must be equal to the $cc variable - that is 1337, and in addition the parameter passed to the intval function must differ from 1337.

The solution is very easy. Intval works in such way: it reads a string starting from the very beginning until it finds the first non-digit character.

So if we pass 1a - it will return 1.

If we pass 23b - it will return 23.

intval function

So in our case, we have to pass 1337 and that will meet the first condition.

The second one will also be satisfied because the $cc variable is a number and the $k1 parameter is a string.

Three equality signs were used to compare variables - that is, in addition to the value, the variable type is also checked.

In our case, string and int are two different types.

The third step is regular expressions.

We know that the key2 parameter must have exactly 42 characters - thanks to the strlen function.

Then, using a regular expression, we check if there are only numbers in the given string.

The caret means that we check the string from its first sign.

The dollar sign, on the other hand, that we check the whole string to its last sign.

An additional condition that we must meet is that the given parameter can not be a number.

So how do you make the string contain only numbers but not be a number itself?

Here, the authors of the task used a very clever solution.

The dollar sign that they used is not a real dollar sign.

To show this difference, we will present a real dollar sign next to that used by the creators.

Fake dollar sign

The characters are similar, but not the same.

So if you do not use the dollar sign, it means that the string must start with at least one digit, and then the fake dollar sign must occur.

The rest of the sequence is not checked.

It just so happens that a string with a fake dollar sign is not treated as a number by the is_numeric function.

So we create any number, append the fake dollar sign and fill the remaining numbers - so that the whole string is equal to 42 characters.

Remember, however, that the dollar is saved in different encoding and php counts it as 3 characters.

A double equality sign is used here - which is somewhat similar to the intval function.

So we modify our content so that it starts from 1337, then a fake dollar and the rest of the characters.

Now we can set the $cc parameter.

Next, with the help of substr function, the script retrieves all characters from the $cc parameter, starting with the 42 letter.

And then checks if this string is equal to sha1 hash of the contents of the $cc parameter.

At first glance, it looks like finding collisions in the $sha1 function.

It is probably possible, but it took over a year for people from Google2, and they had many more resources.

We must therefore look for an alternative solution.

When we take a look at the php manual, we'll see that the sha1 function expects the $cc parameter to be a string.

SHA1 function

What would happen if we pass array there? Let's see.

In PHP, if the argument name ends with square brackets - it is treated as an array.

As you can see we are one more step further.

Why? In PHP, if we pass tables to the sha1 function, it returns null.

Substr returns false if the string is too short - as in this case.

If we look at the table3, we'll see that null with false gives true. All the magic of PHP.

How compare works in PHP

The next line is very deceptive. It looks differently in the browser than in our text editor.

Do you see the difference? How is this possible?

We will use the google chrome development tools to check the code.

We can see here a line with ampresand, hash and value of 8238.

Quick googling reveals that this is the so-called "Right-To-Left"4 sign, mainly used in Arabic languages.

After using it, subsequent characters are displayed in reversed order.

You can see it when you hover over it in the console. Here they are displayed in the correct order, but the browser reverses them.

Right-To-Left

Our text editor displays them correctly.

However, a strange syntax is used here. Two dollar signs before the 'a' value?

In php these are so-called Variable variables.

Reversed chars

The easiest way to demonstrate this is by example, let's look at Stackoverflow5.

We display the contents of the $name variable here.

First, we take its value - that is, real_variable and then we look for a variable with exactly that name and only then we display it.

In this case, real_variable has a value of test and exactly this value will appear on our screen.

Let's get back to the code. Two dollar signs were used here with the a variable.

The a variable in the line above has the value of b assigned.

So we take the value of b that is 2.

That is, to go further, the value of k1 must be equal to 2.

Unfortunately, we have already set key1 to 1337 - to meet the previous conditions

Therefore, we can not make the variable once equal to 1337 and the second time to be 2.

And here comes to the rescue the line with foreach. With it, we can overwrite the value of any variable,

because the variable variables construction is used there.

This time it is enough to pass 2 in the parameter named k1.

In this way, in line 40 the value of k1 will be set to 2.

We have the last condition to fulfill.

The assert function checks if the given condition is met and if it is not - it ends the program execution.

This is visible in the last line with an error, where we see that 42 is not equal to Array.

42 is the value of the $bb variable and array is the value of the $cc variable.

We can not modify the value of $cc here - because it is needed earlier when bypassing the sha1 function.

This may make us think that maybe the value of $bb should be changed.

We can change it using variable variables construction.

Let's try to change it to array.

This time we received a message that Array and Array can not be compared with each other.

Strange, right?

Also the message in the line above changed to syntax error.

It appears when the code passed to php is incorrect. But just a moment ago this line wasn't here.

Where did it come from?

Syntax error

When we look closer at the assert6 function, we can read that the string of characters passed to it is evaluated as a php code.

That is, assert can be compared to the eval function.

Assert

The array === array is not valid. So let's try to put in some valid code there.

For example, using the function die(), which displays the parameter passed to it and ends the execution of the script.

Thanks to this, even if the next part will be incorrect - the function should work.

So let's try to display the contents of the flag - adding double slash to the string, to make a comment of the rest of the data.

Congratulations. We've just displayed the contents of the flag.

And that's all for today. A very interesting and non-trivial task, that shows the danger of using the assert functionality.

=creation