[Moscow.pm] Уязвимость при использовании dataTables и DBIx::Class

Ilya Chesnokov chesnokov.ilya на gmail.com
Вс Дек 27 13:35:48 PST 2015


24 декабря 2015 г., 23:47 пользователь Grigory Batalov
<grisxa на gmail.com> написал:
> Всем привет!
>
> Коллеги нашли в моём коде уязвимость, спешу поделиться, как не надо писать :)
> Мне эта тема кажется интересной, может ещё кому-то пригодится.
>
> В одном веб-проекте я использую фреймворк Mojolicious, плагин jQuery
> dataTables.net и ORM DBIx::Class. DataTables (в режиме serverSide: true)
> получает от сервера JSON и заполняет таблицы, которые можно сортировать
> по колонкам или применить фильтр поиска. При сортировке или фильтрации
> на сервер поступает новый запрос и приходит новый JSON-ответ, заполняющий
> таблицу.
>
> Параметры запроса примерно такие:
>     columns[0][data]            "username",
>     columns[0][name]            "",
>     columns[0][orderable]       "true",
>     columns[0][searchable]      "true",
>     columns[0][search][regex]   "false",
>     columns[0][search][value]   "",
>     columns[1][data]            "filename",
>     columns[1][name]            "",
>     columns[1][orderable]       "true",
>     columns[1][searchable]      "true",
>     columns[1][search][regex]   "false",
>     columns[1][search][value]   "",
>     length                      10,
>     order[0][column]            1,
>     order[0][dir]               "asc",
>     search[regex]               "false",
>     search[value]               "",
>     start                       0
>
> Конечно, я не разбираю их вручную, а поручаю это модулю
> JQuery::DataTables::Request. Как видите, в запросе указан номер колонки
> для сортировки (order) и направление.
>
>     my $req =
>       JQuery::DataTables::Request->new(
>         client_params => $self->req->body_params->to_hash );
>
>     my $sort_column    = $req->column( $req->order(0)->{'column'} )->{'data'};
>     my $sort_direction = $req->order(0)->{'dir'};
>
> DBIx::Class же умеет сортировать выборку из таблицы в базе данных
> (resultset) так: search( {...}, { order_by => { -desc => 'filename' } } ).
> Заманчиво указать прямо здесь параметры, полученные от dataTables :)
>
>     my $files = $schema->resultset('File')->search(
>        { purge => 0 },
>        { order_by => { "-$sort_direction" => $sort_column }
>     );

Отсюда ноги и растут, видимо? Не стоит поддаваться искушению
передавать неотфильтрованные данные прямо в запрос? :-)

> DBIx::Class (посредством SQL::Abstract) обычно формирует запрос с подстановками
> типа
>
> SELECT filename FROM files WHERE SIZE > ? LIMIT ? : '1024', '10'
>
> Но не в этом случае! Если вмешаться в запрос и в колонку вписать
>
> columns[1][data] "id, (DELETE FROM files;)"
>
> в SQL-запросе появится непосредственно
>
> ORDER BY id, (DELETE FROM files;) ASC
>
> К счастью, такие наивные попытки уязвить базу данных пресекаются проверкой
> в SQL::Abstract:
>
> SQL::Abstract::puke(): [SQL::Abstract::_assert_pass_injection_guard]
> Fatal: Possible SQL injection attempt 'id, (DELETE FROM files;)'
> If this is indeed a part of the desired SQL use literal SQL ( '...' or [ '...' ] )
> or supply your own {injection_guard} attribute to DBIx::Class::SQLMaker->new()
>
> Тем не менее, есть и более изощрённые методы. Что произойдёт, если вместо
> названия колонки указать
>
> columns[1][data] "id, (select 1 from (SELECT name, CASE name='john' WHEN FALSE THEN pg_sleep(0) ELSE pg_sleep(10) END from users) as t limit 1)"
>
> ? (это PostgreSQL) В хвосте SQL-запроса окажется
>
> ORDER BY me.id, (select 1 from (SELECT name, CASE name='first' WHEN FALSE THEN pg_sleep(0) ELSE pg_sleep(10) END from users) as t limit 1) ASC
>
> Т.е. помимо выполнения обычного запроса (он не испорчен), произойдёт поиск пользователя
> john в таблице users. При неудаче ответ вернётся сразу, а в случае успеха - через 10 сек.
> Таким образом, злоумышленник, генерируя запросы через веб-интерфейс, может перебором
> определить содержимое таблицы! Такой "слепой" метод доступа к базе данных называется
> Blind SQL Injection и широко используется нашими нечистоплотными коллегами. В сети
> есть автоматические средства для перебора.
>
> Подытоживая эту историю, посыпаю голову пеплом и напоминаю, что никаким входным данным
> нельзя доверять! :) Любая мелочь может привести если не к порче информации, то к утечке.
>
> Я же вынес сортировку в отдельный файл ResultSet/File.pm (там ещё кое-что есть),
> добавив проверку:
>
> # apply sorting order
> sub order_by {
>     my $self = shift;
>     my ( $column, $direction ) = @_;
>
>     # an unknown column name is given
>     return $self if none { $_ eq $column } $self->result_source->columns;
>
>     # sanitize order direction
>     $direction = 'ASC' if uc $direction ne 'DESC';
>
>     return $self->search( undef, { order_by => { "-$direction" => "me.$column" } } );
> }
>
> Используется так:
>
>     my $files = $schema->resultset('File')->search( { purge => 0 } )
>       ->another_filter
>       ->order_by( $sort_column, $sort_direction );
> --
> Moscow.pm mailing list
> moscow-pm на pm.org | http://moscow.pm.org



-- 
Best regards,
Ilya Chesnokov


Подробная информация о списке рассылки Moscow-pm