Мониторинг на дневници Такъв уязвим дневник или как да поставите прасе на колеги
Наблюдението или анализирането на регистрационни файлове, независимо дали става въпрос за сигурност, анализ на натоварването или създаване на статистики и анализи за продавач или захранване на някакъв вид невронна мрежа, често е свързано с много проблеми.
За съжаление, това често се свързва с човешкия фактор, а именно с нежеланието или неразбирането на някои доста прости неща от много разработчици на програми, API и услуги, които регистрират самата информация, която е толкова необходима за наблюдение. По-долу е точно как често се прави това и защо е невъзможно да се живее по този начин. Ще говорим за формати на журнали, ще анализираме няколко примера, ще напишем някои регулярни изрази и т.н.
Уважаеми колеги, разбира се, зависи от вас как и какво ще пишете в регистрационните файлове на вашата програма, но все пак си струва да помислите дали го правите само за себе си ... Може би, освен вас, някой потребител на вашата програма сега гледа този ред с отчаяние или дори умен до невъзможност, но ругае колко напразно, бот.
Бях принуден да напиша тази публикация от друг файл с лог формат, който не е лесен за анализ, което доведе до друга "уязвимост", до писане на готов експлойт в процеса на търсене.
И ако вдъхновя поне един разработчик да мисли с тази статия, това вече ще бъде голяма работа и може би следващия път, когато анализираме журналите, написани от неговата програма, той няма да бъде запомнен с мръсна дума, а напротив, ще бъде похвален с благодарност.
Извинявам се на колегите от Windows, въпреки че разкритията вероятно ще се отварят под git-bash или mingw.
Всички, успокойте се и тръгвайте.
(Бележка под линия към skiddie: споменатият експлойт не е в статията - помислете и напишете сами)
И така, какво се случва в света на развитиетоотносно сечта:
- форматът на записите в дневника е написан, съжалявам, от булдозера - напълно без никакъв "стандарт", структура или ред
- често самата структура на записа е толкова динамична, че почти изцяло зависи от "входните" данни
- потребителски вход или чужди данни често не са маскирани (бягство), произволно се намесват във формата на случаен принцип, по-лошо - често дори и да има валидирана или маскирана стойност, оригиналът ще бъде записан в дневника по някаква причина (добре, ако поне е отрязан), напълно без да мисли с какво е изпълнен
- във всяко следващо издание на нова версия на софтуера, форматът на почти всеки интересен запис в журнала определено ще бъде променен (или дори напълно пренаписан до неузнаваемост), което носи със себе си много удоволствие да анализирате и анализирате всичко това, отново преминавайки през изходните кодове (ако има такива) и генерирайки куп регистрационни файлове, подиграващи се на програмата по 20 различни начина.
Ето моят малък анализ (eng) за това, с какво трябва да се справите в конкретен случай (използвайки fail2ban като пример) и защо това поне не е добре.
Сега за спецификата: като пример, нека разгледаме следните два реда:
Нека се отклоним накратко и да разгледаме дяволски интересните източници на OpenSSH (модул auth.c), а именно, където са създадени тези редове (да, да, разбрахте правилно - направени са от една функция):
Много по-ясно сега, нали? Е, вече знаете отговора? Все още няма. Хм.
Добре, няма да протягам интригата: това е 4.3.2.1
В първия случай се прави опит за „Инжектиране на потребителско име“ ( authctxt->user ) от хост 4.3.2.1 с потребителско име „test from 1.2.3.4 port 46589 ssh2“ . Във втория случай хост 4.3.2.1 се опитва да изпълни „Инжектиране в информация“ (authctxt->info ) със стойност, равна на "ruser от 1.2.3.4 порт 46589 ssh2" .
Не е ли интуитивен формат за запис?
Уликата в този конкретен случай е наличието на двоеточие, което се произвежда от authctxt->info != NULL ? ":" : "",
Наистина не разбирам какво е мислил разработчикът(ите) на този шедьовър.
Ние не търсим лесни начини (всъщност не ни беше оставен избор), така че просто, като пример за сложност, нека се опитаме да съставим регулярен израз, който съответства на този запис. Ще използвам синтаксиса на регулярния израз за python (като езика, на който е създаден fail2ban).
Първо, "статичните" и строго типизираният компонент:
Това е всичко, сега "динамика":
- потребителско име (засега, за простота, нека приемем, че има честен потребител, т.е. без интервали) - \S*
- незадължителна информация в края на записа от метода за удостоверяване и др. (засега, за простота, ще приемем всичко подред до края) - (. . *)? $
Тези. получаваме следния израз, закрепвайки от двете страни ( ^. $ ) за надеждност:
Тестване с два примера, показващи, че най-простият случай работи:
Сега нека се опитаме да усложним условията (потребителското име съдържа интервали), като използваме не-алчни catch-all, въпреки че не ги харесвам, но помним - не ни беше оставен много избор. Тези. използвай .*? вместо \S+ в потребителското име.
Защо това не е добре - добре, например, след като котвата отдясно е практически отворена, защото .*$ е еквивалентно на израз, отворен отдясно без котва. Ще мълчим за скоростта и натоварването на процесора на дълги линии. Но засега нека продължим така (поне двоеточието е задължително в този случай):
Върши работа! Е, сега пробваме най-добрите примери с инжекции:
Това, което виждаме, също работи катоправилно (и двата пъти имаме правилната стойност на 'host': '4.3.2.1'). Но... Винаги има едно „но“, нали?
И двата примера са най-простите, дори без да вземем предвид нежеланото използване на catch-all, ако измислим по-сложно инжектиране, тогава нашият израз ще се „счупи“ или, много по-лошо, ще върне неправилни данни (което теоретично е уязвимост, тъй като можем или да принудим fail2ban да блокира „чужд“ хост, или да повтаряме паролите за неопределено време, защото сме „невидими“).
Тук няма да включвам космат мелница и веднага ще дам "правилния" (не, по-скоро по-подходящ) израз. И аз не го харесвам много (по много причини), но каквото е, такова е.
По-долу ще обясня малко какво прави. Но защо е така и какви инжекции (тест-случаи) обхваща, засега ще замълча ...
Нека да е като домашна работа или ако щете скрипт-децата да не се изкушават, макар че от друга страна и те трябва да научат нещо.
И така - това е сложен (подчинен) израз с условни "скокове", които в python изглеждат като
Накратко защо е трудно (подчинено):
изразът анализира напълно дясно закотвен, ако имаме най-простото потребителско име, точно едно " от " (или без " от " преди ":" и/или без " от " след ":"); докато условната котва вдясно играе важна роля, защото трябва да провери всичко това напълно
или нямаме ":" (обикновено завършва с ssh2), в който случай се предпочита хостът след последния "от"
Да, изразът „(?:(?! от ).)*“ е „условен“ catch-all, който ще улови всичко, ако (докато) срещне „от“ .
Всъщност има трупи, много по-сложниот горния пример, до напълно аструктурни, които не се разбират от регулярния израз по принцип (или поради сложността им, защото триетажните условни скокове там изобщо ще ви избият главата от думата). Понякога е възможно да се съберат данни от трейлър от няколко записа (ако имат общ идентификатор).
Невронните мрежи също, за съжаление, изобщо не са панацея, защото. като правило те трябва първо да бъдат захранени с необходимата информация, където в процеса на обучение те в идеалния случай не трябва да събират никакви "боклуци".
За съжаление, такива трупи се срещат по-често, отколкото бихме искали, и често има много, много други въпроси за "производителите" на трупи. На тази основа често възникват спорове (например с вашия покорен слуга с uv. Prof. yarikoptic) - как (колко строго) е по-добре да се проектира редовен сезон:
използвайте кратък израз (не котви вдясно), който обхваща известната стабилна информация до първия динамичен компонент, който не носи никакъв полезен товар, което теоретично има известен риск, ако разработчикът промени или пренапише регистрирането утре (и се появи подобен запис в журнала, но не е желаният клас). По някаква причина това ще ви позволи успешно да анализирате регистрационни файлове, ако този споменат динамичен компонент се промени почти напълно (и помним, че не е важно).
Вместо заключение, малко повече, според мен, трябва да направите регистриране (каквото и да е, независимо дали е API или най-сложните сървъри):
желателно е да имате (и да започнете) с уникален идентификатор на запис (напр.някакъв префикс като "Опит за удостоверяване: " пасва тук)
статични, постоянно валидни, строго типизирани данни винаги се записват в началото на записа (в нашия пример HOST, метод за идентификация, присъствие на потребител или "невалиден потребител")
динамичният или силно променлив компонент на записа се поставя съответно в края (например потребителското име и/или authctxt->info, изпратени от клиента)
ако е възможно, е желателно да се записват нетипизирани данни, вече маскирани (и съкратени), т.е. избягване на поне нов ред ( "\n" -> "\\n") като разделител на запис и някои специални знаци, използвани като разделители на блокове в структурата на формата на записа (напр. запетая и двоеточие)
структурата на регистрационния запис трябва да бъде добре обмислена още преди първото му появяване в версии (важно за следващата точка)
Е, за този конкретен запис би изглеждало нещо подобно (всичко "строго въведено" до началото; потребителско име и друга динамична информация до края и, например, кавички; добре, ние маскираме (кавички, интервали) например url_encode от по-горе):
Всъщност можете да измислите още много такива точки, но ако поне спазвате тези правила или част от тях, светът на много хора (и не само хора) отново ще блести с нови цветове.
И огромно благодаря от вашите благодарни потребители, колеги, които разбират вашите дневници и особено от някои неща (натоварени с изкуствен интелект, всякакви невронни мрежи и други ботове) ще излеят лъчи на благодарност върху вашата карма.
И тук можете да получите субсидия за тестYandex.Cloud период. Необходимо е само да въведете "Habr" в полето "секретна парола".