[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
- Generate a self-signed PEM certificate with the common name
sns.amazon.com. - Create a notification request.
- Sign it with the private key of the certificate generated in 1.
- Host the certificate somewhere on the internet
(
http://example.com/attacker.pem). - 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.