вторник, 30 августа 2011 г.

Вновь о трассировке

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

Для этого потребуется процессор с поддержкой технологии VT-X и немного шаманства. Шаманство будет заключаться в запуске windows в качестве гостевой системы. Далее с введенным Trap Flag запускается гостевая система, и при выполнении каждой инструкции будет выполнен VM Exit в хостовую систему.

Из гостевой системы детект хоста невозможен по определению, поэтому единственный способ который можно использовать для детекта такого вида трассировки - это тайминговые атаки(через RDTSC). Однако мало того, что RDTSC является одной из VM-Exit команд и для гостевой можно подсунуть все что угодно, так еще можно сэмулировать (через CPUID) факт того, что код выполняется на более медленном процессоре чем он есть на самом деле. Все это сведет тайминговые атаки ( которые сами по себе являются ненадежными ) на нет.

суббота, 6 августа 2011 г.

Препятствуем статическому анализу

Делать это можно по разному, можно через полиморфизм/метаморфизм/обфускацию, а можно изменить подход - убрать сам объект скрытия. Ведь нет кода - нечего и обнаруживать. Что же тогда процессор будет выполнять, если кода нет? Ну, на самом деле он есть, просто в другом модуле. Речь пойдет о Return Oriented Programming (rop).

Rонцепция rop проста - в уже загруженных модулях ищутся так называемые rop-gadgets: последовательности инструкций, заканчивающиеся инструкцией ret. Затем на эти готовые инструкции передается управление. Таким образом, весь исполняемый код программы как бы собирается из кусочков чужих модулей, что позволяет не иметь своего кода, вместо него будет лишь череда аргументов/адресов возврата.

В качестве примера напишем простейшее приложение на masm выводящее "Hello" через MessageBox:

.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

.code

start:
    jmp @F
      szTitle    db "Hello",0
    @@:
   
    push MB_OK
    push offset szTitle
    push offset szTitle
    push 0
    call MessageBox

    push 0   
    call ExitProcess

end start

В IDA будет виден простой и понятный код, теперь перепишем его используя rop технику, избавившись от констант, статической линковки user32 и адресов функций ( для простоты вместо адресов ф-ций будет хардкод ).

Rop-gadgets будем искать в ntdll.dll, так как ее грузит ядро во все процессы, то есть она есть везде.

User32.dll для нашей программы придется грузить динамически, так что ищем соответствующий гаджет в ntdll.dll.
Чтобы не искать вручную, напишем простенький скрипт:

import idaapi
import idautils
import idc

def GetNodeDisasmView( nodeStart, nodeEnd ):
    result = ""
    instructions = Heads( nodeStart, nodeEnd )

    for address in instructions:
        result += hex(address) + "    " + GetDisasm( address ) + "\n"
       
    return result

def SaveTwoNodes( fileHandle, prevNodeStartEA, prevNodeEndEA, idaNodeStartEA, idaNodeEndEA ):
    result = GetNodeDisasmView( prevNodeStartEA, prevNodeEndEA )
    fileHandle.write( result )
    result = GetNodeDisasmView( idaNodeStartEA, idaNodeEndEA )
    result = result + "\n"
    fileHandle.write( result )

def SaveOneNode( fileHandle, idaNodeStartEA, idaNodeEndEA ):
    result = GetNodeDisasmView( idaNodeStartEA, idaNodeEndEA )
    result = result + "\n"
    fileHandle.write( result )

#      
# entry point
#

print "Script Start..."

ea = ScreenEA()
fileHandle = file("C:\\retNodes.txt", "w");

for functionAddress in Functions( SegStart(ea), SegEnd(ea) ):
    rawNodes = idaapi.FlowChart( idaapi.get_func( functionAddress ), None, idaapi.FC_PREDS )

    for idaNode in rawNodes:
        if idaapi.is_ret_block( idaNode.type ):
            if idaNode.id > 0:
                prevNode = rawNodes[ idaNode.id - 1 ]
                SaveTwoNodes( fileHandle, prevNode.startEA, prevNode.endEA, idaNode.startEA, idaNode.endEA )
            else:
                SaveOneNode( fileHandle, idaNode.startEA, idaNode.endEA )

fileHandle.close()

print "Script End..."

Далее в текстовом файле среди rop-gadgets ищем подходящий, и находим:

0x7c927e1c    mov     edi, edi
0x7c927e1e    push    ebp
0x7c927e1f    mov     ebp, esp
0x7c927e21    push    [ebp+arg_C]
0x7c927e24    push    [ebp+arg_8]
0x7c927e27    push    [ebp+arg_4]
0x7c927e2a    call    [ebp+arg_0]
0x7c927e2d    pop     ebp
0x7c927e2e    retn    10h

