Чем на самом деле занимаются специалисты по анализу данных? Выводы из 35 интервью

Автор материала провел серию бесед с экспертами в области анализа и обработки данных и сделал выводы о перспективах и направлениях развития дата-сайентистов.

Теория и методы обработки данных упростили решение самых разных задач в сфере технологий. Сюда относится оптимизация поисковой выдачи Google, рекомендации в LinkedIn, формирование заголовков материалов на Buzzfeed. Однако работа с данными может ощутимо повлиять и на многие сектора экономики: от розничной торговли, телекоммуникаций, сельского хозяйства до здравоохранения, грузовых перевозок и пенитенциарных систем.
 
И все же термины «наука о данных», «теория и методы анализа данных» и «специалист по анализу данных» (data scientist) остаются понятны не до конца. На практике они употребляются для описания широкого спектра методов работы с информацией.
 
Что на самом деле делают специалисты по data science? Как ведущий подкаста DataFramed я получил замечательную возможность провести интервью более чем с 30 специалистами в области анализа данных из разнообразных отраслей и академических дисциплин. В числе прочего я всякий раз спрашивал, в чем именно состоит их работа.
 
Наука о данных — это действительно обширная область. Мои гости подходили к нашим беседам со всевозможных позиций и точек зрения. Они описывали самую разную деятельность, в том числе масштабные онлайн-фреймворки для разработки продуктов на booking.com и Etsy, используемые Buzzfeed методы решения задачи многорукого бандита в ходе оптимизации заголовков материалов и влияние, которое машинное обучение оказывает на принятие бизнес-решений в Airbnb. Последний пример озвучил Роберт Ченг, специалист по анализу данных Airbnb. Когда он работал в Twitter, компания была сосредоточена на росте. Теперь в Airbnb Ченг разрабатывает массовые модели машинного обучения.
 
Подходы к применению теории анализа и обработки данных могут быть самыми разными, и выбор того или иного решения зависит не только от отрасли, но и от типа бизнеса и его задач.
 
Однако, несмотря на многообразие, во всех интервью четко прослеживается ряд общих тем.
 

Что делают эксперты по data science?

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

Большие шаги делаются и в других отраслях, не связанных с технологиями

На одной из встреч мы с Беном Скраинкой, специалистом по обработке данных в компании Convoy, рассмотрели эффективное использование методов обработки информации для внедрения инноваций в североамериканской отрасли грузовых перевозок. А Сэнди Грифит из Flatiron Health рассказала о важной роли, которую анализ данных играет в изучении рака. Вместе с Дрю Конвейем мы обсудили его компанию Alluvium, которая «использует искусственный интеллект и машинное обучение для выявления полезных закономерностей на основе масштабных потоков данных, генерируемых в ходе эксплуатации промышленных систем». Майк Тамир, нынешний глава отдела автономного вождения в Uber, рассказал про работу в Takt, где Тамир помогал компаниям из Fortune 500 вводить методы обработки и анализа данных. В числе прочего он поделился опытом разработки системы рекомендаций для Starbucks.
 

Анализ данных — это не только перспектива появления автономных автомобилей и искусственного интеллекта

Многие гости моего подкаста скептически отнеслись к фетишизации ИИ общей направленности со стороны популярных СМИ (пример: статья VentureBeat’s «К 2042 году будет создан ИИ-бог, который напишет свою библию. Будете ли вы поклонятся ему?») и к шумихе вокруг машинного и глубинного обучения. Конечно, оба этих направления — это мощные подходы с важными примерами практических приложений. Но к подобному ажиотажу всегда следует относится с долей здорового скептицизма. Почти все мои гости отметили, что реальные исследователи в этих областях зарабатывают себе на жизнь, собирая и фильтруя данные, создавая панели управления и отчетов, занимаясь визуализацией данных и статистическим анализом. Кроме того, им необходимо уметь донести суть полученных результатов до ключевых игроков и убеждать людей, принимающих решения.
 

Набор навыков, востребованных в профессии Data scientist, постоянно меняется и пополняется (и наличие опыта работы с глубинным обучением — не главное требование)

В беседе с Джонатаном Нолисом, одним из передовых специалистов в области анализа данных из Сиэтла, который работает с компаниями из Fortune 500, мы обсудили такой вопрос: «Какой из двух навыков более важен для специалиста, работающего с данными, — способность пользоваться сложными моделями глубинного обучения или умение рисовать хорошие слайды в PowerPoint?». Нолис привел аргументы в пользу второго, считая, что доступное разъяснение результатов анализа остается ключевым элементом работы с информацией.
 
Еще одна популярная тема — вариабельность набора основных навыков. Востребованность некоторых из них может измениться в обозримом будущем. Стремительное развитие коммерческих и открытых инструментов анализа данных привело к тому, что сейчас мы наблюдаем массовый переход к автоматизации многих рутинных задач, таких как очистка данных и их первичная подготовка. До сих пор обычным делом была ситуация, когда 80% ценного времени исследователя уходило на простой поиск, отсеивание и структуризацию данных, а только 20% — на их анализ. Но такое положение вещей едва ли сохранится. Сегодня автоматизация добралась даже до процессов машинного и глубинного обучения. В частности, в отдельном подкасте, который целиком посвящен таким вопросам, об этом рассказывал Рэндал Олсон, ведущий специалист по анализу и обработке данных в Life Epigenetics.
 
По итогам интервью подавляющее большинство моих гостей считают, что умение создавать и пользоваться инфраструктурами глубинного обучения вовсе не является ключевым. Вместо этого они указывают на способность учится на лету и умение грамотно объяснять сложные аналитические выкладки ключевым участникам процесса, далеким от технических вопросов. Поэтому целеустремленным специалистам в области обработки и анализа данных следует уделять чуть больше внимания правильной подаче материала, нежели способам обработки информации. Новые методы приходят и уходят, но критическое мышление и численно измеримые профессиональные навыки будут актуальны всегда.
 

Специализация становится все важнее

Несмотря на отсутствие четкой схемы карьерного роста и недостаточную поддержку начинающих специалистов, мы уже наблюдаем за появлением некоторых направлений специализации. Эмили Робинсон описала разницу между учеными типа А и Б. По ее словам, к типу А относятся аналитики, деятельность которых близка к традиционной статистике, а вот представители типа Б занимаются преимущественно созданием моделей машинного обучения.
 
Джонатан Нолис подразделяет науку о данных на три компонента. Первый компонент — бизнес-аналитика, которая сводится к тому, чтобы «взять имеющиеся у компании данные и предоставить их нужным людям» в форме информационных панелей, отчетов, электронных писем. Второй — теория принятия решений, которая нацелена на то, чтобы «взять данные и помочь компании принять оптимальное решение с их помощью». Третий компонент — машинное обучение, где специалисты стремятся ответить на вопрос «Как мы можем осознанно применить информационно-аналитические модели в условиях реального проекта?» Несмотря на то, что многие передовые специалисты в своей деятельности охватывают все три направления, уже начали формироваться конкретные карьерные траектории, как это происходит в случае с инженерами-специалистами машинного обучения.
 

