Demonstration of vulnerabilities in Liqpay

    Since Privatbank did not respond to messages describing the vulnerabilities of its Liqpay payment system, I, having waited several months, will substantiate the theses of my previous article with real examples. In addition, I believe that a kick will be useful - I will advise the PB below how to put out bugs to some extent. Perhaps these vulnerabilities are already being exploited, and their nature is such that end users suffer. Forcing the PB to act, we will help them out (users).

    As I noted, most often errors are made in the logic of generating a signature for key data. The problem is that these errors are conceptual, not implementation.

    Vulnerability number 1


    The bottom line: when paying for a store’s product, an attacker can change the form data sent by his browser on the Liqpay api and pay for a different product than was selected in the store.

    The problem is in the formation of the signature: the signed data is “glued” without a separator, in particular, the order_id and type parameter. Let's say order_id = '1234', and type = 'buy'. The attacker modifies order_id = '123' and type = '4buy', sends it to the liqpay server. In this case, signature validation will be successful since the line for its formation has not changed. You can safely repeat these steps in the Chrome or Firefox browser by editing the form on the page - remove the last character from the order and set it first in the type parameter.

    Just in case, I'm a little chew on what happened,

    Upon purchase, the store creates a unique order number (order_id), forms a form, signs it and sends the customer with this data to Liqpay. When Liqpay makes a successful withdrawal of money from the buyer, he informs the store that such and such order_id has been paid. The problem is that Liqpay will inform you about the payment of a completely different order, which has a different amount for payment. It is enough for an attacker to create unpaid orders until one order_id has a fragment of another order_id for one order. Since often order_id are not formed randomly, this is possible to one degree or another.

    A quick solution for Privatbank: just validate the type parameter, which is not happening now. As I noted, this is a special case that does not solve the problem conceptually.

    Vulnerability number 2


    The bottom line: a form signed by the store intended for payment can be sent manually to the callback url and it will be accepted by the store as the signature is formed in accordance with all the rules. It is only necessary to do a little manipulation:

    rename the result_url field to transaction_id,
    rename the server_url
    field to sender_phone and add the status field with an empty value.

    This package will be accepted by the store!

    This is how the signature on the Liqpay package is considered:

    private_key . amount . currency . public_key . order_id . type . description . result_url . server_url
    


    And here is how the signature in the OT Liqpay package is considered:

    private_key . amount . currency . public_key . order_id . type . description . status . transaction_id . sender_phone
    


    It can be seen that the packages differ only in the last three parameters. We just changed the names of the fields and fooled the store, making him think that this package is from Liqpay, because the signature will match.

    In order for the store to think that this is a successful payment, it takes a little more action and one condition:

    in the description of the product should be a fragment of "success".


    Surely, in stores using Liqpay, there is a product with this fragment. Well, for example, we’ll come up with the title of the book “My successful story”. In the form for payment, we change:

    • cut off a piece of text from the description field at the end of the value so that the “success” fragment and the subsequent text do not fall into it: description = 'My'
    • field status = 'success'
    • field transaction_id = concatenation of the text after "success" and result_url: transaction_id = 'ful storyhttps: // blablabla'
    • rename the server_url field to sender_phone


    The bottom line: the signature does not change! The store accepts the package. Field status = "success" - payment is successful. In the transaction_id field, the left text is accepted, since the store does not have to know how transaction id are formed in Liqpay (ignore it in 99% of cases), sender_phone is the left text (it is unlikely that the package will sniff if something is wrong on the phone, the maximum is pledged).

    Bottom line: you can “buy” any product for free as much as you wish if the word “success” was in the description. Url callback is available in an explicit form, so there will be no problem finding out where to send the packet.

    If you do not understand the meaning of manipulation, then briefly: suppose there are parameters A = AAA, B = BBB, B = BBB. According to Liqpay protocol, the store software concatenates all the parameters in a certain order in one line AAABBBVVV and receives the signature XXX. We can change the names and values ​​of the parameters, let's say this: G = AA, B = ABBBV, B = BB. As a result, the concatenated string is the same (AAABBBVVV), and the parameters and their values ​​are completely different.

    A quick solution for Privatbank: block the processing of purchases with the fragment “success” in the description. Again, this does not solve the problem conceptually.

    I ask you not to kick the PB much, because other payment systems have even more bugs, but so far I have no time for them.

    Also popular now: