Jump to content
  • Checkout
  • Login
  • Get in touch

osCommerce

The e-commerce.

Vulnerability in checkout process


BugReport

Recommended Posts

DESCRIPTION

The checkout process logic relies on redirect headers to handle transaction failure (decline, fraud, stolen card, etc.). This does not secure the store from purpose created bots from submitting arbitrary valid CC numbers from bypassing this check and as a result continues processing as if it were a valid transaction.

 

PROCESS LOGIC FLAW

The checkout_process.php script calls the before_process() method from the respective payment module. However, on failure the only action taken is a header redirect to the checkout_payment.php script.

 

There is no additional security checks performed beyond the initial header redirect. If the client is using a purpose built bot or script (using cURL for example) that does not obey headers this will effectively bypass the accept/decline logic and allow checkout_process.php to continue execution.

 

POSSIBLE FIXES

  • Modify the payment module to return boolean value for the before_process() method and incorporate this into the checkout_process.php script like this:
    // load the before_process function from the payment modules
    $approved = $payment_modules->before_process();
    /**
     * The customer should have redirected already if before_process fails
     * However, some might bypass the simple security check
     * by disabling header codes.  If this is the case let's kill the script
     */
    if ( $approved !== true ){
    	// Try to redirect one more time
    	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('Exception'), 'SSL', true, false));
    	/** 
    	 * Apparently the customer is not going anywhere
    	 * At this point it's safer to destroy the session and kill the script
    	 */
    	session_destroy();
    	exit('Exit -> Exception(3)');
    }


  • Modify the tep_redirect() function to exit() after header call like this:
    ////
    // Redirect to another page or site
     function tep_redirect($url) {
    if ( (strstr($url, "\n") != false) || (strstr($url, "\r") != false) ) { 
      tep_redirect(tep_href_link(FILENAME_DEFAULT, '', 'NONSSL', false));
    }
    
    if ( (ENABLE_SSL == true) && (getenv('HTTPS') == 'on') ) { // We are loading an SSL page
      if (substr($url, 0, strlen(HTTP_SERVER)) == HTTP_SERVER) { // NONSSL url
    	$url = HTTPS_SERVER . substr($url, strlen(HTTP_SERVER)); // Change it to SSL
      }
    }
    
    header('Location: ' . $url);
    /**
    	 * Exit if client does not obey headers
    	 */
    	exit();
     }


INITIAL DISCOVERY AND PROOF OF CONCEPT

Client noticed several orders that were accepted but upon comparison with AuthorizeNET (using AIM method) noticed that they were declined. Since the site was processing several thousand orders daily they were detected only through the random quality assurance checks.

 

Upon closer investigation the flaw was discovered and subsequently replicated using cURL for requests. The vulnerability was verified on 6 other sites (with the cooperation of the respective store owners) using the same script. In addition, 4 were osC 2.2 platforms, 1 was CRE Loaded standard, and the other was CRE Pro.

 

This script is available to the osC / CRE project developers first and then will be released for public consumption. However, the description of the vulnerability should be enough to guide an intermediate level coder to produce the same functionality using whatever programming language they are most comfortable with.

 

SCOPE OF VULNERABILITY

This report affects all stable versions of osCommerce. This report affects all stable versions of CRE Loaded. Other fork projects were not inspected, tested or verified.

 