Вопросы этики и морали — серьезный вызов

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

Как сказал в интервью Омоджу Миллер, главный специалист по машинному обучению GitHub:

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

Актуальная тема — серьезные, вредоносные и аморальные последствия использования науки о данных, как это произошло в случае с рейтингом риска рецидивизма COMPAS, «который использовался для прогнозирования и выявления будущих преступников», и, по данным ProPublica, оказался «настроен предвзято по отношению к темнокожим американцам».
 
Мы постепенно приходим к соглашению по поводу того, что этические стандарты должны зародится внутри самого сообщества профессионалов-аналитиков, а также получить поддержку со стороны законодателей, общественных движений и других заинтересованных лиц. Отчасти особый акцент в этом ставится на интерпретируемость моделей в противовес современным решениям, работающим по принципу черного ящика. То есть необходимо создать модели, которые могут растолковать, почему они сделали тот или иной прогноз. Глубинное обучение отлично справляется со многими задачами, но славится своей необъяснимостью. Преданные своему делу исследователи, разработчики и специалисты по анализу данных добиваются прогресса в этом направлении благодаря таким проектам, как Lime, направленным на объяснение принципов работы моделей машинного обучения.
 
Масштабная революция анализа данных в отраслях человеческой деятельности и обществе только началась. Пока неясно, останется ли профессия специалиста по анализу данных самой привлекательной работой XXI века, станет ли она более узконаправленной или просто превратится в набор навыков, которым должны будут обладать исследователи. Как сказала Хилари Мейсон: «А будет ли наука о данных существовать через 10 лет? Я помню мир, в котором ее не было, и не удивлюсь, если эту профессию ждет та же участь, что постигла профессию веб-мастера».

image


ссылка на оригинал статьи https://habr.com/post/421845/

Google и Mastercard заключили секретную сделку по трекингу покупок в магазинах

Недавно на Хабре публиковалась статья «Добро пожаловать в эпоху нигилизма приватности», где раскрываются многолетние практики сбора конфиденциальных данных о пользователях. Эти методы существуют десятилетиями, но только сейчас проблема начала активно обсуждаться: под прицел критики попали Google и Facebook как крупнейшие игроки. В то же время десятки информационных брокеров готовы продать маркетологам базы с разной информацией о пользователях. Один из таких брокеров — корпорация Mastercard. У неё имеется очень ценная для Google информация о покупках пользователей в розничных магазинах.

Сейчас стало известно, что избранные рекламодатели Google около года имеют доступ к новому инструменту, который позволяет отследить покупки в офлайновых розничных магазинах после показа соответствующей рекламы в интернете. Более 2 миллиардов держателей карт Mastercard ничего не знали о слежке, которая втайне происходит за ними.

Сделка до сих пор держится в секрете, о ней сообщило агентство Bloomberg со ссылкой на несколько источников информации. По их словам, Google платила Mastercard миллионы долларов за получение данных. Кроме того, компании обсуждали схему раздела рекламных доходов от использования этой информации.

Google и Mastercard вели переговоры о сделке в течение примерно четырёх лет, по словам четырёх человек со знанием сделки. Хотя источники анонимные, но информация надёжная: трое из них напрямую работали над этой сделкой и знают все детали. Это сотрудничество дало Google прмой доступ к оценке эффективности рекламы через отслеживание розничных покупок. Доходы Google напрямую зависят от точности таргетирования рекламы, так что благодаря сделке компания получила преимущество перед конкурентами.

Альянс Google и Mastercard — пример сращивания онлайновой рекламной платформы с офлайновым сбором данных. Google и сейчас может соотносить показы рекламы и посещение пользователем конкретных веб-страниц с его координатами в офлайне. Согласно исследованию Google Data Collection, смартфон Android в «спящем» режиме (с активным браузером Chrome в фоне) передаёт информацию о местоположении в Google 340 раз в течение 24-часового периода, то есть производится в среднем 14 передач данных в час. На самом деле, информация о местоположении составляет 35% всех образцов данных, отправленных в Google.

Но сделка Google и Mastercard, которая держалась в секрете, может вызвать более широкие опасения пользователей в отношении конфиденциальности: «Люди не ожидают, что физические покупки в магазине будут связаны с тем, что они покупают в интернете, — сказала в комментарии Bloomberg Кристин Баннан (Christine Bannan), советник информационной группы Electronic Privacy Information Center (EPIC). — Компании берут на себя недостаточно ответственности по информированию пользователей о том, что они делают и какие права у них есть».

Пресс-секретарь Google отказалась комментировать партнёрство с Mastercard, но подтвердила наличие вышеупомянутого рекламного инструмента по отслеживанию офлайновых покупок: «Прежде чем мы запустили бета-версию этого продукта в прошлом году, мы создали новую технологию двойного слепого шифрования, которая не позволяет Google и нашим партнёрам просматривать личную информацию пользователей, — говорится в заявлении компании. — Мы не имеем доступа к какой-либо личной информации с кредитных и дебетовых карт наших партнёров, а также не делимся с ними какой-либо личной информацией». Компания заявила, что каждый пользователь может отказаться от отслеживания рекламы с помощью онлайн-консоли Google «Активность в интернете и приложениях» (Web and App Activity). Однако нужно заметить, что для держателей карт нет простого способа отказаться от отслеживания.

Представитель Mastercard также отказался комментировать сделку с Google. Но он сказал, что Mastercard делится с магазинами и сервис-провайдерами тенденциями транзакций, чтобы помочь им измерить «эффективность своих рекламных кампаний». Информация включает в себя объёмы продаж и средний размер покупки, она предоставляется только с разрешения торговцев: «Никакие индивидуальные транзакции или личные данные не предоставляются, — сказал представитель компании в заявлении. — Мы не предоставляем аналитику, которая отслеживает, показывает или даже измеряет эффективность рекламы для отдельных потребителей».

Store Sales Measurement

Google запустила бета-версию сервиса Store Sales Measurement в прошлом году. Тогда она сообщила, что у неё есть доступ к «примерно 70 процентам» кредитных и дебетовых карт США через партнёров, не назвав их.

Цифра в 70% может означать, что компания имеет дело с другими компаниями кредитных карт, а не только Mastercard. Или она может означать, что 70% владельцев банковских карт входят учётные записи Google, такие как Gmail, когда они нажимают на поисковую рекламу, то есть у Google есть возможность привязать активность по банковским картам к профилям людей.

