osu!gaming CTF 2025 web/admin-panel writeup

thanks to strellic for this challenge

263 solves / 109 points

Description:
 we found the secret osu! admin panel!!
can you find a way to log in and read the flag?
expected difficulty: 1/5

Author: strellic

Download: web_admin-panel.tar.gz

The challenge consists of a two bug chain:

1) Auth bypass using strcmp() type juggling

The login code uses strcmp() to verify the password:

if ($username == "peppy" && strcmp($admin_password, $password) == 0) {
    $_SESSION["logged_in"] = true;
    header("Location: admin.php");
    exit();
}

If you send an array instead of a string for the password parameter (password[]), strcmp() will return NULL instead of comparing strings.

PHP has two comparison operators:

  • == (loose comparison) - “kinda match” - compares values after type conversion
  • === (strict comparison) - “exact match” - compares both value and type

The code uses ==, which performs loose comparison. When PHP compares NULL == 0:

  1. PHP converts both values to a common type
  2. NULL gets converted to boolean false
  3. 0 also converts to boolean false
  4. Therefore: NULL == 0 evaluates to TRUE
strcmp(NULL, "anything") == 0 // true (NULL == 0 using loose comparison)

If the code used === instead, this exploit wouldn’t work:

strcmp(NULL, "anything") === 0 // false (NULL !== 0 using strict comparison)

So just send a request with username “peppy” (was hardcoded) and specify password using password[] to pass it as an array. The value of the password[] doesn’t matter.

Login request

yes, it will result in an error, but it doesn’t matter because the state on the backend has changed so you can move to the admin.php page manually

Error page

2) PHP code execution using .htaccess override

The environment uses FROM php:7.2-apache docker image with default configuration that only executes .php files as code:

$ sudo docker exec admin-panel grep -C 5 -r "SetHandler.*php" /etc/apache2/
/etc/apache2/conf-available/docker-php.conf-<FilesMatch \.php$>
/etc/apache2/conf-available/docker-php.conf: SetHandler application/x-httpd-php
/etc/apache2/conf-available/docker-php.conf-</FilesMatch>

It means trying to avoid the first filter using other extensions like .phtml, .php5, etc. is meaningless because backend won’t even try to execute them. The only way to get code execution is to pass the .php extension through, but the filter wouldn’t let us. Or we can just bend the rules of what files can be executed as PHP code:

the default configuration contains:

$ sudo docker exec admin-panel grep -r "AllowOverride" /etc/apache2/
/etc/apache2/conf-available/docker-php.conf: AllowOverride All

.htaccess (Hypertext Access) is a configuration file used by Apache web server that allows directory-level configuration overrides. When AllowOverride All is enabled (as it is here), you can place a .htaccess file in any directory to modify Apache’s behavior for that specific directory and its subdirectories.

Normally, Apache is configured to only treat .php files as executable PHP code. The .htaccess file allows us to reconfigure Apache’s file handling rules at the directory level, telling it to treat other file extensions as PHP code as well.

  1. Upload .htaccess file with content:

    AddType application/x-httpd-php .jpg
    

    This reconfigures Apache in that directory to execute .jpg files as PHP code (it could be any extension). The AddType directive associates the MIME type application/x-httpd-php with .jpg files, instructing Apache to pass them to the PHP interpreter.

  2. Upload payload.jpg with content:

    <?= file_get_contents('/flag.txt'); ?>
    
  3. Access the file to execute the code and retrieve the flag.

The <?= short tag is used here as an alias for <?php to bypass any code filtering. According to PHP documentation, the <?= shorthand is always available.

Flag