This report affects all AIM or SIM payment modules and excludes IPN types (such as PayPal.

Link to comment
Share on other sites

It should be noted that this is not a novel discovery. It was uncovered due to client observations...in other words it is already in use by those with malicious intent and is in circulation around various IRC channels.

 

Plug the holes in your installation before one of them get around to your store...

Link to comment
Share on other sites

This really needs to be released as an update to osCommerce so that all site owners can be aware of it and be able to fix it. If only we had a special forum for hack attempts!

 

This may fit in with something that happened with one of our sites, a credit card order that was processed seemingly via Protx Direct but which proved not to exist in Protx records.

 

I'm not just saving the link to this page but saving the whole page (in case it gets lost).

 

Vger

Link to comment
Share on other sites

DESCRIPTION

The checkout process logic relies on redirect headers to handle transaction failure (decline, fraud, stolen card, etc.). This does not secure the store from purpose created bots from submitting arbitrary valid CC numbers from bypassing this check and as a result continues processing as if it were a valid transaction.

 

PROCESS LOGIC FLAW

The checkout_process.php script calls the before_process() method from the respective payment module. However, on failure the only action taken is a header redirect to the checkout_payment.php script.

 

There is no additional security checks performed beyond the initial header redirect. If the client is using a purpose built bot or script (using cURL for example) that does not obey headers this will effectively bypass the accept/decline logic and allow checkout_process.php to continue execution.

 

POSSIBLE FIXES

  • Modify the payment module to return boolean value for the before_process() method and incorporate this into the checkout_process.php script like this:
    // load the before_process function from the payment modules
    $approved = $payment_modules->before_process();
    /**
     * The customer should have redirected already if before_process fails
     * However, some might bypass the simple security check
     * by disabling header codes.  If this is the case let's kill the script
     */
    if ( $approved !== true ){
    	// Try to redirect one more time
    	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('Exception'), 'SSL', true, false));
    	/** 
    	 * Apparently the customer is not going anywhere
    	 * At this point it's safer to destroy the session and kill the script
    	 */
    	session_destroy();
    	exit('Exit -> Exception(3)');
    }


  • Modify the tep_redirect() function to exit() after header call like this:
    ////
    // Redirect to another page or site
     function tep_redirect($url) {
    if ( (strstr($url, "\n") != false) || (strstr($url, "\r") != false) ) { 
      tep_redirect(tep_href_link(FILENAME_DEFAULT, '', 'NONSSL', false));
    }
    
    if ( (ENABLE_SSL == true) && (getenv('HTTPS') == 'on') ) { // We are loading an SSL page
      if (substr($url, 0, strlen(HTTP_SERVER)) == HTTP_SERVER) { // NONSSL url
    	$url = HTTPS_SERVER . substr($url, strlen(HTTP_SERVER)); // Change it to SSL
      }
    }
    
    header('Location: ' . $url);
    /**
    	 * Exit if client does not obey headers
    	 */
    	exit();
     }


INITIAL DISCOVERY AND PROOF OF CONCEPT

Client noticed several orders that were accepted but upon comparison with AuthorizeNET (using AIM method) noticed that they were declined. Since the site was processing several thousand orders daily they were detected only through the random quality assurance checks.

 

Upon closer investigation the flaw was discovered and subsequently replicated using cURL for requests. The vulnerability was verified on 6 other sites (with the cooperation of the respective store owners) using the same script. In addition, 4 were osC 2.2 platforms, 1 was CRE Loaded standard, and the other was CRE Pro.

 

This script is available to the osC / CRE project developers first and then will be released for public consumption. However, the description of the vulnerability should be enough to guide an intermediate level coder to produce the same functionality using whatever programming language they are most comfortable with.

 

SCOPE OF VULNERABILITY

This report affects all stable versions of osCommerce. This report affects all stable versions of CRE Loaded. Other fork projects were not inspected, tested or verified.

 

This report affects all AIM or SIM payment modules and excludes IPN types (such as PayPal.

 

As far as I can see, there is already a tep_exit() call (which closes the session and exists) after the header redirect in the tep_redirect() function.

Treasurer MFC

Link to comment
Share on other sites

I'm not going to argue with Bug Report. If he says these bots ignore the header redirect it's good enough for me.

 

Vger

 

Yep, I second that. I am glad Bug Report was there to bring it to our attention.

Link to comment
Share on other sites

I'm not going to argue with Bug Report. If he says these bots ignore the header redirect it's good enough for me.

You should ;)

 

tep_redirect ends wirh:

 

	header('Location: ' . $url);
tep_exit();
}

wich calls:

 tep_exit() {
  tep_session_close();
  exit();
 }

 

so his second fix is:

Modify the tep_redirect() function to exit() after header call like this

 

Why add something which is already there?

 

So if you want to be sure, just look if you have an original tep_redirect which ends with a call to (tep)_exit...

This bug report should only aplies to a modified or a fork version of osC.

Link to comment
Share on other sites

What he's saying is that the bot ignores the header and so does not get redirected but continues through checkout process. However, I am glad that Team Members are now aware and looking at this. I just hope that minds are open to the possibility that this is an exploit and that the coders who are aware of how hackers do what they do are able to test the scenarios.

 

Vger

Link to comment
Share on other sites

Looking at this again I begin to see what Bug Report is getting at. The default checkout process just says this:

 

// load the before_process function from the payment modules
 $payment_modules->before_process();

 

He is adding validation to that e.g.

 

// load the before_process function from the payment modules
$approved = $payment_modules->before_process();
/**
 * The customer should have redirected already if before_process fails
 * However, some might bypass the simple security check
 * by disabling header codes.  If this is the case let's kill the script
 */
if ( $approved !== true ){
	// Try to redirect one more time
	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('Exception'), 'SSL', true, false));
	/** 
	 * Apparently the customer is not going anywhere
	 * At this point it's safer to destroy the session and kill the script
	 */
	session_destroy();
	exit('Exit -> Exception(3)');
}

 