По словам двух источников, Google обращалась по поводу аналогичного сотрудничества ещё к двум компаниям, но итог переговоров неизвестен. Представители Google подтверждают, что услуга распространяется только на людей, которые вошли в одну из своих учётных записей и не отказались от отслеживания рекламы. Согласно отчету финансовой исследовательской компании Nilson, покупки по картам Mastercard составляют около четверти объёма всех розничных покупок в США в прошлом году.

С помощью системы Store Sales Measurement можно анонимно сопоставлять существующие профили пользователей с покупками в физических магазинах. Это исключительно ценная информация: ведь Google знает, что конкретный человек перед этим нажал на рекламу. Таким образом, теперь можно сказать рекламодателям, что эта деятельность привела к фактическим продажам в магазинах.

Сейчас Google тестирует сервис на «небольшой группе» рекламодателей в США, сказал пресс-секретарь. С помощью Store Sales Measurement маркетологи видят совокупные показатели продаж и оценку, какой объём можно отнести к объявлениям Google, но они не видят личную информацию покупателей, сколько они тратят или что именно покупают. Тест доступен только для розничных торговцев, а не для компаний, которые продают товары в магазинах, сказала пресс-секретарь.

Для Google сделка с Mastercard означает ещё большее увеличение рекламных доходов. Рекламодатели готовы щедро платить, чтобы почерпнуть ценную информацию о связи рекламных объявлений с посещениями веб-сайта или покупками в интернете. А вот добыть информацию о действиях пользователя в офлайне несравнимо труднее. Это особенно обидно для компаний, продающих явно «офлайновые» товары, такие как одежда или товары для дома. Эти товары покупатели часто изучают в интернете, но ходят покупать в реальные офлайновые магазины. Именно этот «информационный разрыв» создаёт спрос на новую услугу: «Google должен связать эти активности по щелчку мыши, — говорит Джозеф Макконеллог (Joseph McConellogue), руководитель отделения онлайновой торговли в рекламном агентстве Reprise Digital. — Большинство рекламодателей стремятся к такой интеграции».

Первая неудачная попытка: Google Wallet

Изначально Google пыталась решить проблему с помощью собственного решения — сервиса мобильных платежей Google Wallet. Часть первоначальной концепции заключалась в том, чтобы связать клики по объявлениям с покупками в физических магазинах. Об истинных целях Google (усиление отслеживания пользователей) сейчас рассказывают те, кто работал над продуктом. Но собственная платёжная система так и не получила достаточной популярности, поэтому Google начала искать партнёров. Сейчас пресс-секретарь Google заявляет, что платежная служба никогда не использовалась для измерения рекламы, но это понятно, потому что проект так и не вышел на промышленный уровень.

Отслеживание перемещений

С 2014 года Google показывает рекламодателям, если нажавший на рекламное объявление пользователь посещает физический магазин рекламодателя. Это делается с помощью функции «История местоположений» в Картах Google. Однако рекламодатель до настоящего времени не знал, совершил ли покупатель покупку. Поэтому Google доработала систему. В 2015 году новый инструмент позволил рекламодателям загружать в систему адреса электронной почты клиентов, сделавших покупки. Так можно оценивать эффективность онлайновой рекламы: Google сверяется с профилями и определяет, сколько человек сделали покупки и ходили ли они прежде по рекламным ссылкам. Кроме того, Google покупает данные у сторонних брокеров, таких как Experian и Acxiom, которые предоставляют демографическую и финансовую информацию для маркетологов.

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

Инструмент Store Sales Measurement появился в 2017 году. Он работает следующим образом: например, девушка ищет «красную помаду» в Google, нажимает на рекламное объявление, просматривает веб-страницы, но ничего не покупает. Позже она заходит в магазин и покупает красную помаду, расплачиваясь картой Mastercard. После этого рекламодатель, который запустил объявление, получает отчёт от Google, где среди прочей информации есть столбец «Офлайн-доход». Информация вносится туда только если пользователь вошёл в аккаунт Google и сделал покупку в течение 30 дней после нажатия на объявление. Рекламодателям показывают процент покупателей, которые нажали или просмотрели объявление, а затем совершили соответствующую покупку.

Это не точный список имён и фамилий, но всё равно самый мощный инструмент для связи онлайн-рекламы с покупками в реальном мире на сегодняшний день.

В течение многих лет Facebook и Google работали над тем, чтобы связать свои базы данных о поведении пользователей с финансовыми данными людей. И вот постепенно такая интеграция начинает происходить.

Автор статьи «Добро пожаловать в эпоху нигилизма приватности» считает, что мы уже проиграли битву за приватность. Мы пытаемся бороться за конфиденциальность личных данных и предпринимаем усилия, чтобы остановить отслеживание, но на самом деле уже поздно. Поток приватных данных идёт из огромного количества источников:

«Очень легко и комфортно обвинять Google в нынешнем положении дел. Мы создаём пугало и боремся со «злодеем», который кажется достойным врагом. Но истинный противник в нарушениях приватности — это не конкретный злодей из комиксов, которого можно загнать в угол, разоблачить и победить. На самом деле истинный враг — это туманная муть, леденящий душу лавкрафтовский шёпот, его невозможно увидеть, не говоря уже о прикосновении, не говоря уже о победе. Даже «облако» — неправильная метафора, потому что откачка газообразного яда только вызывает новый холодный сквозняк из невидимых источников. Если не сайты, то фармацевтика. Если не данные о местоположении, то домашние товары. Если не лайки, то банковские счета и демография районов. Ваши данные везде, и нигде, и невозможно изменить ситуацию и избежать того, что они ещё смогут сделать для вас.

… В конечном счёте это проигранная битва. Вы действительно собираетесь прекратить использовать Google? Или уйти из Facebook? Или прекратить просмотр веб-страниц? Или отказаться от смартфона? Или отключить службы определения местоположения в настройках? Может быть, некоторые люди сейчас на некоторое время способны на это, но реальность современной жизни загонит их обратно в эти службы. В конце концов такое станет невозможным… Невозможно отказаться от благ цивилизации в реальной жизни, независимо от того, сколько бровей нахмурилось и сколько твитов опубликовано об этом».


ссылка на оригинал статьи https://habr.com/post/421945/

Внедрение зависимостей в консольном приложении .Net Core

Здравствуйте, коллеги.

Следует признать, что с появлением платформы .NET Core и обновлением ASP.NET Core практика внедрения зависимостей в .NET ничуть не утратила актуальности. Лаконичный кейс об использовании встроенных контейнеров на платформе .NET разобран в статье Эндрю Лока, перевод которой мы сегодня предлагаем вашему вниманию

Максимум потенциала ASP.NET Core заключен во внедрении зависимостей (DI). Возможны различные разногласия о способах реализации DI, но, в целом, эта практика рекомендуется к использованию и мне кажется однозначно выигрышной.

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

Зачем использовать встроенный контейнер?

