erlang-guidelines/XXE-prevention-guideline.md
2019-06-17 18:15:32 +03:00

6.5 KiB
Raw Blame History

Введение

XML eXternal Entity injection (XXE) - атака на приложение, которое анализирует ввод XML. XXE занимает категорию А4 в списке уязвимостей OWASP Top 10. Эта атака возможна из-за обработки синтаксическим анализатором XML непроверенных данных, содержащих ссылку на внешнюю сущность.

Возможные последствия:

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

Данное руководство содержит краткую информацию для предотвращения этой уязвимости. Подробнее можно ознакомиться здесь: XML External Entity (XXE).

Демонстрация атаки

В стандартном XML-парсере xmerl по умолчанию разрешены внешние сущности, что позволяет читать локальные файлы или вызвать отказ в обслуживании сервиса.

Пример уязвимого кода:

#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname factorial -mnesia debug verbose
main([Path]) ->
    try
        io:format("Xml file path[~p]~n", [Path]),
        Xml = xmerl_scan:file(Path),
        {ok, Dir} = file:get_cwd(),
        Format = io_lib:format("~p", [Xml]),
        ok = file:write_file(Dir ++ "/xml.out", Format, [append])
    catch
        Excep:Error:St ->
            io:format("~p: ~n ~p~n~p", [Excep, Error, St]),
            halt(1)
    end;
main(_) ->
    halt(1).

Пример вредоносной нагрузки на чтение локальных файлов:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<response>
	<result>&xxe;</result>
</response>

Пример вредоносной нагрузки на отказ в обслуживании сервиса:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///dev/zero" >]>
<response>
	<result>&xxe;</result>
</response>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ELEMENT foo ANY >
<!ENTITY xxe "XXE" >
<!ENTITY xxe2 "&xxe;&xxe;&xxe;&xxe;&xxe;&xxe;&xxe;&xxe;&xxe;&xxe;" >
<!ENTITY xxe3 "&xxe2;&xxe2;&xxe2;&xxe2;&xxe2;&xxe2;&xxe2;&xxe2;&xxe2;&xxe2;" >
<!ENTITY xxe4 "&xxe3;&xxe3;&xxe3;&xxe3;&xxe3;&xxe3;&xxe3;&xxe3;&xxe3;&xxe3;" >
<!ENTITY xxe5 "&xxe4;&xxe4;&xxe4;&xxe4;&xxe4;&xxe4;&xxe4;&xxe4;&xxe4;&xxe4;" >
<!ENTITY xxe6 "&xxe5;&xxe5;&xxe5;&xxe5;&xxe5;&xxe5;&xxe5;&xxe5;&xxe5;&xxe5;" >
<!ENTITY xxe7 "&xxe6;&xxe6;&xxe6;&xxe6;&xxe6;&xxe6;&xxe6;&xxe6;&xxe6;&xxe6;" >
<!ENTITY xxe8 "&xxe7;&xxe7;&xxe7;&xxe7;&xxe7;&xxe7;&xxe7;&xxe7;&xxe7;&xxe7;" >
<!ENTITY xxe9 "&xxe8;&xxe8;&xxe8;&xxe8;&xxe8;&xxe8;&xxe8;&xxe8;&xxe8;&xxe8;" >
]>
<response>
	<result>&xxe9;</result>
</response>

Общие рекомендации

Самый безопасный способ предотвратить XXE - полностью отключить DTD (внешние сущности) Если невозможно полностью отключить DTD, то внешние сущности и объявления типа внешнего документа должны быть отключены способом, специфичным для каждого анализатора.

xmerl

В стандартном парсере xmerl по умолчанию разрешены внешние сущности. Чтобы их запретить, необходимо использовать опции {acc_fun, Fun} и {fetch_fun, Fun}

Пример безопасного парсера:

#!/usr/bin/env escript
%% -*- erlang -*-
%%! -smp enable -sname factorial -mnesia debug verbose
main([Path]) ->
    try
        io:format("Xml file path[~p]~n", [Path]),
        Fun = fun(_, GlobalState) ->                    %% deny to fetch an external resource (e.g. a DTD).
            throw(contain_entity),
            {ok, {string, "not_fetched"}, GlobalState}
        end,

        FunAcc = fun
            (ParsedEntity, Acc, GlobalState) ->
                case xmerl_scan:user_state(GlobalState) of
                    N when N > 5 -> throw(too_mach_items);      %% deny a big file
                    N ->
                        {[ParsedEntity | Acc], xmerl_scan:user_state(N+1, GlobalState)}
                end
        end,

        RulesState = #{rules => []},

        FunRuleRead = fun(Context, Name, ScannerState) ->
            throw(contain_entity)   %% deny process any <!ENTITY ... >
        end,

        FunRuleWrite = fun(Context, Name, Definition, ScannerState) ->
            throw(contain_entity)   %% deny process any <!ENTITY ... >
        end,

        Xml = xmerl_scan:file(Path, [
            {user_state, 0},
            {fetch_fun, Fun},
            {acc_fun, FunAcc},
            {rules, FunRuleRead, FunRuleWrite, RulesState}
        ]),
        {ok, Dir} = file:get_cwd(),
        Format = io_lib:format("~p", [Xml]),
        ok = file:write_file(Dir ++ "/xml.out", Format, [append])
    catch
        Excep:Error:St ->
            io:format("Error ~p: ~n ~p~n~p", [Excep, Error, St]),
            halt(1)
    end;
main(_) ->
    halt(1).

References