- On crée la commande, les identifiants
wsLogin
etwsPassword
sont forcément spécifiés sinon la commande ne pourrait pas être créee.
> POST
https://test-ws.hipay.com//soap/payment-v2/generate
:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://test-ws.hipay.com/soap/payment-v2">
<SOAP-ENV:Body>
<ns1:generate>
<parameters>
<wsLogin>8a413ed7b226cbf7dc068a730ca96a3d</wsLogin>
<wsPassword>********************************</wsPassword>
<websiteId>573429</websiteId>
<categoryId>621</categoryId>
<amount>4.99</amount>
<currency>EUR</currency>
<rating>ALL</rating>
<locale>en_US</locale>
<customerIpAddress>127.0.0.1</customerIpAddress>
<description>Order description</description>
<executionDate>2019-06-18T09:27:38</executionDate>
<manualCapture>0</manualCapture>
<urlCallback>http://00ead46c.ngrok.io</urlCallback>
</parameters>
</ns1:generate>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
< Response:
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="https://test-ws.hipay.com/soap/payment-v2">
<SOAP-ENV:Body>
<ns1:generateResponse>
<generateResult>
<redirectUrl>https://test-payment.hipay.com/index/ws/id/a7821290e89e3e31aa20fbb61134b4d4</redirectUrl>
<code>0</code>
<description>N/A</description>
</generateResult>
</ns1:generateResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
- On va sur la page de paiement indiquée par
redirectUrl
et on effectue le paiement.
Puis on attend que la notification soit envoyée au serveur...
< Notification (authorization):
<?xml version="1.0" encoding="UTF-8"?>
<mapi>
<mapiversion>1.0</mapiversion>
<md5content>4a533f46b102bc190a61dc84f55d6d7e</md5content>
<result><operation>authorization</operation><status>ok</status><date>2019-06-18</date><time>09:24:38 UTC+0000</time><origAmount>4.99</origAmount><origCurrency>EUR</origCurrency><idForMerchant/><emailClient>customer@example.com</emailClient><idClient>762443</idClient><cardCountry>US</cardCountry><ipCountry>FR</ipCountry><merchantDatas/><transid>5D08ADCC0E2C1236</transid><is3ds>No</is3ds><paymentMethod>VISA</paymentMethod><customerCountry>FR</customerCountry><returnCode/><returnDescriptionShort/><returnDescriptionLong/></result>
</mapi>
- On vérifie la signature de la notification avec
md5content
comme indiqué ici:
signature = md5('<result>...</result>' + wsPassword);
Dans mon cas le résultat est: 'a914f4de5519100e2fdbc3c3ce01b305'
Donc signature
est différent de md5content
=> la notification est invalide!
Par contre, si on essaye sans concaténer wsPassword:
signature = md5('<result>...</result>');
On obtiens: '4a533f46b102bc190a61dc84f55d6d7e'
Cette fois, signature
est égal à md5content
.
Conclusion :
md5content
n'est qu'un hash md5 de '<result>...</result>'
et ne fournis donc
aucune sécurité concernant l'authenticité de la notification.
Par conséquent, n'importe qui trouvant l'endpoint sur lequel sont envoyés les
notifications peut crafter ses propres notifications pour potentiellement
abuser du système de paiement.
Mais même si le système de signature fonctionnait il semble comporter quelques points faibles:
- L'utilisation de l'algorithme md5 qui n'est plus sûr pour de la cryptographie (l'ancienneté de l'API explique surement son utilisation).
- L'utilisation d'une concaténation plutôt que d'un HMAC (voir https://crypto.stackexchange.com/a/15074).
- Les erreurs d'implémentation qui arrivent facilement lorsque l'on récupère le
contenu de
'<result>...</result>'
:
Par exemple, la librairiehipay-wallet-sdk-magento
utilise strrpos afin de récupérer la string du result (https://github.com/hipay/hipay-wallet-sdk-magento/blob/cb62049efe447c5ad37ce4730e686a2e536f0581/src/app/code/local/HimediaPayments/Hipay/controllers/MapiController.php#L583).
Un hacker qui parviendrait à récupérer une seule notification valide pourrait alors en crafter des fausses:
(dans cet exemple, la librairie vérifiera le<?xml version="1.0" encoding="UTF-8"?> <mapi> <mapiversion>1.0</mapiversion> <md5content>ORIGINAL_MD5CONTENT</md5content> <result>HACKER_TAMPERED_RESULT</result> <!--<result>ORIGINAL_RESULT</result>--> </mapi>
'<result>ORIGINAL_RESULT</result>'
(qui est commenté) puisqu'elle utilisestrrpos
qui récupère la dernière occurrence! Par contre, le parser XML décodera le résultat altéré par le hacker!)
Une bonne solution serait d'implémenter une signature sur l'ensemble de la réponse avec un algorithme plus sûr:
signature = HMAC-SHA256(wsPassword, NOTIFICATION_BODY)
Puis d'envoyer cette signature dans un header HTTP (ex: X-Signature-Sha256
).
En bonus, de cette façon la version actuelle resterait complètement retro
compatible.