Text size
CVE-2025-52670October 20258 min read

IDOR in Banner Deletion
When Parent Validation Isn't Enough

Revive Adserver validates you own a campaign, but never checks if you own the banner you're deleting. Any manager can sabotage any other manager's ads.

CVSS
7.1
Impact
HIGH
Privilege
LOW
Complexity
LOW
TL;DR

Manager A authenticates -> Uses own clientid/campaignid (passes checks) -> Supplies victim's bannerid (no check) -> Manager B's banner deleted

1

Technical Overview

I found an IDOR vulnerability in Revive Adserver's banner deletion endpoint - now tracked as CVE-2025-52670. It's textbook CWE-639: Authorization Bypass Through User-Controlled Key.

The /www/admin/delete-banner.php endpoint handles bulk banner deletion. It does three things right: verifies you're a manager, checks you have delete permissions, and validates your CSRF token. But it makes one critical mistake.

The endpoint checks if you have access to the clientid and campaignid parameters. These pass. Then it takes whatever bannerid values you supply and deletes them. No ownership check. No validation that those banners belong to the campaign. Just... delete.

This is the classic "validate the parent, forget the child" pattern. And it means any manager can delete any other manager's banners.

2

Attack Flow

Step 1 of 5

Authenticate

Attacker

Log in as Manager A with valid credentials. Navigate to your own campaign banner page.

Session established, CSRF token available

Visual Walkthrough

Step 1 of 4
IDOR Attack Demonstration
Loading...

Login as Manager A

Attacker authenticates with valid manager credentials.

What the Victim Sees

Nothing. Manager B logs in the next day and their banner is just... gone. The audit log shows a deletion, but from a session that looks like routine activity. Unless they check timestamps carefully, they won't know what happened.

3

Code Analysis

The Vulnerable Code

Here's the authorization block in delete-banner.php:

php
// Security checks - Lines 28-34
OA_Permission::enforceAccount(OA_ACCOUNT_MANAGER);
OA_Permission::enforceAccountPermission(OA_ACCOUNT_MANAGER, OA_PERM_MANAGER_DELETE);
OA_Permission::enforceAccessToObject('clients', $clientid);      // Validates client
OA_Permission::enforceAccessToObject('campaigns', $campaignid);  // Validates campaign
// MISSING: OA_Permission::enforceAccessToObject('banners', $bannerid);

OA_Permission::checkSessionToken();  // CSRF check

And here's the deletion loop:

php
// Deletion loop - Lines 40-48
if (!empty($bannerid)) {
  $ids = explode(',', $bannerid);
  foreach ($ids as $bannerid) {
      $doBanners = OA_Dal::factoryDO('banners');
      $doBanners->bannerid = $bannerid;  // Accepts ANY ID
      if ($doBanners->get($bannerid)) {
          $aBanner = $doBanners->toArray();
      }
      $doBanners->delete();  // Deletes without ownership check
  }
}
The Missing Check

Notice what's absent: OA_Permission::enforceAccessToObject('banners', $bannerid). This single missing line is the entire vulnerability.

How It Should Look

Compare with campaign-delete.php, which handles this correctly:

php
// campaign-delete.php - Correct pattern
foreach ($ids as $campaignid) {
  OA_Permission::enforceAccessToObject('campaigns', $campaignid);  // Check INSIDE loop
  $doCampaigns = OA_Dal::factoryDO('campaigns');
  $doCampaigns->delete();
}

The campaign deletion validates ownership inside the loop for each ID. The banner deletion doesn't.

4

Impact Analysis

Competing Managers Victim

  • Delete competitor banners
  • Sabotage rival campaigns
  • Cause revenue loss

Ad Platform Victim

  • Data integrity compromised
  • Audit trail misleading
  • Trust erosion

Advertisers Victim

  • Lost active advertisements
  • Unexpected campaign failures
  • Revenue impact

Attack Characteristics

This isn't a sophisticated attack. That's what makes it dangerous.

Low privilege required — Any manager account works. No special tools — Just a browser and URL manipulation. Stealthy — Looks like legitimate deletion in logs. Scalable — Can automate to delete hundreds of banners. Cross-tenant — Manager in Agency X can attack Manager in Agency Y.

5

Mitigation

Add Banner Ownership Check

Add OA_Permission::enforceAccessToObject('banners', $bannerid) inside the foreach loop, before the delete operation. This validates the user owns each banner being deleted.

Validate Banner-Campaign Relationship

Alternatively, check that $aBanner['campaignid'] matches the $campaignid parameter. This ensures the banner belongs to the campaign the user is operating on.

Audit Other Delete Endpoints

Check all *-delete.php files for similar patterns. If any deletion endpoint validates parent objects but not the target entity, it likely has the same vulnerability.

The Fix

One line. That's all it takes:

php
if (!empty($bannerid)) {
  $ids = explode(',', $bannerid);
  foreach ($ids as $bannerid) {
      // ADD THIS LINE:
      OA_Permission::enforceAccessToObject('banners', $bannerid);

      $doBanners = OA_Dal::factoryDO('banners');
      $doBanners->bannerid = $bannerid;
      if ($doBanners->get($bannerid)) {
          $aBanner = $doBanners->toArray();
      }
      $doBanners->delete();
  }
}

The Bigger Picture

This vulnerability pattern is everywhere. I've seen it in SaaS platforms, content management systems, and enterprise applications. The developers think about "can this user access this area?" but forget to ask "does this user own this specific thing?"

Next time you audit a deletion endpoint, don't just check that authorization exists. Check that it validates the right thing.

Have you audited your deletion endpoints to ensure they validate ownership of the target entity, not just its parents?

VS
Vitaly Simonovich
Security Researcher