ushainformatique / whatacart

E-Commerce Platform on Yii2
http://whatacart.com
GNU General Public License v3.0
76 stars 43 forks source link

[SECURITY] user can change the final price like he wants #28

Closed francispage closed 6 years ago

francispage commented 6 years ago

One of the first principles of a secure website is to never trust user inputs. In the checkout process there are hidden inputs that can be changed by the user and whatacart uses those fields to calculate the final cost and send that amount to paypal.

How to reproduce

  1. go to http://whatacart.com/liveshop/site/index
  2. add all the extensions
  3. checkout until you see "Confirm Order"
  4. Right click -> inspect and go to console
  5. Run this $('input[name^="amount_"]').each((t,v)=>$(v).val(0.01))
  6. Confirm order

You will only pay couple of cents event if the value is 100$. Worst is that you might not notice it at first because in the admin panel is says that you paid 100$ event if the payment by paypal was 0.06$.

Nobody should use your platform until this is fixed.

Regards,

/Francis

francispage commented 6 years ago

It's kind of the same thing that the other issue I created.

Recalculate everything in the backend at every step.

ushainformatique commented 6 years ago

Thanks for letting us know. This is fixed. You can check on live site and let me know if you are able to do it again. Please check in release 2.0.2.

No issue with the backend. If frontend works perfect, backend would reflect it by picking the data there in database.

I am really thankful to you for letting us know about this one.

francispage commented 6 years ago

Hello, It's not a proper fix. Disable javascript or stop the loading of the page and you just transfered the problem to the another page.

I suggest you implement the Paypal API properly with the SDK that is available in composer (the official one). You create your payment details in the backend and send the request to Paypal to the backend then you redirect the user to paypal directly without going throught the form.

https://github.com/paypal/PayPal-PHP-SDK/tree/master/sample

Something similar to this:

<?php

/**
 * @copyright Copyright (C) 2016 Usha Singhai Neo Informatique Pvt. Ltd
 * @license https://www.gnu.org/licenses/gpl.html
 */

namespace common\modules\payment\controllers\paypal_standard;

use common\modules\payment\business\paypal_standard\Manager;
use common\modules\shipping\traits\ShippingTrait;
use common\utils\ApplicationUtil;
use Exception;
use PayPal\Api\Amount;
use PayPal\Api\Details;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Rest\ApiContext;
use products\behaviors\PriceBehavior;
use usni\library\web\Controller;
use usni\UsniAdaptor;

/**
 * ConfirmController for the cash on delivery payment method
 *
 * @package common\modules\payment\controllers\paypal_standard
 */
class ConfirmController extends Controller {

    use ShippingTrait;

    public function behaviors() {
        return [
            PriceBehavior::className()
        ];
    }