Вызывается ф-ция с тремя аргументами, адрес ф-ции также передается через стек.
HMODULE WINAPI LoadLibrary( __in LPCTSTR lpFileName ); - один параметр, не подходит
Зато вполне подходит LoadLibraryEx:
HMODULE WINAPI LoadLibraryEx( __in LPCTSTR lpFileName, __reserved HANDLE hFile, __in DWORD dwFlags );
Собственно одного гаджета вполне хватит для нашей программы, дальнейшая работа будет заключаться только в подготовке 
параметров для последующих ф-ций. Конечный результат:
.386
.model flat, stdcall
option casemap :none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.code

start:
    ; rop gadget:

    ; 0x7c927e1c    mov     edi, edi
    ; 0x7c927e1e    push    ebp
    ; 0x7c927e1f    mov     ebp, esp
    ; 0x7c927e21    push    [ebp+arg_C]
    ; 0x7c927e24    push    [ebp+arg_8]
    ; 0x7c927e27    push    [ebp+arg_4]
    ; 0x7c927e2a    call    [ebp+arg_0]
    ; 0x7c927e2d    pop     ebp
    ; 0x7c927e2e    retn    10h

    push 00006C6Ch ; "ll"
    push 642E3233h ; "32.d"
    push 72657375h ; "user"
    push 0000006Fh ; "o"
    push 6C6C6548h ; "Hell" (yeah! =])

    push 0         ; uExitCode for ExitProcess
    push 0deadc0deh; 
 
    push 0         ; 
    push 0012FFB0h ; ptr to "Hello"
    push 0012FFB0h ; ptr to "Hello"
    push 0         ; 
    push 7C81CB12h ; return address ( to ExitProcess ) 
  
    push 0                               ; arg_C
    push 0                               ; arg_8
    push 0012FFB8h ; ptr to "user32.dll" ; arg_4
    push 7C801D53h ; LoadLibraryExA      ; arg_0
    push 7E3A07EAh ; return address ( to MessageBoxA )

    push 7c927e1ch ; goto rop-gadget 0x7c927e1c
    ret
end start 
Теперь статическим анализом ничего понять из данного кода будет невозможно, вернее, именно для этого простейшего 
примера легко можно загрузить ntdll.dll, и найти соответствия адресов и инструкций. Однако в более сложных случаях 
код может собираться из многих модулей, модули изза ASLR/конфликтов адресов могут грузиться по разным адресам, и в 
таком случае поможет только динамический анализ( простейший трейсер ).

воскресенье, 31 июля 2011 г.

Driver verifier problem

Тестер проверял драйвер моего tdi фильтра под верифаером, со всеми включенными опциями, поймался bsod:

DRIVER_VERIFIER_IOMANAGER_VIOLATION (c9)
The IO manager has caught a misbehaving driver.
Arguments:
Arg1: 00000208, This IRP is about to run out of stack locations. Someone may have forwarded this
    IRP from another stack.
Arg2: ee8e99ff, The address in the driver's code where the error was detected.
Arg3: 86b5ef68, IRP address.
Arg4: 00000000

...

STACK_TEXT: 
f7675fc8 8065fe0b 0000004c 000000c9 f7675fe8 nt!KeBugCheckEx+0x1b
f7676150 80660571 f767642b 8068f090 00040000 nt!ViBugcheckHalt+0xc3
f76763f4 80660657 80693630 00000208 f7676420 nt!VfBugcheckThrowException+0xa1
f76764e4 806639ac 00000208 00000009 ee8e99ff nt!VfBugcheckThrowIoException+0xb5
f7676510 80663dce 86b5efd8 877faf00 00000002 nt!IovpExamineIrpStackForwarding+0x1dc
f7676584 80656116 001b4950 862c30e8 862df168 nt!IovpCallDriver1+0x1de
f76765ac ee8e9b20 f76765e4 ee8e99ff 861edf18 nt!IovCallDriver+0x8e

После анализа оказалось, что верифаер изменяет число stack locations, так что абсолютно корректный код:

static NTSTATUS ForwardIrp( __in PDEVICE_OBJECT deviceObject, __in PIRP irp )
{
    RT_ASSERT( irp );
    RT_ASSERT( deviceObject );   

    IoSkipCurrentIrpStackLocation( irp );

    return IoCallDriver( deviceObject, irp );   
}

Будет работать без верифаера, и будет падать при включенном верифаере.
Проблема и решение также описана тут: http://www.osronline.com/showthread.cfm?link=139736

