PHP often known as a ‘loosely typed’ programming language

If we recall in PHP, no data types in any variable have to define. In the circumstance of comparisons of different variable, PHP will automatically convert the data into same data type.

For example, if we want to compare integer to string. PHP will convert string to integer.
Let’s assume a situation. We have a input field asking for the number of bottle.

<?php
if(isset($_GET['num_bottles'])){
$num_bottles = $_GET['num_bottles'];
if($num_bottles == 0){
echo "You have order 0 bottles\n";
}
if($num_bottles == 1){
echo "You have order 1 bottles\n";
}
if($num_bottles == 2){
echo "You have order 2 bottles\n";
}
if(!is_numeric($num_bottles)){
echo "Please enter only numbers\n";
}
}
else{
echo "Input numbers";
}
?>

When we try to submit 1 to num_bottles. As we say early on, when comparing string($_GET[num_bottles] will be string data type) and integer, it will auto convert strings to integer. so it match the first if statement

┌──(root💀kali)-[~/Desktop/php-audit/day1]
└─# curl localhost:8080/test.php?num_bottles=1
You have order 1 bottles

It seems nothing special that "1" == 1
What if the user input is “1bottle” ?

YES, PHP will treat “2bottles” as 2 because of it’s loosely comparison. It will abstract the leading numbers from the beginning of string and convert to integer.

┌──(root💀kali)-[~/Desktop/php-audit/day1]
└─# curl localhost:8080/test.php?num_bottles=1bottles
You have order 1 bottles
Please enter only numbers


┌──(root💀kali)-[~/Desktop/php-audit/day1]
└─# curl localhost:8080/test.php?num_bottles=2bottles
You have order 2 bottles
Please enter only numbers

you might ask, what if there are no numbers?
PHP will treat the string as 0

┌──(root💀kali)-[~/Desktop/php-audit/day1]
└─# curl localhost:8080/test.php?num_bottles=bottles
You have order 0 bottles
Please enter only numbers

CTF Challenge - in_array() type juggling

CTF challenge from PHP SECURITY CALENDAR

class Challenge {
const UPLOAD_DIRECTORY = './solutions/';
private $file;
private $whitelist;

public function __construct($file) {
$this->file = $file;
$this->whitelist = range(1, 24);
}

public function __destruct() {
if (in_array($this->file['name'], $this->whitelist)) {
move_uploaded_file(
$this->file['tmp_name'],
self::UPLOAD_DIRECTORY . $this->file['name']
);
}
}
}

$challenge = new Challenge($_FILES['solution']);

The have to bypass the restriction of white listing check with the function in_array()

Description

in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) : bool

Searches for needle in haystack using loose comparison unless strict is set.

Parameters

neddle = The Searched Value

haystack = The array.

strict
If the third parameter strict is set to TRUE then the in_array() function will also check the types of the needle in the haystack.

How in_array() is by comparing a needle to every values in an array. When strict is not set to TRUE, it will not restrict in data types. That’s when PHP loosely comparison come into play.

if we want to upload a malicious PHP files, the filename has to be end with .php but with the restricted white list, we are only allow to send file in the range of 1 - 24.

We can easily construct a file with leading numbers, will bypass the in_array() check

Environment

Docker for convenient

docker run --name app8 -d -p 8080:80 -v $(pwd):/var/www/app romeoz/docker-apache-php:7.0

A simple file for uploading

<!doctype html>
<html>
<body>
<form action="day1.php" method="POST" enctype="multipart/form-data">
<input type="file" name="solution"><br>
<input type="submit" name="submit" value="Submit">
</form>
</body>
</html>

name the malicious file as 1malicious.php will bypass the restriction

CVE example

TBD