    /**
     * Index action for confirm
     * @return string
     */
    public function actionIndex() {

// Replace these values by entering your own ClientId and Secret by visiting https://developer.paypal.com/developer/applications/
        $clientId = 'myclientid';
        $clientSecret = 'mysecret';

        /**
         * All default curl options are stored in the array inside the PayPalHttpConfig class. To make changes to those settings
         * for your specific environments, feel free to add them using the code shown below
         * Uncomment below line to override any default curl options.
         */
// \PayPal\Core\PayPalHttpConfig::$defaultCurlOptions[CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1_2;

        /** @var \Paypal\Rest\ApiContext $apiContext */
        $apiContext = $this->getApiContext($clientId, $clientSecret);

        $config = Manager::getInstance()->getPaypalConfig();
        $cart = ApplicationUtil::getCart();

// ### Payer
// A resource representing a Payer that funds a payment
// For paypal account payments, set payment method
// to 'paypal'.
        $payer = new Payer();
        $payer->setPaymentMethod("paypal");

// ### Itemized information
// (Optional) Lets you specify item wise
// information
        $itemList = new ItemList();
        foreach ($cart->itemsList as $item) {
            $name = $item->name;
            if ($item->displayedOptions != null) {
                $name .= '<br/>' . $item->displayedOptions;
            }
            $priceByCurrency = $this->getPriceByCurrency($item->price, UsniAdaptor::app()->currencyManager->selectedCurrency);

            $item1 = new Item();
            $item1->setName($name)
                    ->setCurrency(UsniAdaptor::app()->currencyManager->selectedCurrency)
                    ->setQuantity($item->qty)
                    ->setSku($item->productId) // Similar to `item_number` in Classic API
                    ->setPrice($priceByCurrency);
            $itemList->addItem($item1);
        }

// ### Additional payment details
// Use this optional field to set additional
// payment information such as tax, shipping
// charges etc.
        $model = ApplicationUtil::getCheckoutFormModel('deliveryOptionsEditForm');
        $shippingPrice = $this->getCalculatedPriceByType($model->shipping, $cart);
        $details = new Details();
        $details->setShipping($shippingPrice);

// ### Amount
// Lets you specify a payment amount.
// You can also specify additional details
// such as shipping, tax.
        $amount = new Amount();
        $amount->setCurrency("USD")
                ->setTotal($cart->getAmount());

// ### Transaction
// A transaction defines the contract of a
// payment - what is the payment for and who
// is fulfilling it. 
        $transaction = new Transaction();
        $transaction->setAmount($amount)
                ->setItemList($itemList)
                ->setDescription("Payment")
                ->setInvoiceNumber(uniqid());

// ### Redirect urls
// Set the urls that the buyer must be redirected to after 
// payment approval/ cancellation.
        $redirectUrls = new RedirectUrls();
        $redirectUrls->setReturnUrl($config['returnUrl'] . '?q=success')
                ->setCancelUrl($config['cancelUrl'] . '?q=cancel');

// ### Payment
// A Payment Resource; create one using
// the above types and intent set to 'sale'
        $payment = new Payment();
        $payment->setIntent("sale")
                ->setPayer($payer)
                ->setRedirectUrls($redirectUrls)
                ->setTransactions([$transaction]);

// ### Create Payment
// Create a payment by calling the 'create' method
// passing it a valid apiContext.
// (See bootstrap.php for more on `ApiContext`)
// The return object contains the state and the
// url to which the buyer must be redirected to
// for payment approval
        try {
            $payment->create($apiContext);
        } catch (Exception $ex) {
            echo print_r($ex);exit;
        }

// ### Get redirect url
// The API response provides the url that you must redirect
// the buyer to. Retrieve the url from the $payment->getApprovalLink()
// method
        $approvalUrl = $payment->getApprovalLink();
//echo $approvalUrl;exit;
        return $this->redirect($approvalUrl);
    }

    /**
     * 
     * @param type $clientId
     * @param type $clientSecret
     * @return ApiContext
     */
    protected function getApiContext($clientId, $clientSecret) {

        // #### SDK configuration
        // Register the sdk_config.ini file in current directory
        // as the configuration source.
        /*
          if(!defined("PP_CONFIG_PATH")) {
          define("PP_CONFIG_PATH", __DIR__);
          }
         */

        // ### Api context
        // Use an ApiContext object to authenticate
        // API calls. The clientId and clientSecret for the
        // OAuthTokenCredential class can be retrieved from
        // developer.paypal.com

        $apiContext = new ApiContext(
                new OAuthTokenCredential(
                $clientId, $clientSecret
                )
        );

        // Comment this line out and uncomment the PP_CONFIG_PATH
        // 'define' block if you want to use static file
        // based configuration

        $apiContext->setConfig(
                array(
                    'mode' => 'sandbox',
                    'log.LogEnabled' => true,
                    'log.FileName' => '/tmp/PayPal.log',
                    'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
                    'cache.enabled' => true,
                //'cache.FileName' => '/PaypalCache' // for determining paypal cache directory
                // 'http.CURLOPT_CONNECTTIMEOUT' => 30
                // 'http.headers.PayPal-Partner-Attribution-Id' => '123123123'
                //'log.AdapterFactory' => '\PayPal\Log\DefaultLogFactory' // Factory class implementing \PayPal\Log\PayPalLogFactory
                )
        );

        // Partner Attribution Id
        // Use this header if you are a PayPal partner. Specify a unique BN Code to receive revenue attribution.
        // To learn more or to request a BN Code, contact your Partner Manager or visit the PayPal Partner Portal
        // $apiContext->addRequestHeader('PayPal-Partner-Attribution-Id', '123123123');

        return $apiContext;
    }

}
ushainformatique commented 6 years ago

Thanks for it. Let us check it out.