and then in includes/functions/general.php he's changes the tep_exit call to just exit.

 

Vger

Link to comment
Share on other sites

Is it possible that the store(s) that were tested used either a fork of osCommerce or a payment module that does not use the standard osCommerce functions? I've seen some payment modules that issue header() calls rather than use the tep_redirect function.

 

Henri has already pointed out that tep_redirect() ends with tep_exit(). I looked through my local copies of 2.2MS2 and the security updates from 11/05 and 08/06, all three have the code that Henri quoted. Both of Bobby's fixes involve putting an exit() function after the header() function to make sure the script does not continue - unless I'm missing something that's functionally the same.

 

Bobby, I would be very interested to see instructions for duplicating this. If it is indeed a problem we need to find a fix for it.

Chris Dunning

osCommerce, Contributions Moderator Team

 

Please do not send me PM! I do not read or answer these often. Use the email button instead!

 

I do NOT support contributions other than my own. Emails asking for support on other people's contributions will be ignored. Ask in the forum or contact the contribution author directly.

Link to comment
Share on other sites

Bobby has said that apart from modified versions of osCommerce and forks like CRE that it has also happened to a couple of default osCommerce websites.

 

I'm no expert but what he seems to be saying is that if these bots (using cURL) ignore the tep_redirect then there's no further checking to prevent them from continuing through checkout process to completion.

 

Vger

Link to comment
Share on other sites

Looking at this again I begin to see what Bug Report is getting at. The default checkout process just says this:

 

// load the before_process function from the payment modules
 $payment_modules->before_process();

 

He is adding validation to that e.g.

 

// load the before_process function from the payment modules
$approved = $payment_modules->before_process();
/**
 * The customer should have redirected already if before_process fails
 * However, some might bypass the simple security check
 * by disabling header codes.  If this is the case let's kill the script
 */
if ( $approved !== true ){
	// Try to redirect one more time
	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode('Exception'), 'SSL', true, false));
	/** 
	 * Apparently the customer is not going anywhere
	 * At this point it's safer to destroy the session and kill the script
	 */
	[code]session_destroy();
	exit('Exit -> Exception(3)');
}

Adding that validation would require all payment modules to return a boolean value from before_process() - something that is not currently required. I don't know how many would already support that.

and then in includes/functions/general.php he's changes the tep_exit call to just exit.

 

Vger

The function tep_exit() calls exit():

  function tep_exit() {
  tep_session_close();
  exit();
 }

This is very similar to Bobby's code:

session_destroy();
	exit('Exit -> Exception(3)');

The osCommerce function calls session_close or session_write_close depending on the PHP version, where Bobby calls session_destroy. I see how that's different but I don't know that it matters.

Chris Dunning

osCommerce, Contributions Moderator Team

 

Please do not send me PM! I do not read or answer these often. Use the email button instead!

 

I do NOT support contributions other than my own. Emails asking for support on other people's contributions will be ignored. Ask in the forum or contact the contribution author directly.

Link to comment
Share on other sites

I have now received a reply from Bobby, and this is what he says:

 

The crux of the vulnerability is not really within the osC code but rather the session_close() PHP function. Notice that the only difference between the stock code and revised is the absence of the tep_session_close() function.

 

To address the comments made about the exit code already being present in tep_exit(): the trick is to defeat the tep_session_store() function call so that it returns a silent fail which will stop processing locally (within the function) and return to the global scope (core code in the payment module then hand off to checkout_process.php). Notice that this part of the code is not present in the revised version.

 

Forward direction: I am working with people from http://www.secunia.com/ so that they may validate my findings. It should be noted that this vulnerability does not only affect osC but many other platforms. As a result, osC will be used in the proof of concept and all suggested fixes will focus on the platform...however, the ramifications are much more broad.

 

Thank you Bobby ...very much. osCommerce owes you on this one!

 

Rhea

Link to comment
Share on other sites

I've PM'd with Bobby and got a very similar response to the one that Rhea posted. I had asked him for instructions to duplicate the problem but so far he has not supplied them.

 

My cURL scripting skills are not enough to whip out something to test this, so I tried a different test. My testing setup was on a nearly stock osCommerce store with the latest official patches applied. I have not applied either of Bobby's suggested changes above. My procedure:

-enter the store, add a product to cart

-sign in

-proceed through checkout, selecting authorize.net, providing a bogus card number that will be rejected

Once I saw the checkout_confirmation page, I opened up the code and made two changes:

--includes/functions/general.php, edited the tep_redirect function. I commented out the header() line, leaving the tep_exit line.

