John Jean

Security research, advisories, and occasional (ultra) bike rides.

← all advisories

Amazon Pay SDK: payment notification forgery via unvalidated SigningCertURL

cve
n/a
written
2015-11-03
released
2015-11-03
risk
high
affected
amzpayments module for PrestaShop before the 2015-11-13 release (Amazon Pay PHP SDK, OffAmazonPaymentsNotifications)
type
signature verification bypass (attacker-controlled signing certificate URL)
status
fixed in the amzpayments release of 2015-11-13 (PR #14)

[0] Application

amzpayments is the official “Login and Pay with Amazon” module for PrestaShop, published by PrestaShop. It embeds Amazon’s own PHP SDK (OffAmazonPayments, OffAmazonPaymentsNotifications) to talk to Amazon Pay. Payment events (authorization, capture, refund…) do not come back through the customer’s browser: Amazon pushes them to the shop as notifications, through Amazon SNS, to the module’s ipn.php endpoint. The shop trusts these notifications to update the state of its orders.

The issue was found during a security review of the module in October 2015 and lives in the SDK’s message validator, so it is not specific to PrestaShop: every integration built on the same SDK code validated notifications the same way.

[I] The vulnerability

A) Overview

A notification is accepted as coming from Amazon when its signature verifies against the certificate found at the URL given in the notification itself. Nothing restricts that URL. An attacker signs a notification with a certificate of their own, hosts it anywhere, and the shop accepts the notification as authentic: a forged payment authorization, for an order the attacker never paid.

B) Required data

Nothing. The shop’s ipn.php URL (the module’s, at a fixed path) and the format of the notification, which is documented by Amazon.

C) Explanation

When an SNS notification reaches ipn.php, its body looks like this:

{
  "Type" : "Notification",
  "MessageId" : "da41e39f-ea4d-435a-b922-c6aae3915ebe",
  "TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
  "Subject" : "test",
  "Message" : "{\"Timestamp\":\"Timestamp\",\"ReleaseEnvironment\":\"ReleaseEnvironment\",\"NotificationReferenceId\":\"NotificationReferenceId\",\"NotificationData\":\"XML DATA HERE\"}",
  "Timestamp" : "2012-04-25T21:49:25.719Z",
  "SignatureVersion" : "1",
  "Signature" : "valid-amazon-signature",
  "SigningCertURL" : "http://amazon.server/cert.pem"
}

(format documented at https://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.html)

Two fields matter: Signature and SigningCertURL. The signature is computed with openssl_sign() over the fields Timestamp, Message, Subject… with the private key of the certificate published at SigningCertURL. The attacker does not know Amazon’s private key.

ipn.php validates the message (ipn.php:35) by handing it to the SDK’s validator, which verifies the signature of the other fields with the certificate at SigningCertURL (SnsMessageValidator.php:133).

There is no control on SigningCertURL. The only check on the certificate is that its common name is sns.amazon.com (OpenSslVerifySignature.php:138), which is not a security check: anyone can put that name in a self-signed certificate.

D) Exploitation

  1. Generate a self-signed PEM certificate with the common name sns.amazon.com.
  2. Create a notification request.
  3. Sign it with the private key of the certificate generated in 1.
  4. Host the certificate somewhere on the internet (http://example.com/attacker.pem).
  5. Send the notification to ipn.php:
{
  "Type" : "Notification",
  "MessageId" : "hacker-notification",
  "TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
  "Subject" : "test",
  "Message" : "{\"Timestamp\":\"Timestamp\",\"ReleaseEnvironment\":\"ReleaseEnvironment\",\"NotificationReferenceId\":\"NotificationReferenceId\",\"NotificationData\":\"XML DATA HERE\"}",
  "Timestamp" : "2012-04-25T21:49:25.719Z",
  "SignatureVersion" : "1",
  "Signature" : "valid-hacker-signature",
  "SigningCertURL" : "http://example.com/hacker.pem"
}

The validator downloads the attacker’s certificate, checks its common name, verifies the signature with it, and accepts the notification.

E) Consequences

Anyone on the internet can tell a shop that an order was authorized or captured. With a NotificationData payload matching an order the attacker placed, the shop marks it as paid without any money changing hands. No account, no secret, no interaction with Amazon.

The flaw is in the SDK, so every merchant using the module was exposed, and so was any other integration built on the same validator code.

[II] Mitigation

1. Restrict SigningCertURL to Amazon’s SNS endpoints before fetching anything. This is what the fix does (PR #14, commit d49edd9): the validator now rejects any certificate URL whose host does not match sns.<region>.amazonaws.com (or .amazonaws.com.cn), with the pattern

^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$

The current Amazon Pay PHP SDK (IpnHandler.php) carries the same check.

2. Do not treat a certificate’s common name as a trust decision. A name in a certificate proves nothing unless the certificate chains to a trusted authority or comes from a trusted location.

[III] Timeline

2015/10/28: Security review of the amzpayments module, issue opened with PrestaShop
2015/11/03: The flaw in the SDK's validator is identified and reported in the same issue
2015/11/09: Fix proposed by the module's author (PR #14)
2015/11/13: Fix reviewed, merged and released

[IV] Author

John JEAN is a French security researcher, CTO at Wargan (https://www.wargan.com).
Follow me on X @johnjean

[V] PGP

My current PGP key is 0xDC3CB82BF0967AF0, you can get it on the Work with me page.