Files
zoneminder/web/includes/Zone.php
Isaac Connor 47d7e0d37f fix: escape Zone, Event and Server names on output
Zone.Name, Event.Name, Event.Notes and Server.Name are persisted
user-controllable strings that were emitted without escaping, so a user
with edit rights on the object could store markup that runs in the
browser of anyone who later views the page, including an administrator.
Zone and Server save through getFormChanges + raw SQL, so they never
pass the object filter_regexp layer that strips markup elsewhere.

HTML and SVG sinks now use validHtmlStr():
- Zone::svg_polygon() escapes the <title>, covering every caller
  (event view, montage and stream).
- views/zone.php and views/plugin.php headings.
- ajax/modals/server.php modal title.

The two inline-JS sinks in views/js/event.js.php are require_once'd
inside a <script nonce> block, so a </script> in Event.Name or Notes
broke out of the element and the nonce did not help. Notes was also
interpolated into a template literal, making backtick and ${} live.
Both now emit json_encode(..., JSON_HEX_TAG|JSON_HEX_APOS|
JSON_HEX_QUOT|JSON_HEX_AMP), which supplies its own double quotes.

Refs GHSA-72c3-g86w-f587.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 12:39:35 -04:00

86 lines
2.5 KiB
PHP

<?php
namespace ZM;
require_once('database.php');
require_once('Object.php');
require_once('Monitor.php');
class Zone extends ZM_Object {
protected static $table = 'Zones';
protected $defaults = array(
'Id' => null,
'MonitorId' => null,
'Name' => '',
'Type' => 'Active',
'Units' => 'Percent',
'NumCoords' => '4',
'Coords' => '',
'Area' => '0',
'AlarmRGB' => 0xff0000,
'CheckMethod' => 'Blobs',
'MinPixelThreshold' => 25,
'MaxPixelThreshold' => null,
'MinAlarmPixels' => null,
'MaxAlarmPixels' => null,
'FilterX' => 3,
'FilterY' => 3,
'MinFilterPixels' => null,
'MaxFilterPixels' => null,
'MinBlobPixels' => null,
'MaxBlobPixels' => null,
'MinBlobs' => 1,
'MaxBlobs' => null,
'OverloadFrames' => 0,
'ExtendAlarmFrames' => 0,
);
public static function find( $parameters = array(), $options = array() ) {
return ZM_Object::_find(self::class, $parameters, $options);
}
public static function find_one( $parameters = array(), $options = array() ) {
return ZM_Object::_find_one(self::class, $parameters, $options);
}
public function Monitor() {
if (isset($this->{'MonitorId'})) {
$Monitor = Monitor::find_one(array('Id'=>$this->{'MonitorId'}));
if ( $Monitor )
return $Monitor;
}
return new Monitor();
}
public function Points() {
return coordsToPoints($this->Coords());
}
public function AreaCoords() {
return preg_replace('/\s+/', ',', $this->Coords());
}
public function svg_polygon($width=0, $height=0) {
$areaCoords = $this->AreaCoords();
if ($width && $height) {
$points = coordsToPoints($this->Coords());
$isPixel = false;
foreach ($points as $point) {
if ($point['x'] > 100 || $point['y'] > 100) {
$isPixel = true;
break;
}
}
if ($isPixel) {
foreach ($points as &$point) {
$point['x'] = round($point['x'] / $width * 100, 2);
$point['y'] = round($point['y'] / $height * 100, 2);
}
unset($point);
$areaCoords = preg_replace('/\s+/', ',', pointsToCoords($points));
}
}
return '<polygon points="'.$areaCoords.'" class="'.$this->Type().'" data-mid="'.$this->MonitorId().'" data-zid="'.$this->Id().'"><title>'.validHtmlStr($this->Name()).'</title></polygon>';
}
} # end class Zone
?>