Audit #15 Benign
Show full summary
Historical PHP Object Injection chain in Admitad integration — gated since v6.0.0 (2023-08-21), endpoint dead.
Two compounding patterns in application/libs/admitad/AdmitadProducts.php + application/libs/RestClient.php formed a remotely-exploitable PHP deserialization vulnerability for ~7 years (2016-09 → 2023-08). The chain remains structurally present in trunk but is unreachable in practice: the only live entry points throw a deprecation Exception before any HTTP traffic is sent, and the operator endpoint they targeted has been dark since at least early 2024.
The chain (still in source as of v11.0.0)
1. Hardcoded HTTP-only endpoint to a Russian VPS. AdmitadProducts.php:22 declares const API_URI_BASE = 'http://185.58.206.88/wp'. The IP belongs to NesterTelecom LLC (RU). No DNS, no certificate validation, no transport encryption.
2. Error-suppressed unserialize() of remote response. AdmitadProducts.php:38 sets responseType='php'. RestClient.php:240 then calls @unserialize($response) directly on the raw HTTP body with no class allowlist, no integrity check, and @ to suppress deserialize failures. Textbook PHP Object Injection sink — any reachable PHP gadget chain (Composer, Symfony, Magento, etc.) becomes RCE on the WordPress site.
The responseType='php' choice is unique to the Admitad integration. Every other affiliate-network module in Content Egg (Amazon, Aliexpress, AWIN, etc.) uses JSON.
Why it's no longer exploitable
Gating throws added in v6.0.0 (2023-08-21, r2956182)
AdmitadProductsModule.php — the only production caller of AdmitadProducts — has both entry points gated:
public function doRequest($keyword, $query_params = array(), $is_autoupdate = false)
{
throw new \Exception('This module is deprecated. Admitad API was closed.');
// ...dead code below...
}
public function doRequestItems(array $items)
{
throw new \Exception('This module is deprecated. Admitad API was closed.');
// ...dead code below...
}Plus isDeprecated() => true. The throw is the first executable line of each method, so new AdmitadProducts() never instantiates and the Russian IP is never contacted.
Endpoint verified dead (probed 2026-04-28)
| Probe | Result | |---|---| | HTTP HEAD /wp/, /wp/index.php, /wp/up.php | Connection timeout | | TCP :80 :443 :8080 :8004 | Closed/filtered | | ICMP ping | 100% packet loss | | Reverse DNS | NXDOMAIN |
Nothing is listening on 185.58.206.88 from any external vantage point.
RIPE registration timeline
The 185.58.206.0/24 inetnum was re-registered to NesterTelecom LLC on 2024-01-18 (~5 months after keywordrush added the deprecation throws). Consistent with: Admitad public API closed → keywordrush decommissioned their proxy VPS → IP allocation refreshed within NesterTelecom's pool. No evidence of attacker takeover during a "dangling-resource" window — the throw was already in place when the IP went quiet.
Git-level forensics (svn blame + log)
| Date | Revision | Event | |---|---|---| | 2015-08-28 | r1233299 (v1.6.0) | Plugin initial release. RestClient.php ships with @unserialize($response) for case 'php' / case 'php_serial'. | | 2016-09-16 | r1497064 | AdmitadProducts.php added. Hardcodes http://185.58.206.88/wp + responseType='php' from day one. | | 2016-09 → 2023-08 | various | Live exposure window, ~7 years. Cosmetic refactors only; chain unchanged. | | 2023-08-21 | r2956182 (v6.0.0) | Deprecation throw added to both doRequest() and doRequestItems() in AdmitadProductsModule. Chain becomes unreachable. | | 2024-01-18 | (RIPE) | /24 inetnum record refreshed under NesterTelecom — endpoint already not in use by content-egg. | | 2023–2026 | various | Cosmetic changes only; throws remain. As of v11.0.0 (2026-04-24), state is unchanged. |
Threat model retrospective
During the 2016–2023 live window, anyone in any of these positions could inject PHP gadget chains and gain RCE on every Admitad-enabled install:
- The keywordrush operator at the Russian VPS (single point of trust)
- Any future buyer of the IP block, the VPS account, or the keywordrush domain
- Any network observer in the path between a WordPress install and Russia (HTTP MITM was trivial — no cert pinning to bypass)
- Any attacker who compromised the keywordrush server (single VPS, no CDN/Cloudflare frontend)
No public reports of in-the-wild exploitation during this window. The vulnerability appears to have been latent rather than weaponized.
Author intent assessment
keywordrush (real brand, keywordrush.com) has been the sole committer since 2015-08-28 — same identity through the live-exposure window and the deprecation. The chain is consistent with bad-by-modern-standards 2015-era PHP practices (serialized PHP wire format was once common, Russian VPS hosting was cheap, HTTPS was not universal) maintained by inertia. The deprecation throws were added when the upstream service closed, not in response to a security disclosure — the framing in the throw message ("Admitad API was closed") suggests internal knowledge of the API change rather than a security retrofit. No malicious intent indicated; architectural debt rather than backdoor.
Verdict
Benign — historical exposure (now closed). The chain was a real latent RCE for ~7 years in shipping code on ~10k installs; the live-exposure window has been closed for ~2.5 years through a combination of code-level gating and (independently) endpoint death. No current risk to site owners running content-egg.
Recommendations
For the plugin author (keywordrush)
- Delete the dead code rather than leaving it gated. The chain code itself (
AdmitadProducts::__construct(), thecase 'php'branch inRestClient::_decodeResponse(), theresponseType='php'config) should be removed in a future release. Gated dead code carries regression risk if a future refactor accidentally removes the throw. - If
php_serialdecoding needs to remain available for any other module, change@unserialize($response)tounserialize($response, ['allowed_classes' => false])(PHP 7+). - Audit other affiliate-network modules for the same shape (HTTP-only +
responseType='php'+@unserialize). All visible JSON modules look fine, but a full sweep is cheap insurance.
For the WP Beacon catalog
- Retain the IOC pattern
php_unserialize_after_remote_call_with_php_response_type— it remains a useful detection signal for future plugins shipping the same shape without a deprecation gate. Would have caught this chain in 2016, ~7 years before the gating throws.
For site owners
- No action required. The chain is not exploitable on current versions.
Audit retained for the record. No action required.
Plugins under the same committer's SVN access
keywordrush holds push access to 1 plugin totalling 10k+ active installs.
IOCs extracted (3)
| Kind | Value | Confidence |
|---|---|---|
| code_pattern | php_unserialize_after_remote_call_with_php_response_type |
high |
| ip | 185.58.206.88 |
high |
| url | http://185.58.206.88/wp |
medium |
Plugin version history
Every release on wp.org for this plugin, color-coded by relationship to the incident. The compromise window shows where the wp.org Plugin Review Team deleted the malicious tags from SVN — those versions cannot be re-downloaded today.
-
Clean 54 earlier releases before the incident
-
1.6.0 -
1.6.1 -
1.7.1 -
1.8.0 -
1.9.0 -
2.0.1 -
2.1.0 -
2.2.0 -
2.3.0 -
2.4.0 -
2.4.2 -
2.5.1 -
2.6.0 -
2.6.1 -
2.7.0 -
2.8.0 -
2.8.1 -
2.9.0 -
2.9.1 -
3.0.0 -
3.0.1 -
3.0.2 -
3.0.5 -
3.2.0 -
3.2.1 -
3.4.1 -
3.5.0 -
3.5.1 -
3.6.2 -
3.7.0 -
3.9.0 -
3.9.1 -
4.0.3 -
4.2.1 -
4.3.0 -
4.4.3 -
4.5.0 -
4.8.0 -
4.9.8 -
5.0.0 -
5.1.0 -
5.2.0 -
5.2.1 -
5.3.0 -
5.4.0 -
5.5.0 -
6.0.0 -
7.0.0 -
8.0.0 -
9.0.0 -
9.0.1 -
9.0.2 -
10.0.0 -
10.1.0
-
-
11.0.0Last clean Last clean release before incident
Historical PHP Object Injection chain in Admitad integration — gated since v6.0.0 (2023-08-21), endpoint dead.
Two compounding patterns in application/libs/admitad/AdmitadProducts.php + application/libs/RestClient.php formed a remotely-exploitable PHP deserialization vulnerability for ~7 years (2016-09 → 2023-08). The chain remains structurally present in trunk but is unreachable in practice: the only live entry points throw a deprecation Exception before any HTTP traffic is sent, and the operator endpoint they targeted has been dark since at least early 2024.
The chain (still in source as of v11.0.0)
1. Hardcoded HTTP-only endpoint to a Russian VPS. AdmitadProducts.php:22 declares const API_URI_BASE = 'http://185.58.206.88/wp'. The IP belongs to NesterTelecom LLC (RU). No DNS, no certificate validation, no transport encryption.
2. Error-suppressed unserialize() of remote response. AdmitadProducts.php:38 sets responseType='php'. RestClient.php:240 then calls @unserialize($response) directly on the raw HTTP body with no class allowlist, no integrity check, and @ to suppress deserialize failures. Textbook PHP Object Injection sink — any reachable PHP gadget chain (Composer, Symfony, Magento, etc.) becomes RCE on the WordPress site.
The responseType='php' choice is unique to the Admitad integration. Every other affiliate-network module in Content Egg (Amazon, Aliexpress, AWIN, etc.) uses JSON.
Why it's no longer exploitable
Gating throws added in v6.0.0 (2023-08-21, r2956182)
AdmitadProductsModule.php — the only production caller of AdmitadProducts — has both entry points gated:
public function doRequest($keyword, $query_params = array(), $is_autoupdate = false)
{
throw new \Exception('This module is deprecated. Admitad API was closed.');
// ...dead code below...
}
public function doRequestItems(array $items)
{
throw new \Exception('This module is deprecated. Admitad API was closed.');
// ...dead code below...
}Plus isDeprecated() => true. The throw is the first executable line of each method, so new AdmitadProducts() never instantiates and the Russian IP is never contacted.
Endpoint verified dead (probed 2026-04-28)
| Probe | Result | |---|---| | HTTP HEAD /wp/, /wp/index.php, /wp/up.php | Connection timeout | | TCP :80 :443 :8080 :8004 | Closed/filtered | | ICMP ping | 100% packet loss | | Reverse DNS | NXDOMAIN |
Nothing is listening on 185.58.206.88 from any external vantage point.
RIPE registration timeline
The 185.58.206.0/24 inetnum was re-registered to NesterTelecom LLC on 2024-01-18 (~5 months after keywordrush added the deprecation throws). Consistent with: Admitad public API closed → keywordrush decommissioned their proxy VPS → IP allocation refreshed within NesterTelecom's pool. No evidence of attacker takeover during a "dangling-resource" window — the throw was already in place when the IP went quiet.
Git-level forensics (svn blame + log)
| Date | Revision | Event | |---|---|---| | 2015-08-28 | r1233299 (v1.6.0) | Plugin initial release. RestClient.php ships with @unserialize($response) for case 'php' / case 'php_serial'. | | 2016-09-16 | r1497064 | AdmitadProducts.php added. Hardcodes http://185.58.206.88/wp + responseType='php' from day one. | | 2016-09 → 2023-08 | various | Live exposure window, ~7 years. Cosmetic refactors only; chain unchanged. | | 2023-08-21 | r2956182 (v6.0.0) | Deprecation throw added to both doRequest() and doRequestItems() in AdmitadProductsModule. Chain becomes unreachable. | | 2024-01-18 | (RIPE) | /24 inetnum record refreshed under NesterTelecom — endpoint already not in use by content-egg. | | 2023–2026 | various | Cosmetic changes only; throws remain. As of v11.0.0 (2026-04-24), state is unchanged. |
Threat model retrospective
During the 2016–2023 live window, anyone in any of these positions could inject PHP gadget chains and gain RCE on every Admitad-enabled install:
- The keywordrush operator at the Russian VPS (single point of trust)
- Any future buyer of the IP block, the VPS account, or the keywordrush domain
- Any network observer in the path between a WordPress install and Russia (HTTP MITM was trivial — no cert pinning to bypass)
- Any attacker who compromised the keywordrush server (single VPS, no CDN/Cloudflare frontend)
No public reports of in-the-wild exploitation during this window. The vulnerability appears to have been latent rather than weaponized.
Author intent assessment
keywordrush (real brand, keywordrush.com) has been the sole committer since 2015-08-28 — same identity through the live-exposure window and the deprecation. The chain is consistent with bad-by-modern-standards 2015-era PHP practices (serialized PHP wire format was once common, Russian VPS hosting was cheap, HTTPS was not universal) maintained by inertia. The deprecation throws were added when the upstream service closed, not in response to a security disclosure — the framing in the throw message ("Admitad API was closed") suggests internal knowledge of the API change rather than a security retrofit. No malicious intent indicated; architectural debt rather than backdoor.
Verdict
Benign — historical exposure (now closed). The chain was a real latent RCE for ~7 years in shipping code on ~10k installs; the live-exposure window has been closed for ~2.5 years through a combination of code-level gating and (independently) endpoint death. No current risk to site owners running content-egg.
Recommendations
For the plugin author (keywordrush)
- Delete the dead code rather than leaving it gated. The chain code itself (
AdmitadProducts::__construct(), thecase 'php'branch inRestClient::_decodeResponse(), theresponseType='php'config) should be removed in a future release. Gated dead code carries regression risk if a future refactor accidentally removes the throw. - If
php_serialdecoding needs to remain available for any other module, change@unserialize($response)tounserialize($response, ['allowed_classes' => false])(PHP 7+). - Audit other affiliate-network modules for the same shape (HTTP-only +
responseType='php'+@unserialize). All visible JSON modules look fine, but a full sweep is cheap insurance.
For the WP Beacon catalog
- Retain the IOC pattern
php_unserialize_after_remote_call_with_php_response_type— it remains a useful detection signal for future plugins shipping the same shape without a deprecation gate. Would have caught this chain in 2016, ~7 years before the gating throws.
For site owners
- No action required. The chain is not exploitable on current versions.