Skip to content

Commit 6ed8d7f

Browse files
ccaillytrasher
authored andcommitted
Unauthorized reading of a specific asset object
1 parent f0cd62a commit 6ed8d7f

3 files changed

Lines changed: 92 additions & 12 deletions

File tree

.phpstan-baseline.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -511,24 +511,12 @@
511511
'count' => 1,
512512
'path' => __DIR__ . '/ajax/webhook.php',
513513
];
514-
$ignoreErrors[] = [
515-
'message' => '#^Cannot call method getFromDB\\(\\) on CommonDBTM\\|false\\.$#',
516-
'identifier' => 'method.nonObject',
517-
'count' => 1,
518-
'path' => __DIR__ . '/ajax/webhook.php',
519-
];
520514
$ignoreErrors[] = [
521515
'message' => '#^Cannot call static method getTypeName\\(\\) on CommonDBTM\\|false\\.$#',
522516
'identifier' => 'staticMethod.nonObject',
523517
'count' => 1,
524518
'path' => __DIR__ . '/ajax/webhook.php',
525519
];
526-
$ignoreErrors[] = [
527-
'message' => '#^Parameter \\#1 \\$item of method Webhook\\:\\:getApiPath\\(\\) expects CommonDBTM, CommonDBTM\\|false given\\.$#',
528-
'identifier' => 'argument.type',
529-
'count' => 1,
530-
'path' => __DIR__ . '/ajax/webhook.php',
531-
];
532520
$ignoreErrors[] = [
533521
'message' => '#^Parameter \\#1 \\$input of method CommonDBTM\\:\\:add\\(\\) expects array\\<string, mixed\\>, array\\<string, mixed\\>\\|null given\\.$#',
534522
'identifier' => 'argument.type',

ajax/webhook.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,24 @@
128128
$error[] = __s('Please select an event');
129129
}
130130

131+
// Validate that the requested itemtype matches the webhook's configured itemtype
132+
if (
133+
count($error) === 0
134+
&& $webhook->getID()
135+
&& $itemtype !== $webhook->fields['itemtype']
136+
) {
137+
throw new AccessDeniedHttpException();
138+
}
139+
131140
if (count($error) > 0) {
132141
array_unshift($error, __s("Result can't be loaded :"));
133142
echo implode("<br>&nbsp; - ", $error);
134143
} else {
135144
$obj = getItemForItemtype($itemtype);
145+
// Check that the current user has READ access to this item
146+
if ($obj === false || !$obj->can($items_id, READ)) {
147+
throw new AccessDeniedHttpException();
148+
}
136149
$obj->getFromDB($items_id);
137150
$path = $webhook->getApiPath($obj);
138151
echo $webhook->getResultForPath($path, $event, $itemtype, $items_id, $raw_output);

tests/functional/WebhookTest.php

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,4 +448,83 @@ private function countQueuedRequestForWebhook(Webhook $webhook): int
448448
'webhooks_id' => $webhook->getID(),
449449
]);
450450
}
451+
452+
/**
453+
* Ensure the get_webhook_body action enforces READ permission on the requested item.
454+
* A user who cannot READ an item must not be able to retrieve its webhook body.
455+
*/
456+
public function testGetWebhookBodyReadPermissionCheck(): void
457+
{
458+
$this->login();
459+
460+
$computer = $this->createItem(\Computer::class, [
461+
'name' => 'Test computer',
462+
'entities_id' => $_SESSION['glpiactive_entity'],
463+
]);
464+
$computers_id = $computer->getID();
465+
466+
$webhook = $this->createItem(Webhook::class, [
467+
'name' => 'Test webhook',
468+
'entities_id' => $_SESSION['glpiactive_entity'],
469+
'url' => 'http://localhost',
470+
'itemtype' => \Computer::class,
471+
'event' => 'new',
472+
'is_active' => 1,
473+
'use_default_payload' => 1,
474+
]);
475+
476+
// Sanity check: with READ right the item is accessible
477+
$obj = new \Computer();
478+
$this->assertTrue($obj->can($computers_id, READ));
479+
480+
// Remove READ right on Computer for the current profile
481+
$saved_rights = $_SESSION['glpiactiveprofile'];
482+
$_SESSION['glpiactiveprofile']['computer'] = ALLSTANDARDRIGHT & ~READ;
483+
484+
// The can() check must now fail, which is the guard used by the ajax action
485+
$obj2 = new \Computer();
486+
$this->assertFalse($obj2->can($computers_id, READ));
487+
488+
// Restore rights
489+
$_SESSION['glpiactiveprofile'] = $saved_rights;
490+
}
491+
492+
/**
493+
* Ensure the get_webhook_body action rejects requests whose itemtype does not
494+
* match the webhook's configured itemtype.
495+
*/
496+
public function testGetWebhookBodyItemtypeMismatch(): void
497+
{
498+
$this->login();
499+
500+
// Create a webhook configured for Computer
501+
$webhook = $this->createItem(Webhook::class, [
502+
'name' => 'Test webhook',
503+
'entities_id' => $_SESSION['glpiactive_entity'],
504+
'url' => 'http://localhost',
505+
'itemtype' => \Computer::class,
506+
'event' => 'new',
507+
'is_active' => 1,
508+
'use_default_payload' => 1,
509+
]);
510+
511+
// The webhook's configured itemtype is Computer
512+
$this->assertEquals(\Computer::class, $webhook->fields['itemtype']);
513+
514+
// A request asking for Monitor (a different itemtype) must be rejected:
515+
// the mismatch condition used in the ajax action is:
516+
// $webhook->getID() && $itemtype !== $webhook->fields['itemtype']
517+
$requested_itemtype = \Monitor::class;
518+
$this->assertNotEquals($requested_itemtype, $webhook->fields['itemtype']);
519+
$this->assertGreaterThan(0, $webhook->getID());
520+
521+
// Directly testing the mismatch guard logic
522+
$mismatch_detected = $webhook->getID() && $requested_itemtype !== $webhook->fields['itemtype'];
523+
$this->assertTrue($mismatch_detected, 'Itemtype mismatch should have been detected');
524+
525+
// A request with the correct itemtype must not be rejected
526+
$matching_itemtype = \Computer::class;
527+
$no_mismatch = $webhook->getID() && $matching_itemtype !== $webhook->fields['itemtype'];
528+
$this->assertFalse($no_mismatch, 'Correct itemtype should not trigger the mismatch guard');
529+
}
451530
}

0 commit comments

Comments
 (0)