(*该漏洞影响版本:Struts 2.0.0 – Struts 2.3.15)
(*该博客仅仅只是记录我工作学习时遇到的问题,仅供参考!)
(*如果,描述中可能存在错误,请多指教!)
在昨天在对我目前负责的那个项目进行日常维护的时候,系统被别人攻克,上传了一个.txt文件,他人可以直接访问这个项目下txt文件,就可以获取到txt文件内的内容。
首先,介绍下我目前维护的项目,使用的是strust2.1+hibernate3.0架构模式,也就是javaweb+SSH框架,不过为了简化,并没有添加spring框架。也并没有使用现在的springMVC模式!
至于数据库,运用的是Mysql,因为mysql的轻便,深受国内用户的喜好!服务器,使用的是tomcat,也没什么好介绍的!
接下来,我先演示我所负责的项目,是如何被攻克的!
首先,打开strust漏洞监测工具:将我目前项目网址放在action框中:
如下:
一般,使用SSH框架,都是由通过strust.xml文件定义的action进行控制,然后进行,验证,如果验证通过,会跳转到success.jsp,如果没有通过验证,则会回退到login.jsp。
当然,通过这个检测工具可以看到,我所负责的项目,存在strust2远程代码执行漏洞,编号S-016!
这里我上传一个test文件,结尾是.txt文件的后缀名。然后点击上传:
等工具提示上传成功之后,在访问该目录下的test.txt文件,就可以显示到这个
当时遇到这个问题,顿时头都大了,前段时间,我和别人讨论过php通过上传文件漏洞,上传一个被截断的.JPG格式的木马,获取服务器权限的一个例子。
后来我上网查了下资料,原来这个漏洞,早已在2013年7月份,就公布出来,该漏洞具体情况如下:
Bugtraq ID:61189
CVE ID:CVE-2013-2251
CNCVE ID:CNCVE-20132251
漏洞发布时间:2013-07-16
漏洞更新时间:2013-07-16
漏洞起因
输入验证错误
危险等级
高
影响系统
Apache Struts 2.0.0-2.3.15
不受影响系统
危害
远程攻击者利用漏洞以应用程序上下文执行任意代码。
CVSSv2:
攻击所需条件
攻击者必须访问Struts应用。
漏洞信息
Apache Struts框架是一个基于Java Servlets,JavaBeans, 和 JavaServer Pages (JSP)的Web应用框架的开源项目。
Apache Struts 2 DefaultActionMapper在处理短路径重定向参数前缀"action:"/"redirect:"/"redirectAction:"时存在命令执行漏洞,由于对"action:"/"redirect:"/"redirectAction:"后的URL信息使用OGNL表达式处理,远程攻击者可以利用漏洞提交特殊URL可用于执行任意Java代码。
测试方法
1,简单表达式
http://host/struts2-blank/example/X.action?action:%25{3*4}
http://host/struts2-showcase/employee/save.action?redirect:%25{3*4}
2,命令执行
http://host/struts2-blank/example/X.action?action:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()}
http://host/struts2-showcase/employee/save.action?redirect:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()}
http://host/struts2-showcase/employee/save.action?redirectAction:%25{(new+java.lang.ProcessBuilder(new+java.lang.String[]{'command','goes','here'})).start()}
安全建议
厂商解决方案
Apache Struts 2.3.15.1已经修复此漏洞,建议用户下载更新:
http://struts.apache.org/
漏洞提供者
Takeshi Terada of Mitsui Bussan Secure Directions, Inc.
漏洞消息链接
http://struts.apache.org/release/2.3.x/docs/s2-016.html
http://struts.apache.org/release/2.3.x/docs/version-notes-23151.html
http://packetstormsecurity.com/files/122442/Apache-Struts-2-Open-Redirection-Command-Execution.html
文章来源:http://www.venustech.com.cn/
当然,解决办法,也很简单,要么打补丁,要么提升strust的版本,当然,这两种方法带来的工作量都是很大的,不是那么容易去做,再说以我目前的技术,很难快速的把这个问题解决掉!
后来,我发现,该漏洞是通过访问上传的文件,其后缀名是.txt,格式,如果我不让他访问这个文件,是不是可以解决掉这个问题,只要通过strust拦截器进行拦截就好。(当然,该项目中也是加入了拦截器,但是该拦截器只是拦截.jsp格式的文件,并没有将其他格式的文件,进行拦截!)
当 有了这个想法之后,接下来的一切都好解决了,对项目拦截器,进行优化就好!
首先,贴上我原本没有优化的web.xml代码:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>WebJourManage</display-name>
<welcome-file-list>
<welcome-file>login.html</welcome-file>
<welcome-file>login.htm</welcome-file>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>loginauth</filter-name>
<filter-class>LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginauth</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
</web-app>
可以看到,在这一块代码中,我只进行了对.jsp文件的拦截,并没有对其他文件进行拦截验证:
<filter>
<filter-name>loginauth</filter-name>
<filter-class>LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginauth</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
至于在LoginFilter.class中,我是这样写的:
package Filter;
import javax.servlet.Filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
HttpSession session = servletRequest.getSession();
String path = servletRequest.getRequestURI();
String empId = (String) session.getAttribute("username");
if(path.indexOf("/login.jsp") > -1) {
chain.doFilter(servletRequest, servletResponse);
return;
}
if (empId == null || "".equals(empId)) {
servletResponse.sendRedirect("/login.jsp");
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
}
这个文件会回去session中的username,即登录的用户名,如果该用户名不存在,就是跳转到login.jsp上,如果存在。(该功能就是为了验证用户是否登录。)
接下来就是在web.xml中,将
<filter>
<filter-name>loginauth</filter-name>
<filter-class>LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginauth</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
这段代码改成
<filter>
<filter-name>loginauth</filter-name>
<filter-class>LoginFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>loginauth</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
其中:
<url-pattern>*</url-pattern>
这一块,是对所有文件进行验证!不过这样写后,还是有个问题,就是当你登录完成之后,会跳转到login.action。该login.action也被拦截,然后跳转到login.jsp上!
后来,我看了下LoginFilter.class的代码,发现这一块。
if(path.indexOf("/login.jsp") > -1) {
chain.doFilter(servletRequest, servletResponse);
return;
}
当页面为login.jsp时候,会执行:chain.doFilter(servletRequest, servletResponse);
只要在这里对login.action加入到判断语句中,就可以避免这个问题,代码如下:
if(path.indexOf("/login.jsp") > -1||path.indexOf("/login.action") > -1) {
chain.doFilter(servletRequest, servletResponse);
return;
}
就这样,问题结局了,但是该漏洞还在,文件依旧可以上传,但是如果没有登录,在访问上传的那个文件,会发现会直接跳转到login.jsp上。
总结:虽然这并不是一个很好解决这个问题的方法,但是使用访问地址重新定向到login.jsp,用一种很简单的方式,避免了服务器 提权的问题!