Мораль - проверяющие тулзы это конечно хорошо, но иногда и они ошибаются.

суббота, 30 июля 2011 г.

Windows Logical Prefetcher

Logical Prefetcher появился в Windows XP, он представляет собой часть ядра и нужен для уменьшения числа head seek-ов жесткого диска, что соответственно увеличивает скорость запуска приложений или системы.

При старте процесса он отслеживает первые 10 секунд его работы(таймаут задается через реестр), затем запоминает имена файлов, смещения, а также метаданные фс к которым был произведен доступ, после чего данная информация записывается в файл в директории %SystemRoot%\Prefetch.

При повторном старте приложения, Logical Prefetcher проверяет, есть ли префетч файл в соответствующей директории, и если есть - парсит его и подгрузит все метаданные для каждой директории указанной в префетч файле.Затем идет отображение каждого файла, перечисленного в префетч файле, загрузка его и установка тех смещений, которые были запомнены ранее( и сохранены в префетч файле ).

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

Prefetcher может ускорять не только запуск приложений, но и уменьшать boot-time системы.

Настройки префетчера храняться в реестре:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters

Для его включения нужно установить EnablePrefetcher = 3 (Disabled = 0, Application = 1, BootUp = 2, Application AND BootUp = 3), хотя по умолчанию он и так включен.

Там же прописан таймаут работы префетчера при первом запуске - AppLaunchTimerPeriod.

Пример префетч файла: C:\WINDOWS\Prefetch\VMMAP.EXE-3B5AFAED.pf (вывод утилитой от Руссиновича Strings):

SCCA
VMMAP.EXE
C-p
Xa%
0pL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\NTDLL.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\KERNEL32.DLL
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\UNICODE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\LOCALE.NLS
\DEVICE\HARDDISKVOLUME1\WINDOWS\SYSTEM32\SORTTBLS.NLS
\DEVICE\HARDDISKVOLUME2\INSTALLEDTOOLS\VMMAP\VMMAP.EXE
...
Структура .pf файла проста - заголовок + смещения + юникодные имена файлов.
Также для префетч файла есть несколько требований - это наличие сигнатуры в заголовке(SCCA), сам файл должен быть меньше 16мб, смещения не должны вылезать за пределы файла ( это проверяется ф-цией PfWithinBounds ).

Что касается имени префетч файла, например VMMAP.EXE-3B5AFAED.pf, то 3B5AFAED это хеш от пути, хеш подсчитывается следующей ф-цией:

#define RNDM_CONSTANT    314159269
#define RNDM_PRIME        1000000007

ULONG CcPfHashValue( PVOID Key, ULONG Len )
/*
Routine Description:
    Generic hash routine.

Arguments:
    Key - Pointer to data to calculate a hash value for.
    Len - Number of bytes pointed to by key.

Return Value:
    Hash value.
*/
{
    char *cp = Key;
    ULONG i, ConvKey=0;

    for ( i = 0; i < Len; i++ )
    {
        ConvKey = 37 * ConvKey + (unsigned int)*cp;
        cp++;
    }

    return ( abs( RNDM_CONSTANT * ConvKey ) % RNDM_PRIME );
}

Однако кроме обычных приложений есть еще и приложения которые имеют командную строку, для таких файлов хеш считается по другому, а сами файлы перечислены в реестре:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters\HostingAppList: DLLHOST.EXE,MMC.EXE,RUNDLL32.EXE

Ф-ция ответственная за проверку принадлежит или нет процесс данному списку называется CcPfIsHostingApplication.

Что касается реализации, то префетчер запускается при запуске первичного потока, цепочка вызовов выглядит так:

NtCreateThread => PspCreateThread => KeInitThread( ..., PspUserThreadStartup, ...):

