mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-11-02 10:44:30 -05:00
After saving a zone, actions/zone.php redirected to the REFERER the zone form posted, which the form fills from the Referer its page was loaded with. A zone editor who reached the zone page through a link on another site was sent to that site after saving: an open redirect, usable to lead someone from a trusted ZoneMinder page to a look-alike login. Forging the POST itself is blocked by the CSRF token, so it needs an editor who followed such a link. localRedirectUrl() returns the target only if it is a query-only URL or an absolute http(s) URL on the host and port of the current request, and the fallback otherwise: another host, //host, backslashes, user@host, other schemes and control characters all give the fallback. The zone save falls back to the zones list, as it already does with no Referer (#5207). tests/php/test_local_redirect_url.php covers 21 cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
4.0 KiB
PHP
92 lines
4.0 KiB
PHP
<?php
|
|
//
|
|
// ZoneMinder web action file
|
|
// Copyright (C) 2019 ZoneMinder LLC
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 2
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
//
|
|
|
|
// Monitor edit actions, require a monitor id and edit permissions for that monitor
|
|
if ( !empty($_REQUEST['mid']) && canEdit('Monitors', $_REQUEST['mid']) ) {
|
|
$mid = validInt($_REQUEST['mid']);
|
|
if ( ($action == 'zone') && isset($_REQUEST['zid']) ) {
|
|
$zid = validInt($_REQUEST['zid']);
|
|
$monitor = new ZM\Monitor($mid);
|
|
|
|
if ( !empty($zid) ) {
|
|
$zone = dbFetchOne('SELECT * FROM Zones WHERE MonitorId=? AND Id=?', NULL, array($mid, $zid));
|
|
} else {
|
|
$zone = array();
|
|
}
|
|
|
|
// Ensure threshold values are always stored as percentages of zone area (0-100).
|
|
// Values > 100 are pixel counts; convert them using the zone's pixel area.
|
|
$thresholdFields = array('MinAlarmPixels', 'MaxAlarmPixels', 'MinFilterPixels', 'MaxFilterPixels', 'MinBlobPixels', 'MaxBlobPixels');
|
|
if (isset($_REQUEST['newZone']['Coords'])) {
|
|
$points = coordsToPoints($_REQUEST['newZone']['Coords']);
|
|
if ($points) {
|
|
$zoneArea = getPolyArea($points);
|
|
$zonePixelArea = $zoneArea / 10000.0 * $monitor->ViewWidth() * $monitor->ViewHeight();
|
|
if ($zonePixelArea > 0) {
|
|
foreach ($thresholdFields as $field) {
|
|
if (isset($_REQUEST['newZone'][$field]) && $_REQUEST['newZone'][$field] !== '' && floatval($_REQUEST['newZone'][$field]) > 100) {
|
|
$_REQUEST['newZone'][$field] = round(floatval($_REQUEST['newZone'][$field]) / $zonePixelArea * 100, 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach ($thresholdFields as $field) {
|
|
if (isset($_REQUEST['newZone'][$field]) && $_REQUEST['newZone'][$field] !== '') {
|
|
$_REQUEST['newZone'][$field] = max(0, min(100, floatval($_REQUEST['newZone'][$field])));
|
|
}
|
|
}
|
|
|
|
unset($_REQUEST['newZone']['Points']);
|
|
|
|
# convert these fields to integer e.g. NULL -> 0
|
|
$types = array(
|
|
'OverloadFrames' => 'integer',
|
|
'ExtendAlarmFrames' => 'integer',
|
|
);
|
|
|
|
$changes = getFormChanges($zone, $_REQUEST['newZone'], $types);
|
|
|
|
if ( count($changes) ) {
|
|
if ( $zid > 0 ) {
|
|
dbQuery('UPDATE Zones SET '.implode(', ', $changes).' WHERE MonitorId=? AND Id=?', array($mid, $zid));
|
|
} else {
|
|
dbQuery('INSERT INTO Zones SET MonitorId=?, '.implode(', ', $changes), array($mid));
|
|
}
|
|
ZM\AuditAction(($zid > 0 ? 'update' : 'create'), 'zone', $zid, 'MonitorId: '.$mid);
|
|
if ( daemonCheck() && ($monitor->Type() != 'WebSite') ) {
|
|
$monitor->zmcControl('reload');
|
|
}
|
|
if ( ($_REQUEST['newZone']['Type'] == 'Privacy') && $monitor->Controllable() ) {
|
|
$monitor->sendControlCommand('quit');
|
|
}
|
|
} // end if changes
|
|
# HTTP_REFERER will typically be ?view=zone so no good.
|
|
# if a referer is passed in $_REQUEST then use it otherwise go to ?view=zones.
|
|
# The zone form always posts REFERER, empty when the form had no Referer
|
|
# (a typed URL or bookmark); an empty $redirect renders the zone view with
|
|
# this action's $zone, which has no Points. The form fills it from the
|
|
# Referer its page was loaded with, which can be another site, so only go
|
|
# back to it when it is on this server.
|
|
$redirect = localRedirectUrl(isset($_REQUEST['REFERER']) ? $_REQUEST['REFERER'] : '', '?view=zones&mid='.$mid);
|
|
} // end if action
|
|
} // end if $mid and canEdit($mid)
|
|
?>
|