Один из неоднократно встречавшихся мне вопросов – можно ли использовать встроенный провайдер в консольном приложении .NET Core? Коротко – нет, по крайней мере, не «из коробки», однако, добавить такой провайдер совершенно легко. Однако, уже другой вопрос – а стоит ли его использовать.

Одно из достоинств встроенного контейнера в ASP.NET Core заключается в том, что библиотеки фреймворка сами регистрируют с ним свои зависимости. При вызове расширяющего метода AddMvc() в методе Startup.ConfigureServices фреймворк зарегистрирует в контейнере целую кучу сервисов. Если позже добавить сторонний контейнер, то к нему перейдут и эти зависимости, и их придется перерегистрировать, чтобы они нормально разрешались через сторонний контейнер.

Если вы пишете консольное приложение, то, скорее всего, вам не понадобится MVC или другие ASP.NET-специфичные сервисы. В таком случае может быть столь же просто прямо с самого начала использовать StructureMap или AutoFac вместо встроенного провайдера, возможности которого ограничены.

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

Добавляем инъекцию зависимостей в консольное приложение

Если вы решили, что встроенный контейнер вам подходит, то добавить его в приложение не составляет труда – для этого используется пакет Microsoft.Extensions.DependencyInjection. Чтобы показать, как это делается, создам простое приложение с двумя сервисами:

public interface IFooService {     void DoThing(int number); }  public interface IBarService {     void DoSomeRealWork(); }

У каждого из этих сервисов будет единственная реализация. BarService зависит от IFooService, а FooService использует ILoggerFactory для логирования некоторой работы:

public class BarService : IBarService {     private readonly IFooService _fooService;     public BarService(IFooService fooService)     {         _fooService = fooService;     }      public void DoSomeRealWork()     {         for (int i = 0; i < 10; i++)         {             _fooService.DoThing(i);         }     } }  public class FooService : IFooService {     private readonly ILogger<FooService> _logger;     public FooService(ILoggerFactory loggerFactory)     {         _logger = loggerFactory.CreateLogger<FooService>();     }      public void DoThing(int number)     {         _logger.LogInformation($"Doing the thing {number}");     } }

Как вы уже видите, я использую в приложении новую инфраструктуру логирования – поэтому добавлю соответствующий пакет в project.json. Также добавлю пакет DependencyInjection и пакет Microsoft.Extensions.Logging.Console, чтобы можно было просматривать результаты логирования:

{   "dependencies": {     "Microsoft.Extensions.Logging": "1.0.0",     "Microsoft.Extensions.Logging.Console": "1.0.0",     "Microsoft.Extensions.DependencyInjection": "1.0.0"   } }

Наконец, чтобы окончательно сложить все вместе, обновлю мой метод static void main. Сейчас мы его подробно разберем.

