Files
cacti/tests/Unit/DbLikeEscapeTest.php
Thomas VincentandTheWitness 4238aadcb1 security: escape LIKE wildcards in poller_automation and plugins.php filters (#7670)
* security: add db_like_escape and bind the PTR hostname in poller_automation

poller_automation.php concatenated the gethostbyaddr() result into a LIKE clause, so an attacker controlling the reverse-DNS zone injected SQL through a crafted PTR record. The new db_like_escape() helper neutralises the LIKE metacharacters, and the reverse-DNS hostname is now passed as a bound parameter (LIKE ?). The escape semantics are covered by unit tests and verified against a live MariaDB.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

* security: bind and LIKE-escape the plugins.php filter (GHSA-jjfq-mj5r-2r95)

The plugins.php filter reached its LIKE clauses through db_qstr(), which escapes quotes but not the LIKE metacharacters, so a filter of '%' matched every row and bypassed the state filter. The filter LIKE clauses are now bound placeholders and the value is LIKE-escaped once with db_like_escape() and reused for each placeholder; the count and list queries are prepared.

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>

---------

Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
2026-08-16 21:15:44 -04:00

61 lines
2.6 KiB
PHP

<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| 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. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
*/
/*
* db_like_escape() neutralises the LIKE metacharacters so a value matches
* literally under a bound LIKE ? clause. The MySQL semantics (that '\%' and '\_'
* become the literal characters) are verified separately against a live MariaDB;
* these tests pin the escaping the helper produces.
*/
require_once dirname(__DIR__) . '/Helpers/UnitStubs.php';
require_once dirname(__DIR__, 2) . '/lib/database.php';
test('escapes percent, underscore and backslash for LIKE', function () {
expect(db_like_escape('100%'))->toBe('100\\%');
expect(db_like_escape('a_b'))->toBe('a\\_b');
expect(db_like_escape('c\\d'))->toBe('c\\\\d');
});
test('escapes the backslash before the wildcards so nothing is left unescaped', function () {
// a raw backslash must not become a LIKE escape for a following char
expect(db_like_escape('\\%'))->toBe('\\\\\\%');
});
test('leaves ordinary text untouched', function () {
expect(db_like_escape('router-01.example.net'))->toBe('router-01.example.net');
expect(db_like_escape(''))->toBe('');
});
test('neutralises a wildcard-injection payload', function () {
// the reported filter/PTR payloads relied on an unescaped % matching every row
expect(db_like_escape('%'))->toBe('\\%');
expect(db_like_escape('foo%" OR "1"="1'))->toContain('\\%');
expect(db_like_escape('_'))->toBe('\\_');
});
test('returns a string for non-string input', function () {
expect(db_like_escape(12345))->toBe('12345');
expect(db_like_escape(null))->toBe('');
});
test('poller_automation binds the PTR hostname with db_like_escape', function () {
$src = file_get_contents(dirname(__DIR__, 2) . '/poller_automation.php');
// the reverse-DNS hostname must reach LIKE via a bound parameter, not concat
expect($src)->toContain('hostname = ? OR hostname LIKE ?');
expect($src)->toContain('db_like_escape($hostname) . \'%\'');
expect($src)->not->toContain('hostname LIKE "\' . $hostname . \'%"');
});