|
114 | 114 | /* |
115 | 115 | * thold.php:617 builds a WHERE clause with a RLIKE predicate. The user- |
116 | 116 | * supplied rfilter value must be quoted via db_qstr before interpolation |
117 | | - * to prevent regex injection that could exhaust server memory (ReDoS) or |
118 | | - * expose data via crafted patterns. |
| 117 | + * so it is embedded in the SQL statement as data rather than SQL syntax. |
| 118 | + * This verifies SQL-injection hardening for the interpolated RLIKE value; |
| 119 | + * it does not, by itself, constrain regex complexity or mitigate ReDoS. |
119 | 120 | */ |
120 | 121 |
|
121 | 122 | test('RLIKE predicate uses db_qstr on rfilter value', function () use ($thold_src): void { |
|
173 | 174 | }); |
174 | 175 |
|
175 | 176 | // --------------------------------------------------------------------------- |
176 | | -// thold_webapi.php — sanitize_unserialize_selected_items |
| 177 | +// thold_webapi.php — cacti_unserialize with structural validation |
177 | 178 | // --------------------------------------------------------------------------- |
178 | 179 |
|
179 | | -describe('thold_webapi.php — sanitize_unserialize_selected_items', function () use ($thold_webapi_src): void { |
| 180 | +describe('thold_webapi.php — cacti_unserialize with structural validation', function () use ($thold_webapi_src): void { |
180 | 181 | /* |
181 | | - * thold_webapi.php:864 processes a serialized graph array from user input. |
182 | | - * Using sanitize_unserialize_selected_items() instead of raw unserialize() |
183 | | - * prevents PHP object injection attacks. |
| 182 | + * thold_webapi.php processes a serialized graph array from user input. |
| 183 | + * selected_graphs_array has string keys ('cg'/'sg') and nested non-integer |
| 184 | + * sub-arrays; sanitize_unserialize_selected_items() rejects non-numeric keys |
| 185 | + * and would always return false for this structure. cacti_unserialize() with |
| 186 | + * explicit post-deserialization structural validation is the correct approach. |
184 | 187 | */ |
185 | 188 |
|
186 | | - test('selected_graphs_array is processed through sanitize_unserialize_selected_items', function () use ($thold_webapi_src): void { |
| 189 | + test('selected_graphs_array is deserialized via cacti_unserialize with stripslashes', function () use ($thold_webapi_src): void { |
187 | 190 | expect($thold_webapi_src)->toContain( |
188 | | - "sanitize_unserialize_selected_items(get_nfilter_request_var('selected_graphs_array'))" |
| 191 | + "cacti_unserialize(stripslashes(get_nfilter_request_var('selected_graphs_array')))" |
189 | 192 | ); |
190 | 193 | }); |
191 | 194 |
|
|
237 | 240 | expect(intval('-5'))->toBe(-5); |
238 | 241 | }); |
239 | 242 | }); |
| 243 | + |
| 244 | +// --------------------------------------------------------------------------- |
| 245 | +// notify_lists.php — rfilter validation chain for RLIKE |
| 246 | +// --------------------------------------------------------------------------- |
| 247 | + |
| 248 | +describe('notify_lists.php — rfilter validated before RLIKE use', function () use ($notify_lists_src): void { |
| 249 | + /* |
| 250 | + * rfilter is registered with FILTER_VALIDATE_IS_REGEX in the request var |
| 251 | + * table, so get_request_var() returns '' for any syntactically invalid |
| 252 | + * pattern. db_qstr() then SQL-quotes the validated value, preventing the |
| 253 | + * RLIKE operand from escaping its string literal context. |
| 254 | + */ |
| 255 | + |
| 256 | + test('rfilter request var is registered with FILTER_VALIDATE_IS_REGEX', function () use ($notify_lists_src): void { |
| 257 | + expect($notify_lists_src)->toContain('FILTER_VALIDATE_IS_REGEX'); |
| 258 | + }); |
| 259 | + |
| 260 | + test('RLIKE predicate in list filter uses db_qstr on rfilter', function () use ($notify_lists_src): void { |
| 261 | + expect($notify_lists_src)->toContain("RLIKE ' . db_qstr(get_request_var('rfilter'))"); |
| 262 | + }); |
| 263 | + |
| 264 | + test('regex special characters are contained within db_qstr SQL quotes', function (): void { |
| 265 | + // db_qstr wraps the value in single quotes; simulate the quoting and |
| 266 | + // confirm the metacharacter sequence cannot escape the string boundary. |
| 267 | + foreach (['.', '*', '+', '(a+)+', '[a-z]', '.*.*.*'] as $pattern) { |
| 268 | + $quoted = "'" . str_replace("'", "\\'", $pattern) . "'"; |
| 269 | + expect(substr($quoted, 0, 1))->toBe("'"); |
| 270 | + expect(substr($quoted, -1))->toBe("'"); |
| 271 | + } |
| 272 | + }); |
| 273 | + |
| 274 | + test('invalid regex returns empty string from get_request_var with FILTER_VALIDATE_IS_REGEX', function (): void { |
| 275 | + // FILTER_VALIDATE_IS_REGEX rejects invalid patterns; simulate that |
| 276 | + // preg_match returns false for a malformed pattern so db_qstr never |
| 277 | + // receives unvalidated input. |
| 278 | + $malformed = '[unclosed'; |
| 279 | + expect(@preg_match('/' . $malformed . '/', ''))->toBe(false); |
| 280 | + }); |
| 281 | +}); |
| 282 | + |
| 283 | +// --------------------------------------------------------------------------- |
| 284 | +// notify_lists.php — zero-value UPDATEs are guarded by WHERE bindings |
| 285 | +// --------------------------------------------------------------------------- |
| 286 | + |
| 287 | +describe('notify_lists.php — UPDATE SET 0 statements include bound id guards', function () use ($notify_lists_src): void { |
| 288 | + /* |
| 289 | + * notify_warning and notify_alert are int(11) unsigned NULL columns; 0 is |
| 290 | + * the Cacti sentinel for "no list assigned". The WHERE clause must bind |
| 291 | + * both the host id and the current list id so that only rows referencing |
| 292 | + * the deleted list are cleared, not all rows. |
| 293 | + */ |
| 294 | + |
| 295 | + test('notify_warning cleared only where it matches the deleted list id', function () use ($notify_lists_src): void { |
| 296 | + expect($notify_lists_src)->toContain('AND td.notify_warning = ?'); |
| 297 | + }); |
| 298 | + |
| 299 | + test('notify_alert cleared only where it matches the deleted list id', function () use ($notify_lists_src): void { |
| 300 | + expect($notify_lists_src)->toContain('AND td.notify_alert = ?'); |
| 301 | + }); |
| 302 | + |
| 303 | + test('thold_host_email reset uses prepared statement with bound host id', function () use ($notify_lists_src): void { |
| 304 | + expect($notify_lists_src)->toContain("db_execute_prepared('UPDATE host"); |
| 305 | + expect($notify_lists_src)->toContain('SET thold_host_email = 0'); |
| 306 | + }); |
| 307 | +}); |
| 308 | + |
| 309 | +// --------------------------------------------------------------------------- |
| 310 | +// $selected_items loop — edge case and non-numeric input handling |
| 311 | +// --------------------------------------------------------------------------- |
| 312 | + |
| 313 | +describe('selected_items loop — empty array and non-numeric element handling', function (): void { |
| 314 | + test('empty selected_items produces zero loop iterations', function (): void { |
| 315 | + $selected_items = []; |
| 316 | + $iterations = 0; |
| 317 | + for ($i = 0; $i < count($selected_items); $i++) { |
| 318 | + $iterations++; |
| 319 | + } |
| 320 | + expect($iterations)->toBe(0); |
| 321 | + }); |
| 322 | + |
| 323 | + test('non-numeric element is coerced to 0 by intval', function (): void { |
| 324 | + $selected_items = ['not-an-id']; |
| 325 | + expect(intval($selected_items[0]))->toBe(0); |
| 326 | + }); |
| 327 | + |
| 328 | + test('array with mixed types: intval extracts leading integer', function (): void { |
| 329 | + expect(intval('5abc'))->toBe(5); |
| 330 | + }); |
| 331 | + |
| 332 | + test('injection payload in element is coerced to 0', function (): void { |
| 333 | + expect(intval("'; DELETE FROM thold_data; --"))->toBe(0); |
| 334 | + }); |
| 335 | +}); |
0 commit comments