Cyberleague Quarterfinals 2022 - Pretty Hyper Phrog
Writeup for one of the web challenges I set for Cyberleague 2022.
Here is the source
The main vuln is PHP deserialization. In PHP, this allows for arbitrary properties to be assigned to objects. In addition, the class of the object can also be controlled. With these interesting capabilities of unsafe deserialization, I made a 2-phase challenge that will require one to leverage both concepts + some other exotic concepts to ultimately get RCE.
Phase 1: LFI
Since the source wasn’t provided in the CTF, our first goal is to get some sort of LFI to get the complete source.
Here’s a sample “password”, our attack vector:
Tzo0OiJVc2VyIjozOntzOjc6InBpY3R1cmUiO3M6MTY6InN0YXRpYy9waHJvZy5naWYiO3M6ODoidXN lcm5hbWUiO3M6ODoic2FtdXpvcmEiO3M6NDoidXVpZCI7czoxMzoiNjNhNDYyNTViNGM5YiI7fQ== |
We know that each user’s password is a PHP serialized object, and there is a
property picture
that is pointing to static/phrog.gif
. After some
experimenting we see that this is passed to file_get_contents
, which we can
use to enumerate the source.
Phase 2: RCE
We not only want LFI, we want RCE too! With big and well-known frameworks, PHP deserialization is a piece of cake, as there are ready-made RCE gadget chains online. However, this is a custom implementation so it doesn’t exist. We need to make it ourselves :(
Some of the leaked files:
index.php
|
user.php
|
util.php
|
Some things to note:
- In
Conn
, the query is being prepared manually. This could maybe give us SQLi, if we manage to bypass the (not so stringent) checks. Props to RVCTF who found an unintended solution via the weak escaping. (The reason why I had to implement my own query, is because we need multiple queries for the exploit - and I couldn’t find a driver that could allow me to do that. So I exploded based on;
and ran each query manually.) - In
Conn
,__call
is being used to invoke the query function. This magic method defines a fallback for undefined properties that are invoked.
As you might guess based on the context of the challenge, we can change $temp
(and hence $user
) into a $Conn
object. This would allow us to control the
$query
array in our poisoned object and execute our own queries via the
->whoami()
call (since it doesn’t exist on a $Conn
, it will fallback to
__call()
). While we do this, we also need to ensure that ->username
and
->uuid
remains constant so we pass the check.
With this we can execute arbitrary SQL (should I call this SQLi?). But how to get RCE?
SQLite uses a single-file database locally, instead of connecting to a remote service. And we can attach new databases to arbitrary locations, which creates a file with some binary content, as well as whatever we insert into the new database.
So in a fashion similar to PHP web shells, we can create a malicious SQLite
database in /var/www/html
, create a table and insert a row with our web
shell, thus gaining RCE!
Below is the code to generate the payload:
|