--checkout_process.php, immediately after the before_process call I added an echo statement like so:

echo 'defeated the redirect!';

I saved both of those files and attempted to process my order. I was presented with a blank screen (note that my echo statement did not show). Nothing was shown in my admin screen for a new order. The card processor shows a declined charge for my bogus card number.

 

So without the redirect there, the session is closed and the order is not saved. If anyone can provide more information about how to defeat the session_write function and make it fail silently then I'm definitely willing to listen.

 

Perhaps this only applies to certain PHP versions? My testing server runs 5.2.0. I realize that most people are still on 4.x, so if someone else would care to make the same test on their server I would be very interested to hear the results.

Chris Dunning

osCommerce, Contributions Moderator Team

 

Please do not send me PM! I do not read or answer these often. Use the email button instead!

 

I do NOT support contributions other than my own. Emails asking for support on other people's contributions will be ignored. Ask in the forum or contact the contribution author directly.

Link to comment
Share on other sites

  • 2 months later...

Well this is being discussed before. In different forms and there is no way around it. It can be described in a much simpler way. So if you say have the default osc and get to the checkout_confirmation page you could simply change the address to the checkout_process.php and depending on the payment module error handling to get through without further efforts.

 

Ok now say you implement one of the workarounds discussed earlier registering a session for instance, change the forms to be validated before sending them to the gateway etc etc. So you can block this even for the COD module.

 

However the real problem remains with the module validation and the before_process() member function.

 

Looking the checkout_process.php code:

// if the customer is not logged on, redirect them to the login page
 if (!tep_session_is_registered('customer_id')) {
$navigation->set_snapshot(array('mode' => 'SSL', 'page' => FILENAME_CHECKOUT_PAYMENT));
tep_redirect(tep_href_link(FILENAME_LOGIN, '', 'SSL'));
 }

 if (!tep_session_is_registered('sendto')) {
tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, '', 'SSL'));
 }

 if ( (tep_not_null(MODULE_PAYMENT_INSTALLED)) && (!tep_session_is_registered('payment')) ) {
tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, '', 'SSL'));
}

// avoid hack attempts during the checkout procedure by checking the internal cartID
 if (isset($cart->cartID) && tep_session_is_registered('cartID')) {
if ($cart->cartID != $cartID) {
  tep_redirect(tep_href_link(FILENAME_CHECKOUT_SHIPPING, '', 'SSL'));
}
 }

 include(DIR_WS_LANGUAGES . $language . '/' . FILENAME_CHECKOUT_PROCESS);

// load selected payment module
 require(DIR_WS_CLASSES . 'payment.php');
 $payment_modules = new payment($payment);

// load the selected shipping module
 require(DIR_WS_CLASSES . 'shipping.php');
 $shipping_modules = new shipping($shipping);

 require(DIR_WS_CLASSES . 'order.php');
 $order = new order;

// load the before_process function from the payment modules
 $payment_modules->before_process();

 require(DIR_WS_CLASSES . 'order_total.php');
 $order_total_modules = new order_total;

 $order_totals = $order_total_modules->process();

This is the code will be executed upon return from the gateway. There is a number of checks just before the order array is structured and stored in the database.

 

The functions that can do the module error handling are

 

1. $payment_modules->before_process();

 

2. $order_totals = $order_total_modules->process();

 

Ok now let take the default authorizenet module that comes with osc and see what it does in before_process()

	function before_process() {
  global $HTTP_POST_VARS;

  if ($HTTP_POST_VARS['x_response_code'] == '1') return;
  if ($HTTP_POST_VARS['x_response_code'] == '2') {
	tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode(MODULE_PAYMENT_AUTHORIZENET_TEXT_DECLINED_MESSAGE), 'SSL', true, false));
  }
  // Code 3 is an error - but anything else is an error too (IMHO)
  tep_redirect(tep_href_link(FILENAME_CHECKOUT_PAYMENT, 'error_message=' . urlencode(MODULE_PAYMENT_AUTHORIZENET_TEXT_ERROR_MESSAGE), 'SSL', true, false));
}

The check relies on the posted variables. There is no secure handshaking with the gateway, (that really depends on the gateway btw) and therefore this posted variable:

	  if ($HTTP_POST_VARS['x_response_code'] == '1') return;

can be faked using a form submitting directly to checkout_process.php with a filed "x_response_code" and value of "1" to satisfy this requirement. Consequently the order will fall through and recorded in the osc admin.

 

So this should explain the problem. As of the solution posted earlier I doubt it will have an effect on this, because the gateway and the store somehow need to do a secure handshaking that cannot be interrupted or faked.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...