VOID PspUserThreadStartup( IN PKSTART_ROUTINE StartRoutine, IN PVOID StartContext )
{
...
    if (CCPF_IS_PREFETCHER_ENABLED())
    {
        //
        // If this is the first thread we are starting up in this process, prefetch the pages likely to be used when initializing the application into the system cache.
        //

        if ((Process->Flags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
        {
            OldFlags = PS_TEST_SET_BITS(&Process->Flags, PS_PROCESS_FLAGS_LAUNCH_PREFETCHED);

            if ((OldFlags & PS_PROCESS_FLAGS_LAUNCH_PREFETCHED) == 0)
            {
                if (Process->SectionObject)
                {
                    //
                    // Notify cache manager of this application launch.
                    //

                    CcPfBeginAppLaunch( Process, Process->SectionObject );
                }
            }
        }
    }

    //
    // Queue the initial APC to the thread
    //

    KeRaiseIrql (APC_LEVEL, &OldIrql);

    KiInitializeUserApc( PspGetBaseExceptionFrame (Thread),
                         PspGetBaseTrapFrame (Thread),
                         PspSystemDll.LoaderInitRoutine,
                         NULL,
                         PspSystemDll.DllBase,
                         NULL);

    KeLowerIrql (PASSIVE_LEVEL);
...
}

Сам префетчинг происходит через перечисление содержимого директории ZwQueryDirectoryFile с классом FileNamesInformation, и загрузкой секций(CcPfPrefetchSections) через IoCreateFile + ZwCreateSection и метаданных (CcPfPrefetchMetadata), но тут уже идет обращение к ФС через ZwFsControlFile(..., FSCTL_FILE_PREFETCH,...).

Работа префетчера по сбору данных идет асинхронно через рабочие потоки, совершенно прозрачно для процесса.

Остался один неосвещенный вопрос - как именно префетчер узнает к какому файлу(секции) было обращение при первом запуске приложения?
Ответ - через отслеживание ошибок страниц.

Цепочка ф-ций, при страничном фолте:

_KiTrap0E => MmAccessFault => MiDispatchFault => MiCompleteProtoPteFault => CcPfLogPageFault.

VOID CcPfLogPageFault(IN PFILE_OBJECT FileObject, IN ULONGLONG FileOffset, IN ULONG Flags )
{
...
    SECTION_OBJECT_POINTERS *sectionObjectPointer = FileObject->SectionObjectPointer;
...
}

С каждым процессом связана трасса - структура данных, позволяющая вести связи между собираемыми данными. Полученный PSECTION_OBJECT_POINTERS сохраняется в этой структуре, также извлекаются и сохраняются DataSectionObject и ImageSectionObject.

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

На этом кратенький, и не претендующий на истину в последней инстанции, обзор префетчера закончен.

вторник, 28 июня 2011 г.

Как поймать то, чего нет?

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

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

У меня был на руках только путь к перемещенному файлу. Поэтому пришлось придумывать нечто новое. И это новое было придумано.

Решение основывалось на системном механизме, называемом кеш менеджер. Информации по нему достаточно, поэтому описывать целиком не вижу смысла. Основная идея - кеширование файловых потоков, файловые потоки это не только данные файла, но и метаданные ФС, такие как Extended Attributes/Alternate Data Streams (а вот Reparse Point врятли кешируются).

Для моего случая интересен факт того, что каждый раз при 1й записи ( то есть при создании исполняемого файла ), файловая система вызывала
один из 4х интерфейсов кеш менеджера, чтобы закешировать файловые потоки ( предварительно создав и инициализировав CacheMap и прочие внутренние структуры связанные с файлом ). Узнать закеширован ли тот или иной файл можно, посмотрев соот-ю структуру FILE_OBJECT, а точнее поле PrivateCacheMap, если оно не нулевое - файл закеширован.

У кешменеджера есть рабочие потоки выполняющие операции write-behind/read-ahead. Write-behind скидывает содержимое кеша на диск с интервалом 1-3 секунды, если это не сделано явно. То есть можно записать в файл, а данные все равно какоето время будут в кеше, т.е. в виртуальной памяти, а не на диске. Таким образом накапливая данные в кеше, и сбрасывая их спустя время одномоментно, уменьшается количество дисковых операций ввода-вывода(если быть более точным, то уменьшает число позиционирования головок жесткого диска). Собственно это и есть главная ф-ция кешменеджера. Read-ahead в свою очередь выполняет упреждающее чтение файлов, когда инициирована операция чтения.

Также, с кешем связан еще один важный механизм, называемый fast I/O.
Везде, где возможно, чтение и запись в закешированные файлы разруливается через высокоскоростной механизм fast I/O.
Fast I/O означает чтение и запись кешированных файлов без работы по генерации IRP.
Это дает существенное быстродействие в некоторых случаях(пример - memory-mapped files).

Именно fast i/o запросы к ФС при создании процесса для получения метаданных(уже не помню, но скорее всего там идет получение EA) позволили ловить запуск несуществующих процессов.

Конкретно для моей задачи нужно было ловить FastIoQueryOpen в минифильтре.

Эквивалент операции FastIoQueryOpen для минифильтра это IRP_MJ_NETWORK_QUERY_OPEN.
Поэтому регистрируем эту операцию, устанавливаем Pre и Post колбеки.

В Pre колбеке просто возвращаем FLT_PREOP_SUCCESS_WITH_CALLBACK, чтобы вызвался Post колбек, а уже в нем проверяем статус операции
( на момент Post колбека операция завершена ), и если тот равен STATUS_OBJECT_NAME_NOT_FOUND, то просто сравниваем имя файла для данной операции, со списком сохраненных ранее файлов. Если совпало - это запуск процесса.

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

вторник, 21 июня 2011 г.

hex-rays или собственный декомпилятор?

Наткнулся недавно на ветку http://exelab.ru/f/index.php?action=vthread&forum=3&topic=15481&page=11, в которой веселые ребята собираются реверсить hex-rays (декомпилятор сишного кода ввиде плагина к IDA, если кто не знает).

Собсно маразм данной идеи даже не в том, что нужно реверсить больше мегабайта бинарного кода, а в том, что это вобще предлагается.
Ведь написать аналог hex-rays в сотни раз проще, чем реверсить бинарь, к тому же необязательно делать декомпилятор ввиде плагина и писать на с++, когда есть IDA-python например, питон для подобных целей гораздо предпочтительнее, т.к. придется работать с довольно сложными конструкциями.

Далее я попытался понять, может быть есть какие-то сложности с построением скажем дерева нодов?
Может быть это какаято архи-сложная задача и я чего-то не понимаю?

Открываем свежие версии IDA/IDA-python, смотрим api, видим классы с заманчивыми названиями FlowChart/BasicBlock/GraphViewer.
Таки да, "все уже украдено до нас" (с)
FlowChart возвращает заботливо построенный за нас список BasicBlock's, а BasicBlock содержит помимо node id, start/end адресов для блока, еще и списки из нодов, на которые ссылается текущий и нодов которые ссылаются на текущий.
Таким образом есть вся достаточная инфа для построения бинарного дерева нодов, которое потребуется впоследствии для свертки оного в Сишные конструкции (if/else/switch и т.д.).

Несколько минут шаманства и готов вывод блоков в окно представленное классом GraphViewer:



Таким образом построение нодов если и вызывала какие-то телодвижения на старых версиях IDA/IDA-python, то сейчас делается буквально в несколько строк.

Какие еще проблемы могут возникнуть при написании декомпиля уровня хекс-рейз?

Свертка нодов в if/else? Да ни разу не проблема
Поиск свитчей/циклов/seh'ов? Тысячи раз обсуждалось, куда статей на эту тему, абсолютно ничего сложного
Отслеживание цепочек live-register's? Элементарно делается
Распарсить type library? Тоже ерундовое дело

Даже не знаю что вызовет затруднение, разве что просто сесть и начать?
Имхо, за неделю будет уже что-то подобное хекс-рейзу версии 1.1.
Да, если писать декомпилятор С++ придется изрядно попотеть.
Но не нужно забывать, что hex-rays и сам то С++ непереваривает, это сишный декомпилятор.

четверг, 16 июня 2011 г.

И не баг и не фича

Что же тогда? Архитектурная особенность! Речь пойдет об уязвимостях.

Каждый кто хотя бы как-то относится к computer security знает, что типов уязвимостей довольно много.

Это buffer/heap/integer overflows, format string vulnerabilities, use after free, race conditions и так далее, имя им легион.

Но всех их объединяет одна вещь - так или иначе, они могут быть выявлены автоматически/полуавтоматически, либо на этапе компиляции( компиляторы, статические анализаторы и т.д. ), либо по методу черного ящика ( fuzzing, binary code analysis и т.д. ).

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

Мне вспомнилось всего два примера таких уязвимостей, это:

1) (MS06-001) уязвимость в Windows Metafile files (.wmf) файлах

"Архитектурная особенность" в данной уязвимости позволяет абсолютно документированно содержать код в .wmf файлах и выполнять его, причем сидела эта ф-ция незамеченной со времен Windows 3.0 с 1990го года по 2006й, 16 лет.

2) (MS10-046) уязвимость в .lnk файлах, которую явил миру Stuxnet.

"Архитектурная особенность" в библиотеке shell32.dll приводит к тому, что открываемый ярлык (.lnk файл) приводит к загрузке и выполнению explorer'ом вредоносной .dll(для так называемые dynamic icons) путь к которой содержится в нем же самом.

Эта же особенность, но уже в самом формате .lnk файла позволяет файлу находиться вобще на удаленном компе ( Lnk File Format => File Location Info => Offset to the network volume table => Network share name ) в расшаренных папках.

Такого рода уязвимости будут всегда находиться только человеком, по крайней мере до тех пор, пока не придумают AI :)

p.s. Если вспомните еще какие-либо уязвимости подобного типа - пишите в комментах.