using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging;  public class Program {     public static void Main(string[] args)     {         // настроим нашу инъекцию зависимостей         var serviceProvider = new ServiceCollection()             .AddLogging()             .AddSingleton<IFooService, FooService>()             .AddSingleton<IBarService, BarService>()             .BuildServiceProvider();          // конфигурируем консольное логирование         serviceProvider             .GetService<ILoggerFactory>()             .AddConsole(LogLevel.Debug);          var logger = serviceProvider.GetService<ILoggerFactory>()             .CreateLogger<Program>();         logger.LogDebug("Starting application");          // здесь выполняется работа          var bar = serviceProvider.GetService<IBarService>();         bar.DoSomeRealWork();          logger.LogDebug("All done!");      } }

Первым делом мы сконфигурируем контейнер инъекции зависимостей, создав ServiceCollection, добавив наши зависимости и, наконец, собрав IServiceProvider. Этот процесс равнозначен методу ConfigureServices в проекте ASP.NET Core, причем, здесь в фоновом режиме происходит практически то же самое. Как видите, мы используем расширяющий метод IServiceCollection, чтобы добавить в наше приложение сервисы логирования, а затем регистрируем наши собственные сервисы. serviceProvider – это наш контейнер, которым мы можем пользоваться для разрешения сервисов в нашем приложении.
На следующем этапе нам потребуется сконфигурировать инфраструктуру логирования с провайдером, так, чтобы результаты логирования куда-то выводились. Сначала выберем экземпляр ILoggerFactory из нашего новоиспеченного serviceProvider и добавим консольный логгер.

В оставшейся части программы мы видим дальнейшее внедрение зависимостей. Сначала выбираем ILogger<T> из контейнера, а затем — экземпляр IBarService. Согласно нашим регистрациям, IBarService – это экземпляр BarService, в который будет внедрен экземпляр FooService.

Далее мы можем запустить наше приложение и убедиться, как красиво разрешаются все наши зависимости!

Добавление StructureMap к консольному приложению

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

Для полноты картины покажу, как легко обновить приложение в расчете на гибридный подход: одновременно использовать встроенный контейнер для добавления любых зависимостей фреймворков, а для нашего собственного кода использовать StructureMap. Более подробно о добавлении StructureMap в приложение ASP.NET Core рассказано здесь.

Сначала нужно добавить StructureMap к зависимостям project.json:

{   "dependencies": {     "StructureMap.Microsoft.DependencyInjection": "1.2.0"   } }

Далее обновить метод static void main, чтобы StructureMap использовался для регистрации наших собственных зависимостей:

public static void Main(string[] args) {     // добавляем сервисы фреймворка     var services = new ServiceCollection()         .AddLogging();      // добавляем StructureMap     var container = new Container();     container.Configure(config =>     {         // Регистрируем информацию в контейнере при помощи нужных API StructureMap…         config.Scan(_ =>                     {                         _.AssemblyContainingType(typeof(Program));                         _.WithDefaultConventions();                     });         // Заполняем контейнер информацией из коллекции сервисов         config.Populate(services);     });      var serviceProvider = container.GetInstance<IServiceProvider>();      // оставшаяся часть метода не изменилась }

На первый взгляд такая версия метода может показаться сложнее предыдущей – на самом деле, так и есть – но, в то же время, этот метод и гораздо мощнее. В примере со StructureMap не придется явно регистрировать наши сервисы IFooService или IBarService – они автоматически регистрировались по соглашению. Когда приложение начинает разрастаться, подобная регистрация на основе соглашений становится чрезвычайно мощным инструментом, особенно в сочетании с отладочными возможностями и вариантами исправления ошибок, которые перед вами открываются.

В этом примере я показал, как использовать StructureMap с адаптером для работы с методами расширения IServiceCollection, однако, мой вариант, безусловно, не является обязательным. Совершенно допустимо применять StructureMap как единственный источник регистрации, просто требуется вручную регистрировать все сервисы, добавляемые в составе методов расширения AddPLUGIN.

Итоги

В этой статье я рассказал, как целесообразно использовать встроенный контейнер для внедрения зависимостей в приложении .NET Core. Я показал, как добавить к проекту коллекцию ServiceCollection, зарегистрировать и сконфигурировать фреймворк логирования, а также извлечь из него сконфигурированные экземпляры сервисов. Наконец, я продемонстрировал, как можно использовать сторонний контейнер в комбинации со встроенным, чтобы усовершенствовать процедуру регистрации – в частности, реализовать регистрацию по соглашениям.
.
Весь проект (а также многие другие проекты, о которых я рассказывал у себя в блоге) вы найдете у меня на Github.


ссылка на оригинал статьи https://habr.com/post/421943/

Используем SQL в Rails

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

Одна из основных концепций ActiveRecord состоит в том, что база данных достаточно утилитарна и может быть изменена. Ну, вы такие сидите, пишете свои модельки с использованием MySQL и вдруг где-то прочитали, что можно вот так вот взять и заменить MySQL на MongoDB. Хорошо, не так радикально, но, скажем, на PostgreSQL у вас могут быть причины заменить MySQL. Или наоборот, ничего не имею против MySQL. Тут ActiveRecord утверждает, что сделать вам это проще простого, мол скоупы, before/after фильтры и ассоциации достаточно абстрактны, чтобы не переживать за формирование запросов к базе данных и заботится о логике приложения. Что вместо WHERE is_archived = TRUE вы с радостью напишете where(is_archived: true) и ActiveRecord сделает все за вас. Все примеры будут преведены для PostgreSQL, а не для MySQL, так что пользователи MySQL вынуждены будут изобретать свой собственный велосипед.

Но как бы не так! На практике оказывается, что этот слой абстракции вся напрочь дырявая, как корыто из сказки о Золотой Рыбке. И что многие базовые возможности использовать нельзя, вроде сравнения дат или работы с массивами. И получаются скоупы с вынужденными where("#{quoted_table_name}.finished_at >= ?", Date.current) или where("#{quoted_table_name}.other_ids <@ ARRAY[?]", ids). На что ActiveRecord дает вполне осознанный и логичный ответ: не используйте это. Вместо массивов используйте habtm-связь, а если надо сравнивать даты, живите с этим. Да, и не дай бог вам пропустить quoted_table_name в таком скоупе — первый же includes или joins расставит все на свои места. Проще везде и всегда писать, чтобы руку не сбивать.

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

И когда окажется, что ваши скоупы в папке models состоят более чем наполовину из вот таких вот использований экстра-возможностей, то станет совершенно очевидно, что ActiveRecord — всего лишь удобная оболочка для интеграции одного куска кода с табличками с другим куском кода. И скоупы, вроде where(is_archived: true).joins(:sprint).merge(Sprint.archived), будут отрабатывать прекрасно и комбинировать их будет не сложнее, чем приготовить яишницу, правда ведь?

Следующей стадией будет денормализация. Нет, денормализация как бы и была всегда и никуда не исчезала, но забота о ней возлагалась на могучие плечи Rails и ActiveRecord, а вы знаете, что эти два парня не отличались расторопностью и аскетичностью в требованиях к ресурсам. Допустим, counter_cache: true — это первый шаг к денормализации, ведь COUNT(*) AS sprints_count вам сделать ActiveRecord так вот просто не позволит (вы же не хотите менять select(), правда ведь?). И еще counter_cache не далек от совершенства и в некоторых случаях может произойти рассинхронизация реального количества от закешированного. Некритично, конечно, но неприятно. И это только первый кандидат для того, чтобы осесть в базе данных и не загружать и без того загруженную голову руби-машины. Всего-то пара триггеров и готово! Во-первых при удалении и добавлении новой записи в табличку А нужно пересчитать количество записей в табличке Б и все, да? Ну и при редактировании, конечно же, если foreign_key поменялся, ведь запрос UPDATE B SET a_id = $1 WHERE id = $2 сломает counter_cache и для старого А и для нового.

  CREATE OR REPLACE FUNCTION update_#{parent_table}_#{child_table}_counter_on_insert() RETURNS TRIGGER AS $$   BEGIN     UPDATE #{parent_table} SET #{counter_column} = COALESCE((SELECT COUNT(id) FROM #{child_table} GROUP BY #{foreign_column} HAVING #{foreign_column} = NEW.#{foreign_column}), 0) WHERE (#{parent_table}.id = NEW.#{foreign_column});     RETURN NULL;   END;   $$ LANGUAGE plpgsql; 

Следующая стезя работ базы данных окажется связанной с датой-временем. И для начала давайте просто поля created_at и updated_at будем обслуживать в базе, благо, это сильно проще. Сначала поставим дефолты:

  change_column_default :table_name, :created_at, -> { 'CURRENT_TIMESTAMP' }   change_column_default :table_name, :updated_at, -> { 'CURRENT_TIMESTAMP' }

А чтобы сразу сделать это везде, можно цикл организовать по всем табличкам, где есть эти поля. Кроме таблиц schema_migrations и ar_internal_metadata, конечно же:

  (tables - %w(schema_migrations ar_internal_metadata)).each { ... }

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

Rails.application.config.active_record.record_timestamps = false

Итак, следующим шагом будет обновление поля updated_at в момент обновления записи. Это просто:

  CREATE OR REPLACE FUNCTION touch_for_#{table_name}_on_update() RETURNS TRIGGER AS $$   BEGIN     SELECT CURRENT_TIMESTAMP INTO NEW.updated_at;     RETURN NEW;   END;   $$ LANGUAGE plpgsql;

Теперь нужно полностью избавится от touch: true в моделях. Эта штука очень сильно похожа на мишень в тире — тоже полностью дырявая. И даже не буду объяснять почему, ведь вы и так все эти случаи знаете. Это не сильно сложнее, всего-лишь нужно обновлять updated_at не только себе:

  CREATE OR REPLACE FUNCTION touch_for_#{table_name}_on_update() RETURNS TRIGGER AS $$   BEGIN     UPDATE foreign_table_name SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.foreign_column_name;     SELECT CURRENT_TIMESTAMP INTO NEW.updated_at;     RETURN NEW;   END;   $$ LANGUAGE plpgsql;

Конечно, цепочка вызовов таких вот триггеров будет делать лишнее действие, но вот в постгресе вменяемого механизма вызвать триггеры не меняя саму запись нет. Можно попробовать сделать SET title = title, но это выходит ничем не лучше, чем SET updated_at = CURRENT_TIMESTAMP.

Точно такой же триггер будет и на вставку, только обновлять updated_at не обязательно:

  CREATE OR REPLACE FUNCTION touch_for_#{table_name}_on_insert() RETURNS TRIGGER AS $$   BEGIN     UPDATE foreign_table_name SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.foreign_column_name;     RETURN NEW;   END;   $$ LANGUAGE plpgsql;

Конечно, можно бы попытаться написать это одной функцией, добавив проверку на текущее событие прям внутрь триггера, на подобии IF TG_OP = 'UPDATE' THEN, но предпочтительнее делать все триггеры как можно проще, чтобы уменьшить вероятность ошибки.

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

  SELECT ccu.table_name AS foreign_table_name, kcu.column_name AS column_name   FROM information_schema.table_constraints AS tc     JOIN information_schema.key_column_usage AS kcu     ON tc.constraint_name = kcu.constraint_name     JOIN information_schema.constraint_column_usage AS ccu     ON ccu.constraint_name = tc.constraint_name   WHERE constraint_type = 'FOREIGN KEY' AND tc.table_name = '#{table_name}'   ORDER BY ccu.table_name; 

Еще один очень полезный совет. Называйте триггеры однообразно, чтобы иметь возможность убедиться в наличии или отсутствии необходимого одним запросом, например этот запрос найдет все touch-insert триггеры:

  SELECT routine_name AS name   FROM information_schema.routines   WHERE     routine_name LIKE 'touch_for_%_on_insert' AND     routine_type ='FUNCTION' AND     specific_schema='public';

И на последок осталось самое страшное. Дело в том, что рельсы не рассчитаны на хоть сколько-нибудь умную базу данных и совершенно плевать хотели на то, что в базе может поменяться хоть что-то, кроме поля id, и то, только при вставке. Поэтому нет вменяемого механизма добавить RETURNING updated_at к update-запросам никакого нет, нужно будет нырнуть во внутренности Rails по самые уши.

Манкипатч получился не такой уж, чтобы сильно аккуратный, но в первую очередь ставилась цель как можно меньше повреждать текущую работу фреймворка.

Приведу его полностью

module ActiveRecord   module Persistence     # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L729-L741     def _create_record(attribute_names = self.attribute_names)       attribute_names &= self.class.column_names       attributes_values = attributes_with_values_for_create(attribute_names)        an_id, *affected_rows = self.class._insert_record(attributes_values).dup       self.id ||= an_id if self.class.primary_key       Hash[ApplicationRecord.custom_returning_columns(self.class.quoted_table_name, :create).take(affected_rows.size).zip(affected_rows)].each do |column_name, value|         public_send("#{column_name}=", self.class.attribute_types[column_name.to_s].deserialize(value)) if value       end        @new_record = false        yield(self) if block_given?        id     end     private :_create_record      # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L710-L725     def _update_record(attribute_names = self.attribute_names)       attribute_names &= self.class.column_names       attribute_names = attributes_for_update(attribute_names)        if attribute_names.empty?         affected_rows = []         @_trigger_update_callback = true       else         affected_rows = _update_row(attribute_names)         @_trigger_update_callback = affected_rows.any?       end        Hash[ApplicationRecord.custom_returning_columns(self.class.quoted_table_name, :update).take(affected_rows.size).zip(affected_rows)].each do |column_name, value|         public_send("#{column_name}=", self.class.attribute_types[column_name.to_s].deserialize(value))       end        yield(self) if block_given?        affected_rows.none? ? 0 : 1     end     private :_update_record   end    module ConnectionAdapters     module PostgreSQL       module DatabaseStatements         # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb#L93-L96         def exec_update(sql, name = nil, binds = [])           execute_and_clear(sql_with_returning(sql), name, binds) { |result| Array.wrap(result.values.first) }         end          # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb#L147-L152         def insert(arel, name = nil, pk = nil, _id_value = nil, sequence_name = nil, binds = [])           sql, binds = to_sql_and_binds(arel, binds)           exec_insert(sql, name, binds, pk, sequence_name).rows.first         end         alias create insert          # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb#L98-L111         def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:           table_ref = extract_table_ref_from_insert_sql(sql)           if pk.nil?             # Extract the table from the insert sql. Yuck.             pk = primary_key(table_ref) if table_ref           end            returning_columns = quote_returning_column_names(table_ref, pk, :create)           if returning_columns.any?             sql = "#{sql} RETURNING #{returning_columns.join(', ')}"           end            super         end          # No source in original repo         def quote_returning_column_names(table_ref, pk, action)           returning_columns = []           returning_columns << pk if suppress_composite_primary_key(pk)           returning_columns += ApplicationRecord.custom_returning_columns(table_ref, action)           returning_columns.map { |column| quote_column_name(column) }         end          # No source in original repo         def sql_with_returning(sql)           table_ref = extract_table_ref_from_update_sql(sql)            returning_columns = quote_returning_column_names(table_ref, nil, :update)            return sql if returning_columns.blank?           "#{sql} RETURNING #{returning_columns.join(', ')}"         end          # No source in original repo         def extract_table_ref_from_update_sql(sql)           sql[/update\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*set/im]           Regexp.last_match(1)&.strip         end       end     end   end end

Самое главное, что тут происходит обращение к ApplicationRecord.custom_returning_columns, чтобы узнать какие же колонки, кроме id нас интерисуют. И выглядит этот метод как-то так:

  class << self     def custom_returning_columns(table_ref, action)       return [] if ['"schema_migrations"', '"ar_internal_metadata"'].include?(table_ref)        res = []       res << :created_at if action == :create       res << :updated_at        res += case table_ref              when '"user_applications"'                [:api_token]              when '"users"'                [:session_salt, :password_changed_at]              # ...              else                []              end        res     end   end


Вместо выводов можно сказать, что, больная голова Rails стала чуть менее больная. Такие рутинные процессы, как counter_cache и touch канут в лету и в следующей статье можно будет думать о чем-то более глобальном, вроде удаления висячих пробелов, валидации данных, каскадном удалении данных или параноидальном удалении. Если эта статья понравилась, конечно же.


ссылка на оригинал статьи https://habr.com/post/421939/

Деньги на ветер: почему ваш антифишинг не детектирует фишинговые сайты и как Data Science заставит его работать?

image

В последнее время фишинг является наиболее простым и популярным среди киберпреступников способом кражи денег или информации. За примерами далеко ходить не нужно. В прошлом году ведущие российские предприятия столкнулись с беспрецедентной по масштабу атакой — злоумышленники массово регистрировали фейковые ресурсы, точные копии сайтов производителей удобрений и нефтехимии, чтобы заключать контракты от их имени. Средний ущерб от такой атаки — от 1,5 млн рублей, не говоря уже про репутационный ущерб, который понесли компании. В этой статье мы поговорим о том, как эффективно детектировать фишинговые сайты с помощью анализа ресурсов (изображений CSS, JS и т.д.), а не HTML, и как специалист по Data Science может решить эти задачи.

Павел Слипенчук, архитектор систем машинного обучения, Group-IB

Эпидемия фишинга

По данным Group-IB, ежедневно жертвами только финансового фишинга в России становятся свыше 900 клиентов различных банков, — этот показатель в 3 раза превышает ежедневное количество жертв от вредоносных программ. Ущерб от одной фишинговой атаки на пользователя варьируется от 2000 до 50 000 рублей. Мошенники не просто копируют сайт компании или банка, их логотипы и фирменные цвета, контент, контактные данные, регистрируют похожее доменное имя, они еще активно рекламируют свои ресурсы в соцсетях и поисковиках. Например, пытаются вывести в топы выдачи ссылки на свои фишинговые сайты по запросу «Перевод денег на карту». Чаще всего поддельные сайты создаются именно для того, чтобы украсть деньги при переводе с карты на карту или при мгновенной оплате услуг операторов сотовой связи.

Фишинг (англ. phishing, от fishing — рыбная ловля, выуживание) — вид интернет-мошенничества, цель которого — обманным путем вынудить жертву предоставить мошеннику нужную ему конфиденциальную информацию. Чаще всего похищают пароли доступа к банковскому счету для дальнейшей кражи денег, аккаунты в социальных сетях (для вымогательства денег или рассылки спама от лица жертвы), подписывают на платные услуги, рассылки или заражают компьютер, делая его звеном в бот-сети.

По способам атак следует различать 2 вида фишинга, ориентированные на пользователей и компании:

  1. Фишинговые сайты, копирующие оригинальный ресурс жертвы (банки, авиакомпании, интернет-магазины, предприятия, госучреждения и т.д.).
  2. Фишинговые рассылки, e-mail, sms, сообщения в соцсетях и т.д.

Атакой на пользователей часто занимаются одиночки, и порог входа в этот сегмент преступного бизнеса такой низкий, что для реализации хватит минимальных «инвестиций» и базовых знаний. Распространению данного типа мошенничества способствуют и phishing kits — программы-конструкторы фишинговых сайтов, которые можно свободно купить в Даркнете на хакерских форумах.

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

В России, по нашим оценкам, действует 15 преступных групп, занимающихся фишингом, направленным на финансовые учреждения. Суммы ущерба всегда небольшие (в десятки раз меньше, чем от банковских троянов), но количество жертв, которые они заманивают на свои сайты, ежедневно исчисляется тысячами. Около 10–15% посетителей финансовых фишинговых сайтов сами вводят свои данные.

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

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

Ловля фишей

Если бренд не следит за своей репутацией, он становится легкой мишенью. Необходимо перехватывать инициативу у преступников сразу после регистрации их фальшивых сайтов. На практике поиск фишинговой страницы делится на 4 этапа:

  1. Формирование множества подозрительных адресов (URL) для проверки фишинга (краулер, honeypots и т.д.).
  2. Формирование множества фишинговых адресов.
  3. Классификация уже задетектированных фишинговых адресов по направлению деятельности и атакуемой технологии, например «ДБО:: Сбербанк Онлайн» или «ДБО:: Альфа-Банк».
  4. Поиск страницы-донора.

Выполнение пунктов 2 и 3 ложится на плечи специалистов по Data Science.

После этого уже можно предпринимать активные действия для блокировки фишинговой страницы. В частности:

  • вносить URL в черные списки продуктов Group-IB и продуктов наших партнеров;
  • автоматически или вручную отправлять письма владельцу доменной зоны с просьбой удалить фишинговый URL;
  • отправлять письма службе безопасности атакуемого бренда;
  • и т.д.

image

Методы, основанные на анализе HTML

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

Также весьма эффективные системы для антифишинга могут разрабатывать исследователи phishing kits. Но и в данном случае нужно исследовать HTML-страницу. Кроме того, эти решения не универсальные — для их разработки необходима база самих «китов». Некоторые phishing kits могут быть неизвестны исследователю. И, разумеется, разбор каждого нового «кита» — это достаточно трудоемкий и дорогой процесс.

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

По данным Group-IB, в настоящий момент таких фишинговых сайтов не более 10% от общего количества, однако пропуск даже одного может дорого обойтись жертве.

Таким образом, фишеру для обхода блокировки достаточно просто менять каркас HTML, реже — обфусцировать HTML-страницу (запутывая разметку и/или подгружая контент через JS).

Постановка задачи. Метод, основанный на ресурсах

Гораздо более эффективны и универсальны для обнаружения фишинговых страниц методы, основанные на анализе используемых ресурсов. Ресурс — это любой файл, подгружаемый при рендеринге web-страницы (все изображения, каскадные таблицы стилей (CSS), JS-файлы, шрифты и т.д.).

В этом случае можно построить двудольный граф, где одни вершины будут адресами, подозрительными на фишинг, а другие — ресурсами, связанными с ними.

image

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

Гипотеза заключается в том, что, основываясь на реальных данных, с достаточно большой долей вероятности можно сказать, что кластер содержит совокупность URL, которые принадлежат одному бренду и генерируются одним phishing kits. Тогда для проверки данной гипотезы каждый такой кластер можно отправить на ручную проверку в CERT (Центр реагирования на инциденты информационной безопасности). Аналитик в свою очередь проставлял бы статус кластеру: +1 («утвержденный») или –1 (отвергнутый»). Всем утвержденным кластерам аналитик также присваивал бы атакуемый бренд. На этом «ручная работа» заканчивается — остальной процесс автоматизирован. В среднем на одну утвержденную группу приходится 152 фишинговых адреса (данные на июнь 2018 г.), а иногда даже попадаются кластеры по 500–1000 адресов! Аналитик тратит около 1 минуты на утверждение или опровержение кластера.

Далее все отвергнутые кластеры удаляют из системы, а все их адреса и ресурсы через некоторое время вновь подают на вход алгоритма кластеризации. В итоге мы получаем новые кластеры. И опять отправляем их на проверку, и т.д.

Таким образом, для каждого вновь поступившего адреса система должна делать следующее:

  1. Извлекать множество всех ресурсов для сайта.
  2. Проверять на принадлежность хотя бы одному ранее утвержденному кластеру.
  3. Если URL принадлежит какому-либо кластеру, автоматически извлечь название бренда и выполнить действие для него (уведомить заказчика, удалить ресурс и т.д.).
  4. Если по ресурсам нельзя проставить ни один кластер, добавить адрес и ресурсы в двудольный граф. В дальнейшем данный URL и ресурсы будут участвовать в образовании новых кластеров.

image

Простой алгоритм кластеризации по ресурсам

Один из важнейших нюансов, который должен учитывать специалист Data Science в ИБ, — тот факт, что его противником является человек. По этой причине условия и данные для анализа очень быстро меняются! Решение, замечательно устраняющее проблему сейчас, через 2–3 месяца может перестать работать в принципе. Поэтому важно создавать либо универсальные (топорные) механизмы, если это возможно, либо максимально гибкие системы, которые можно быстро доработать. Специалист по Data Science в ИБ не сможет решить задачу раз и навсегда.

Стандартные методы кластеризации не работают из-за большого количества признаков. Каждый ресурс можно представить как булевый признак. Однако на практике мы получаем от 5000 адресов сайтов ежедневно, и каждый из них в среднем содержит 17,2 ресурса (данные на июнь 2018 г.). Проклятье размерности не позволяет даже загрузить данные в память, не то что построить какие-то алгоритмы кластеризации.

Другая идея — попытаться разбить на кластеры с помощью различных алгоритмов коллаборативной фильтрации. В данном случае нужно было создать еще один признак — принадлежность к тому или иному бренду. Задача будет сводиться к тому, что система должна спрогнозировать наличие или отсутствие этого признака для остальных URL. Метод дал положительные результаты, однако обладал двумя недостатками:

  1. для каждого бренда нужно было создавать свой признак для коллаборативной фильтрации;
  2. нужна была обучающая выборка.

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

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

  1. Возьмем всевозможные ресурсы (обозначим их как а), для которых не менее N1 адресов, обозначим данное отношение как #(a) ≥ N1.
  2. Построим всевозможные пары ресурсов (a1, a2) и выберем только те, для которых будет не менее N2 адресов, т.е. #(a1, a2) ≥ N2.

Затем мы аналогичным образом рассматриваем пары, состоящие из пар, полученных в предыдущем пункте. В итоге получаем четверки: (a1, a2) + (a3, a4) → (a1, a2, a3, a4). При этом, если хотя бы один элемент присутствует в одной из пар, вместо четверок мы получаем тройки: (a1, a2) + (a2, a3) → (a1, a2, a3). Из полученного множества оставим только те четверки и тройки, которым соответствует не менее N3 адресов. И так далее…

image

Можно получать множества ресурсов произвольной длины. Ограничим количество шагов до U. Тогда N1, N2… NU — это параметры системы.
Величины N1, N2… NU — параметры алгоритма, они задаются вручную. В общем случае мы имеем CL2 различных пар, где L — количество ресурсов, т.е. сложность для построения пар будет  O(L2). Затем из каждой пары создается четверка. И в теории мы, возможно, получим O(L4). Однако на практике таких пар значительно меньше, и при большом количестве адресов эмпирически была получена зависимость O(L2log L). При этом последующие шаги (превращения двоек в четверки, четверок в восьмерки и т.д.) пренебрежимо малы.

Следует заметить, что L — это количество некластеризированных URL. Все URL, которые уже можно отнести к какому-либо ранее утвержденному кластеру, не попадают в выборку для кластеризации.

На выходе можно создать множество кластеров, состоящих из максимально больших по размерам множеств ресурсов. Например, если существует (a1, a2, a3, a4, a5), удовлетворяющая границам Ni, следует убрать из множества кластеров (a1, a2, a3) и (a4, a5).
Затем каждый полученный кластер отправляется на ручную проверку, где аналитик CERT присваивает ему статус: +1 («утвержден») или –1 («отвергнут»), а также указывает, какими являются URL, попадающие в кластер, — фишинговыми или легитимными сайтами.
При добавлении нового ресурса количество URL может уменьшиться, остаться тем же, но никогда не увеличиться. Поэтому для любых ресурсов a1… aN справедливо отношение:

#(a1) ≥ #(a1, a2) ≥ #(a1, a2, a3) ≥ … ≥ #(a1, a2, …, aN).

Следовательно, разумно задавать параметры:

N1 ≥ N2 ≥ N3 ≥ … ≥ NU.

На выходе мы выдаем всевозможные группы для проверки. На рис. 1 в самом начале статьи приведены реальные кластеры, для которых все ресурсы — изображения.

Использование алгоритма на практике

Заметим, что теперь полностью отпадает необходимость исследовать phishing kits! Система автоматически кластеризует и найдет нужную фишинговую страницу.

Ежедневно система получает от 5000 фишинговых страниц и конструирует всего от 3 до 25 новых кластеров в сутки. Для каждого кластера выгружается список ресурсов, создается множество скринов. Данный кластер отправляется аналитику CERT на подтверждение или опровержение.

При запуске точность алгоритма была невысокой — всего 5%. Однако уже через 3 месяца система держала точность от 50 до 85%. В действительности точность не имеет значения! Главное, чтобы аналитики успевали просмотреть кластеры. Поэтому, если система, например, генерирует около 10 000 кластеров в сутки и у вас только один аналитик, придется менять параметры системы. Если не более 200 в сутки, это задача, посильная для одного человека. Как показывает практика, на визуальный анализ в среднем требуется около 1 минуты.

Полнота системы — около 82%. Оставшиеся 18% — это либо уникальные случаи фишинга (поэтому их нельзя сгруппировать), либо фишинг, у которого малое количество ресурсов (не по чему группировать), либо фишинговые страницы, которые вышли за границы параметров N1, N2… NU.

Важный момент: как часто запускать новую кластеризацию на свежих, непроставленных URL? Мы делаем это каждые 15 минут. При этом в зависимости от количества данных само время кластеризации занимает 10–15 минут. Это значит, что после появления фишингового URL есть лаг во времени в 30 минут.

Ниже приведены 2 скриншота из GUI-системы: сигнатуры для обнаружения фишинга соцсети «ВКонтакте» и «Bank Of America».

image

image

Когда алгоритм не работает

Как уже было сказано выше, алгоритм не работает в принципе, если не достигаются границы, заданные параметрами N1, N2, N3… NU, или если количество ресурсов слишком мало, чтобы образовать необходимый кластер.

Фишер может обойти алгоритм, создавая для каждого фишингового сайта уникальные ресурсы. Например, в каждом изображении можно поменять один пиксель, а для подгружаемых JS- и CSS-библиотек использовать обфускацию. В данном случае необходимо разработать алгоритм сравнимого хеша (перцептивного хеша) для каждого типа подгружаемых документов. Однако данные вопросы выходят за рамки этой статьи.

Соединяем все вместе

Соединяем наш модуль с классическими HTML-регулярками, данными, полученными от Threat Intelligence (система киберразведки), и получаем полноту в 99,4%. Разумеется, это полнота на данных, которые уже предварительно классифицированы Threat Intelligence как подозрительные на фишинг.

Какова полнота на всех возможных данных, не знает никто, так как охватить весь Даркнет невозможно в принципе, однако, согласно отчетам Gartner, IDC и Forrester, по своим возможностям Group-IB входит в число ведущих международных поставщиков Threat Intelligence-решений.

А что делать с непроклассифицированными фишинговыми страницами? В день их получается примерно 25–50. Их вполне можно проверить ручным способом. В целом в любой достаточно сложной для Data Sciense задаче в сфере ИБ всегда есть ручной труд, а любые утверждения о 100-процентной автоматизации — это маркетинговый вымысел. Задача специалиста Data Sciense уменьшить ручной труд на 2–3 порядка, сделав работу аналитика максимально эффективной.

Статья опубликована на JETINFO


ссылка на оригинал статьи https://habr.com/post/420269/