The elusive bug

(Note: This is something that happened earlier this year. A recent discussion reminded me that I should write about it and here it is)

Symptoms

The bug description wasn’t very helpful. The users couldn’t scroll on their Macs on this one website. Only on a few Macs, not all of them (for one, no one could reproduce this locally). Other operating systems (including iOS) worked fine for them. Other websites scrolled just fine. The browser they were using didn’t matter – Firefox, Chrome, and Safari – if one browser didn’t work, others had the same issue. The reports had been pouring in for almost a year from the in-page “Contact us” button on the website but without being able to replicate the issue, it was impossible to fix. It started off with a single user complaining about not able to scroll and by the time it was fixed a report was pouring in almost every week about how another user wasn’t able to scroll.

I like unsolvable mysteries and I was very interested in this one. I took it up and this is how it turned out (the whole thing happened over the course of a week).

Probable causes

There wasn’t much to go on so I started off with two possible reasons:

The OS

I didn’t expect that the OS could be at fault, but when I searched for scroll issues on mac I came up with two bugs – reported on Chromium and Firefox. It was an Apple bug and related to scrolling so it fit the problem description. The part that didn’t fit was that the issue was triggered by a Mac scroll gesture but maybe we had hit upon another related bug. While I was certain this wasn’t like one of those Internet Explorer’s CSS bugs, there could be a CSS property which relies on the OS which in turn was rendered wrong by the OS rendering engine. If the OS was actually at fault here (according to my notes it seemed unlikely to me at that time because it would definitely be more widespread) there wasn’t much to do except try to reproduce it.

Browser plugin

A badly written browser plugin could just be the culprit. The bug reporting tool on the website included the list of plugins installed and the ones installed on every bug report were these two – Shockwave Flash and WebKit built-in PDF. Not helpful, because these are default plugins and always installed. The next candidates in the list were installed on 60% of the browsers – Silverlight Plug-In and iPhotoPhotocast – both of them again commonly installed plugins and probably were not at fault.

The debugging process

I realized if I was to figure this out, I’d have to pinpoint the source of the problem by trying out different things and ask users to try it out. I started with a basic page template and created test pages by truncating things. There were only a handful of people who reported the issue and were willing to help us fix it, so I had to be careful with my tests to not overwhelm them. Some of the test pages I created were:

  1. With third party JS removed
  2. Non-minified CSS
  3. Page with no CSS

Not much progress here since all tests came back negative. After another iteration of test pages, I was somewhat certain that this was related to a software at the user’s end. Luckily, an enterprising user tried out the scenario for us and disabled browser extensions until the issue went away.

The extension was installed by a video downloading software, which installed extensions to integrate into all available browsers. Finally, the issue could be reproduced! But how could a browser extension mess up the scrolling on a website?

I should note at this point that I don’t use or own a Mac. If I had to figure this out, I’d have to get my hands on one. I tried out Browserstack, but the connection was so slow I couldn’t get any thing done. It’d be great they just hand out VNC details I can connect to myself and control the quality at my end. I didn’t want to let this go when I was so close and had no choice but to install a Mac OS VM.

I installed Firefox and the video downloader on my VM and copied the browser extension directory back to my machine. I had some experience writing a Firefox extension, so I knew where to start looking. The extension loads its own CSS and JS on every page. The CSS was the first thing I opened and right at the very top was this CSS block:

.clear { /* generic container (i.e. div) for floating buttons */
    overflow: hidden !important;
    width: 100% !important;
}

A generic name (even they realized that) in the stylesheet, and it was injected on every page. I recognized this class was used in our code – to clear floats. The extension’s “overflow: hidden” would come in and mess up scrolling. A small change to the rule name confirmed this was what needed to be fixed.

The Fix

The real fun was about to begin. A grep of the code base told me that I had 80 files (this is legacy code, not well written by today’s standards) to sift through to rename this CSS class. Not a lot, but it’d take me a few hours at the very least. And it wasn’t something that I could optimize with a perl pie. Resetting the CSS class with Javascript was difficult because the included stylesheet also set a width on the class and I couldn’t find any way to unset that rule. The naive, hopeful me thought I could just loop through document.styleSheets in JS and remove the conflicting CSS as a temporary fix until the maintainers fixed this issue at their end. Turns out, for security reasons, browsers don’t like it when you do this. In the end, I had go through each file and rename the CSS class.

Epilogue

I sent an email to the maintainers of the video downloader letting them know that their browser extension wasn’t playing nice. They got back to me and said they were going to fix the issue but I haven’t gone back and checked if they actually did. I hope they did stop breaking the internet.

Posted in bugs, computers, tech Tagged with: , , , ,

Stripe CTF 3 write up

Stripe CTF 3 finished a few days ago. The latest CTF focussed on distributed systems, instead of security. I have read about distributed systems but getting to develop one was a good learning experience. Unfortunately, it took me a while to solve the last level, and after the capturing the flag I didn’t have time to go back and optimize my code for the other levels. I will walk through my solutions here.

Level 0

Given a path to a dictionary file, and a text file as input, print words which are not in the dictionary wrapped in <>

The non-optimized solution was implemented in Ruby, which built the word array and performed an O(n) lookup for every word, resulting in a O(mn) running time. Hash tables and bloom filters sprang to my mind immediately. I wrote my solution in Python:

words = open(path).read().splitlines()

entries = set(words)

output = ''
for line in sys.stdin:
    for word in re.split('([^ \n]+)', line):
        w = word.strip()
        if not w or w.lower() in entries:
            output += word
        else:
            output += '<{0}>'.format(word)

This basic solution netted me 340 points and I moved on to the next level.

Level 1

Git commit a commit message with SHA1 hash lexicographically smaller than 000001, before the bot does.

The non-optimized solution was written in shell, using git commands to generate hashes which involves spawning a new process – a very slow operation. Git commits are SHA1 hashes. Assuming the hashes are evenly distributed, one in 16^6 (= 2^24 = 16777216) hashes will match the given criteria, which is about 17 million. I used Python multiprocess Pool map to generate 2 million hashes in ~8 seconds:


def commit_hash(nonce):

    body = '''tree {2}
parent {3}
author CTF user <me@example.com> {0} +0000
committer CTF user <me@example.com> {0} +0000

Mined a GitCoin!
nonce {1}
'''.format(timestamp, nonce, tree, parent)

    sha1 =  hashlib.sha1('commit {0}\0{1}'.format(len(body), body)).hexdigest()

    if sha1 < '000001':
        commit = 'commit/commit' + nonce
        f = open(commit, 'w')
        f.write(body)
        f.close()
        sys_stdin('git hash-object -t commit -w "{0}"'.format(commit))
        sys_stdin('git reset --hard "{0}" > /dev/null'.format(sha1))
        sys_stdin('git push origin master')

def solve():
    step = 100
    p = Pool(step)
    for c in xrange(0, 2000000, step):
        a = []
        for counter in xrange(c, c + step):
            nonce = str(counter)
            a.append(nonce)

        p.map(commit_hash, a)

This also had a player-vs-player round, but the high latency of my connection meant I was at a huge loss.

Level 2

Implement a DDoS protection for “Shield”. Some attackers were sending a lot of requests (elephants), breaking the servers for legitimate users (mice).

The shield was implemented in Node.js, originally letting all requests through. I modified the code to reject requests based on the average requests and added a rudimentary idleness check:


if (timeNow - lastRequestTS <= 1000 &&
    requestCount > 20 &&
    ipRequestCount > requestCount / ipCount / 5) {
    rejectRequest(reqData);
}
else {
    this.proxies[0].proxyRequest(reqData.request, reqData.response, reqData.buffer);
}

Level 3

Implement a distributed search engine. You are given a master node with 3 slave nodes and 4 minutes for indexing. Each node is limited to 500 MB of RAM. Queries will be dictionary words only. Results should include any suffixes/prefixes (eg: fan should also return results for fancy or infant).

The non-optimized solution was implemented in Scala. The master node would query all 3 nodes for the search results… and return result only for the first one. Very inefficient, to say the least. I started modifying the solution to distribute the searches across 3 nodes and combining the results in the master, but Scala turned out to be a difficult language to hack in. I moved back to Python and threw together a small Flask app which copied the directory contents to /tmp/ (I tried /dev/shm first, but there wasn’t enough space) and used grep to get the search results (grep is fast, really fast). After a couple of tries, I got to the next level.

I am not really satisfied with my solution, because search is an interesting problem and I did this without building any index. I was hoping to come back to this problem and implement it with suffix arrays and will try to write one soon.

Level 4

Build a distributed SQLite Cluster with 5 nodes. The servers listen on Unix sockets. There’s an evil Octopus though, which simulates TCP latency and lossy connections. The nodes in the SQLite cluster had to be replicated to stay in sync. Scoring was based on how many successful queries you answered, minus the network traffic.

This was unarguably the toughest problem in the CTF. I went through the Raft paper to understand how I could implement a distributed system, but it would have been difficult to write a Raft implementation from scratch (some folks did though). I found raftd, which is a sample implementation of goraft. The non-optimized code was based on raftd, with almost the same structure.

I started integrating the raftd code in the sqlcluster code, which turned out to be a lot harder than I had imagined. In raft, only the leader can perform actions, the slaves must forward their requests to the leader for responses. Forwarding was easy, but the responses could get lost due to Octopus. However, every query eventually did make it to each node, so I set up a local cache of queries and poll the cache to check if the response was ready and return it.

It was easy to capture the flag once I got my distributed failover working. But I wanted to optimize it more and looked at decreasing my network traffic first. I tried gzip but it doesn’t compress small strings too well. I realized I could compress queries by representing each word with a single character (eg: SELECT with !). I shaved off almost 100 bytes from each query. To speed up query responses, I put my query forwarding in a goroutine for concurrency. Finally, I added in memory SQLite instead of using a file in /tmp/. After a few hundred tries, I got my highest score of 2723, 5 ranks short of the leaderboard.

My solution for level 4 on Github.

Summary

I really liked the focus on distributed systems. It gave a chance to get acquainted with how scaling works. The scoring system was an interesting twist. My only gripe was the huge variance in scoring on the same code. It’d be nice to have advanced test cases for each level for the next time where you get to play with real world test cases.

Posted in hacking Tagged with: , , , , , , ,

How secure are Indian payment gateways?

India has a dearth of good payment gateways – most have obsolete difficult-to-use APIs requiring days, if not weeks, to integrate. They require at least a dozen documents (most of them need them sent through post) before they even let you inside their walled garden. Additionally, gateways have complicated plans – depending on your initial spending budget you can get a deal for 3% or otherwise end up paying 5%. Of course, you can also haggle. Above that most of them have a monthly/yearly maintenance fee. It’s a sad state of affairs – and part of the reason is because of the government policies making it very difficult to accept payments easily.

I was very excited when Flipkart’s Payzippy launched recently. (NB: I wrote this post a while ago, but I waited while I contacted the gateways about the security issues before publishing). As I was skimming through their API document, I discovered they were using MD5 which is not considered cryptographically secure anymore. I looked deeper and found a few other security measures which, as a payment gateway, they should have taken. When I looked at other payment gateways, they had similar problems (some even much worse) and so I compiled a list of Indian payment gateways I could find and research about.

First, some layman technical info.

How payment gateways work

Indian payment gateways are asynchronous; the user is first redirected to the gateway where he/she enters his/her card details, and then redirected to the mandatory 3-D Secure/Securecode password verification. These redirects happen through the user’s browser. To verify that a transaction received by the gateway is associated with the right user and originated from the merchant – the merchant attaches a message authentication code (MAC) of the transaction which is transmitted to the gateway along with the transaction details. The MAC generation algorithm is gateway specific. Gateway at its own end will recompute and verify the MAC to make sure data hasn’t been tampered with and then proceeds with assessing the rest of the transaction. After it verifies the transaction it redirects back to the merchant with the status of the transaction and the MAC of the status so the merchant can verify the request originated from the gateway. These “handshakes” should be tamper proof, or bad things can happen.

Not following along? Here’s a drawing to help you understand:

Message Authentication Code Flow

Message Authentication Code

The sender (merchant) has a secret key (shared with the gateway) and uses a predetermined MAC algorithm to generate a MAC string which is included in a hidden form field while redirecting him/her to the receiver (gateway).

A bit about hashes

Why are MACs needed? They are used to ensure data integrity during redirects. MACs need to be tamper proof – otherwise a user could change the transaction amount in transit from merchant to gateway, or modify a failed transaction to look like a successful one. A naïve approach to implementing simple MACs is to use a common hashing algorithm (like SHA1 or MD5) with a shared secret key which is then appended to the data transmitted.

# Method 1: Key as a suffix
mac = hash(message + key)

# Method 2: Key as a prefix
mac = hash(key + message)

Both these approaches have flaws:

  1. When using key as a suffix with a collision prone hash algorithm like MD5 an attacker may modify the message in such a way that the MAC doesn’t change.
  2. When using key as a prefix (in addition to the previous vulnerability) an attacker could use length extension attack to add more information to the message.

MACs like these depend on the collision resistance of the underlying hashing algorithm for security. One right way to preserve and validate integrity is to use HMAC which uses a nested hash calculation to make the hashes tamper proof. HMACs are meant for verifying integrity; using a hash function isn’t.

Security best practices

I chose a few selected security practices which can result in possibly serious vulnerabilities. Of course, this is not an exhaustive list.

Merchant login page shouldn’t be on HTTP

Serving login pages over HTTP is insecure; it’d be trivial to redirect user to a fake login page, or modify the page in transit and give user a modified form to submit data to.

Use HSTS

Using the HTTP Strict Transport Security (HSTS) header a site can tell the browser to always use HTTPS for that domain in the future. None of the gateways I researched were using HSTS. Most of them are using 301 redirects which are stored in the browser cache. But 301 redirects are URL specific, while HSTS is domain specific. i.e. 301 redirects a single page, HSTS redirects a complete domain. With HSTS (after the first visit by a user) the browser will never contact the site using HTTP again on any of the site pages. If a site isn’t using HSTS, the user cookies can be stolen by directing the user to visit a non HTTPS page while logged in or by redirecting them to a modified login page.

Secure cookies

Cookies should be marked Secure, which tells the browser to transmit the cookie only when the request is HTTPS. So, even if a user opens an HTTP page (which shouldn’t exist), their cookie is never transmitted in clear text to the server. If the secure parameter doesn’t exist, an attacker could eavesdrop on a user’s session when an HTTP page is opened. Having access to the merchant’s cookie, the attacker could steal user data, reset keys or even worse.

Hash based authentication

I explained MACs earlier in the post. While the above-mentioned security practices involve a malicious third party, this one just involves a malicious user. If a gateway is using a weak MAC generation algorithm, a user could spoof a request with a “transaction succeeded” message which will tell the merchant that the user has paid.

Not relying on “security through obscurity”

Security through obscurity may work in some cases, but it does not work in case of payment gateways at all. Firstly, this information (handshake algorithm, for example) is available through other means (eg: through e-commerce software code) and secondly, merchants, both existing and past, using the gateway have that information already.

Payment gateways tested

I included the following payment gateways in my research. Gateways are rated on a scale of 1 to 10. If any payment gateway is missing, leave the name and links in comments and I’ll update the post with my observations. Click on a gateway to directly jump to its review.

EBS
Payu
CCAvenue
Zaakpay
Direcpay
Transecute
Payzippy

Citruspay (Added on 2013/10/02)
Juspay (Added on 2013/10/22)

I tried to contact the gateways about the critical issues over three weeks before publishing this post. I sent a generic email because none of them had specialized email for reporting security issues. However, none of them replied to my later emails regarding their plans on fixing these issues. Clearly, the one who didn’t reply at all don’t care about user security at all, but the others didn’t fare well either.

Observations

The following observations are based on what information I could find – I do not have an account to login and play around with any of them (test accounts are only available to customers). Even their documentation is a closely guarded secret. Thanks to search engines, I found API documentation or integration code for most of them, which I used to figure out how the communication between merchant and gateways was going on.

EBS

The home page of EBS loads unencrypted over HTTP. Their login page is hosted on secure.ebs.in, but it’s also available on HTTP. Cookies are marked secure. HSTS is not implemented.

EBS uses MD5 with hash(key | message), and calls it “secure hash method”, which is really not secure at all. A length extension attack is not effective here though, because the messages have a fixed data structure with separators.

Hash = MD5(secretKey|account_id|amount|reference_no|return_url|mode)

This hash is embedded in HTML form. While redirecting back to the merchant, EBS uses the same key to encrypt data with RC4.

Let’s talk about RC4 first – RC4 is a stream cipher. Stream ciphers create a pseudorandom stream, called keystring, based on the key. This keystring is bitwise XORed with the plain text to generate the ciphertext (and if XORed with the ciphertext gives back the plaintext because of the way XOR works). Stream ciphers have one very important requirement – the same key should never be used twice to encrypt messages.

RC4 is also susceptible to bit flipping attacks. For example, EBS redirects back to merchant with a string containing “0” or “1” depending on whether a transaction succeeded. Even if a transaction fails at EBS’s end the user can just flip a bit to change 0 to 1 and the merchant will receive the transaction as valid. If you had to take away one thing from this post, remember this – “Encryption is not authentication“.

EBS’s implementation of RC4 is also broken. RC4 has weak encryption for the initial part of keystream. It’s recommended that the first few thousand characters be discarded to prevent leaking the key. But EBS encrypts the message without discarding any part of the keystream.

Lastly, this whole message encryption is an optional feature which is disabled by default.

To sum up, possible vulnerabilities in EBS:

  • Secret key can be cracked easily, or a transaction state can be modified
  • Spoofing gateway responses and requests
  • Redirect user to a fake login page.

Source: Integration manual (the manual is in the zip), “Secure hash validation manual

Gateway response: Only initial response, no follow up from their end.

Grade: 1/10

Payu

Payu redirects user to the secure site after which all connections and links are encrypted. Login page is not available over HTTP. Payu doesn’t use 301 redirects. Every time a user opens payu.in, HTTP page is loaded first. No HSTS implemented. Cookies are marked secure.

PayU uses trivial MACs – SHA512 (key | message | salt) which has no known collisions. Although it’s quite likely in the future. Their hash exchange protocol is also resistant to length extension attack – during communication the hashes are computed for the message in normal and reverse order.

Possible vulnerabilities:

  • Redirect user to a tampered login page.
  • MAC is secure for now but needs to be future-proofed

Note: After I published this post, a PayU engineer replied and said that verifying a transaction is a necessary requirement for merchants who provide services before receiving funds.

Grade: 8/10

Source: Integration document

CCAvenue

Home page opens on HTTP. Login page also links to HTTP which then redirects to SSL page. A fake login page can be presented. Luckily, cookies are marked secure so at least existing sessions are safe from sniffing. No HSTS is implemented.

CCAvenue uses Adler checksum – before writing this I had never even heard of it. Adler is an ancient checksum based on the sum of ASCII values in a string. The callback from gateway to merchant uses Adler again making a request easily tamperable. The return request has a character Y or N pointing to if the transaction succeeded among other details. Changing N to Y is simple – use the difference of (Y – N) and modify the checksum.

After I contacted CCAvenue, they told me that they do not use Adler32 anymore and instead use encryption. I couldn’t find any information about their new protocol, but “encryption is not authentication”.

Possible vulnerabilities:

  • Spoofing gateway responses and requests
  • Redirect user to fake login page

CCAvenue already has a poor track record in security – they were broken into in 2011 with an SQL injection.

Grade: 1/10

Gateway response: Migrated away from Adler32

Source: Integration document, Sample implementation

Zaakpay

Zaakpay redirects to secure page with a 301 redirect, but no HSTS here either. Cookies are marked secure. Zaakpay is the only gateway I researched which uses HMAC for verification. And is the only gateway to not have a “secret integration document” available only to customers.

Possible vulnerabilities:

  • Redirect merchant to a spoof site on first load

Grade: 9/10

Source: API documentation (with sample codes)

Direcpay

Login form exists on the home page which is served on HTTP which then submits to a HTTPS . Cookies are not marked secure. No HSTS.

And you thought CCAvenue using Adler was bad? For encryption/decryption – ahem – DirecPay uses doubly encoded Base64. As if doing it twice makes it any more secure. Let me quote from their verbiage (in their documentation):

Transaction parameters will be accepted from the merchant website in an encoded format to ensure that no data is tampered and transaction is processed in a secured fashion.
The script for encryption / decryption logic of transaction parameters can be downloaded from the link:
http://www.timesofmoney.com/direcpay/downloads/dpEncodeRequest.zip

I wouldn’t put it past them to be unable to stop basic SQL injections.

Well, I’ll assume there’s no authentication.

(Note added on 2013-10-11: Direcpay’s response on using Base64 is that they have been migrating their customers to an API with encrypted communication since December 2012, however not all the merchants have moved yet. Newer merchants will use the new API. Nevertheless, my view on this is that using encryption may be better than Base64, but it’s still not the most secure way of doing things.)

Possible vulnerabilities:

  • Redirect users to a spoofed login page
  • Sniff already logged in user cookies.
  • Free for all – spoof gateway responses and requests

Incidentally, Google India uses DirecPay for AdWords but they get to use a special API which uses PGP for communication. Indiatimes Shopping (DirecPay is owned by Times group) also gets a special API which uses something completely different.

Grade: 0/10 (Remember this is on a scale of 1 to 10)

Gateway response: None

Source: Integration document

Transecute

Transecute login loads over HTTPS directly on a different domain (secure.transecute.com). HTTP is inaccessible (404) on that domain. No HSTS is implemented. At least cookies are marked secure.

I couldn’t find Transecute’s integration document. I found an Opencart forum post with their integration code in PHP according to which it uses MD5 with (message|key)

Possible vulnerabilities:

  • Modify text such that MAC hash remains same
  • Redirect users to a fake login page during initial load

Grade: 6.5/10

Gateway response: No follow up after initial response

Source: Integration code (archive link), Opencart forum post with same code

PayZippy

The newest kid on the block. At least it’s well designed. HSTS is not implemented. Home page redirects from HTTP to HTTPS. Cookies are not marked secure so cookies can be sniffed the first time user opens the HTTP page.
Hash is complicated in PayZippy’s case – it’s up to the user to choose either MD5 and SHA1, but it’s still MAC(message | key), which can be tampered with. Their (official?) Magento extension is also using MD5 by default.

Possible vulnerabilities:

  • Modify text such that MAC hash remains same
  • Redirect users to a fake login page during initial load

Grade: 6.5/10

Gateway response: No follow up after initial response

Source: Magento integration, Integration document received in email

Citruspay

Login page is served over HTTP, which submits to HTTPS. Cookies are marked secure. No HSTS.

CitrusPay is the second gateway which uses HMAC for verification.

Possible vulnerabilities:

  • Redirect user to a fake home page

Grade: 7.5/10

Source: Integration document, Sample integration code

JusPay

The homepage of JusPay opens on HTTP, with no redirects. The merchant site is a subdomain (merchant.juspay.in) linked on the home page. It’ll redirect to HTTPS, but there are no 301 redirects. Cookies are tagged secure.

JusPay has 2 solutions for acccepting payments:

  • IFrame checkout –  a JusPay page is displayed in a frame at the merchant’s site and the user can enter their details.
  • Inline checkout – The user enters the credit card details on the merchant’s site itself

Both are different from how other gateways are doing things. The CC details are input on the merchant site directly. In the IFrame, the data goes directly to JusPay, and in Inline the data goes to merchant’s server which then communicates to JusPay (server to server). I hope they have a strict requirement of HTTPS on merchant checkout pages.

JusPay is not using any MAC. That works for inline checkout because their communication happens server to server, i.e. the user can’t tamper with the traffic. On return from JusPay, payment status verification is mandatory, which should provide security for IFrame. But none of their integration handled that verification, it’s up to the merchant write that piece of code. It might be an issue if the merchant just checks the final status of the transaction and doesn’t check the amount and other details, which could have been tampered with.

Possible vulnerabilities:

  • Redirect user to a fake home page.
  • IFrame option might have issues depending on how the merchant handles the payment reconciliation.

Grade: 6.5/10

Source: Demos on their merchant site, integration code

Conclusion

I didn’t grade on some things – XSS, CSRF, iframe embedding, vulnerable software, server misconfiguration – you get the idea. Those are, in many cases, result of bugs. Bad design decisions aren’t bugs though. Overall this post should give a good idea if you are looking for a payment gateway to choose from. It’s great that Zaakpay is doing many things right. Now only if they could redesign their UI.

None of the gateways use HSTS – which I consider a huge security issue in some of them when coupled with other issues. HSTS is not perfect – there’s the first page load which can still be tampered with, but it’s better than 301 redirects and infinitely better than serving all pages on an insecure channel. And it’s not at all complicated to set up.

MD5 is the favourite method among many gateways – Transecute, PayZippy and EBS use it. It’s surprising that developers still believe that MD5 for something this important and sensitive is appropriate.

PCI-DSS is useless. Every gateway has PCI DSS certification when they are clearly not secured at all. PCI-DSS deals with how credit card data is handled – which they might be adhering to. But when it comes to handling transactions PCI-DSS says nothing about that.

What I found disappointing was that the gateways (except Zaakpay and to a lesser extent Payu) do not seem to be taking security seriously and have not been upfront about these flaws to their customers. Security should be a priority, rather than an afterthought. Bad security choices made consciously while designing the system encourage bad security practices throughout the rest of the system. Furthermore, by keeping their security practices unpublished, they have avoided the kind of public scrutiny which could have caught these design flaws long ago.

Epilogue

Before I wrap up, timing attacks are worth a mention – since all gateways are using MACs some way or the other, all of them are vulnerable to timing attacks. None of the integration code I read had special protection against these. A timing attack works on information leaked by lazy comparison between two strings. Consider these two comparisons for example:

# Comparison 1
python -m timeit '"abcd" == "abce"'
10000000 loops, best of 3: 0.0329 usec per loop

# Comparison 2
python -m timeit '"abcd" == "qwer"'
10000000 loops, best of 3: 0.0293 usec per loop

Note the time difference between comparison 1 and 2. Using this time difference it’s not difficult to compute the right hash (without knowing the key at all) one character at a time. It’s not practical in many cases because there’s too much noise – but if a merchant is running a site on a shared host things become quite a bit easier. The search range is very small – there are only 16 hexadecimal characters. If you are a merchant with a gateway using MACs – make sure to use a comparison function which doesn’t do lazy comparison.

Disclaimer: This post is for informative purposes only. I don’t condone the use of any of the vulnerabilities highlighted here for any unlawful use.

Posted in crypto, security, tech Tagged with: , , , , , , , , , , , , , , , ,

KWallet Security Analysis

KDE 4.12 comes with a KWallet GnuPG backend, which is more secure. More info here, including how to move existing wallets.

(Note: Tom Leek has written a more detailed analysis on StackExchange.)

I started using KWallet a few months ago to stop reusing same passwords on multiple sites. Using KDE Wallet plugin for Firefox, I setup my passwords to be automatically stored in a KWallet file, which was the best solution I found for Firefox on Linux (other than Lastpass, but it stores passwords remotely and is closed source, which I don’t prefer when dealing with passwords). But I needed access to my passwords on my Android device occasionally, so I started to work on a Python script to read KWallet .kwl files. While I was writing the script, I discovered that KWallet has some implementation problems.

About KWallet Code

The backend code for KWallet was written nearly a decade ago, from looking at the copyright notices on files. The source code for KWallet is available as part of KDE Runtime here. The backend code uses its own Blowfish and CBC implementation. KWallet’s Blowfish implementation doesn’t match the OpenSSL’s Blowfish due to wrong endianness.

The KWallet format

KWallet has a simple storage format, the data structures are stored in a serialized format using QDataStream, which is like JSON for C++/Qt. QDataStream provides a platform independent way to read and write to files, which is good since KDE runs on a variety of platforms. The serialized string generated by QDataStream is then encrypted with Blowfish in CBC mode with a key size depending on the size of the password used for the wallet given by the user.

Where things go wrong

Key generation

KWallet doesn’t use the password input by the user to encrypt a wallet directly, instead it uses a key generation scheme. The key generation can be described as – break up user password into blocks of 16 characters each, and apply SHA1 2000 times on each of them. This is key stretching without a salt. These blocks are then trimmed and concatenated to fit into 56 bytes, the maximum key length allowed by the Blowfish cipher. The problem here is that the user password is not salted, so for short passwords, it’s easy to compute rainbow tables using the key generation sequence and crack multiple KWallet passwords.

Mode of cipher

KWallet uses CBC, or to be accurate, it pretends to – the keysize used for CBC is the length of the string to be encrypted, which turns CBC into the less safe mode of cipher – ECB. ECB doesn’t hide information well, for example (as on Wikipedia), an image of Tux encrypted with ECB will show patterns of the data that was encrypted:

Now, no one is going to store images in KWallet, which is when ECB is glaringly insecure, but any sort of repetitive data or reused passwords will show up in an analysis of a KWallet file. CBC and ECB mode both ensure confidentiality, but additionally CBC mode also adds randomness which ECB doesn’t.

Blocksize

Because KWallet effectively uses ECB, a block size of 8 would be very bad at hiding information. In most cases, there’s not even 8 bytes of information in a block in KWallet. KWallet uses QDataStream, which encodes QString objects (used in KWallet maps) as UTF-16. So, the string “abcd” will be stored as “\0a\0b\0c\0d“, which gives four bytes of information per block.

Be safe

A replacement of KWallet, KSecretService has been due for a while. It even made it in release of KDE 4.8, but was removed due to a feature freeze in KDE libs. The replacement will be a part of KDE Frameworks 5, which is nearly a year away.

Unless Blowfish is broken, passwords in KWallet are safe – they just aren’t using recommended cryptography protocols. That said, if you are really *paranoid* I’d suggest the following safe practices for using KWallet to better hide information:

  • Obviously, don’t reuse passwords but with KWallet using ECB necessitates it more.
  • Don’t put KWallet files in version control
  • Use a long password – the longer the password, lesser is KWallet susceptible to rainbow tables (or even brute force)

Huge thanks to the Matasano Crypto challenges, I learnt a lot solving those challenges.

I reported this to security@kde.org on 2013-06-07, but I never heard back and later posted this on my blog. There’s a related CVE-2013-7252 for this security issue now.

Posted in crypto, scripting, security, tech Tagged with: , , , , , , ,

QTreeView and custom filter models

I’ve been learning PySide which is an amazingly easy library to get into GUI development. For an application I was developing, I needed a search filter for some hierarchical data represented by QTreeView.  QSortFilterProxyModel is the default choice to add a filter to a QTreeView, but it, by default, applies the filter to all nodes from any root to leaf. If any node has to show up in the search result, all the nodes from a root to that node have to match the search filter, which is mostly useless for hierarchical data. From the documentation: “For hierarchical models, the filter is applied recursively to all children. If a parent item doesn’t match the filter, none of its children will be shown.”

However, it is easy to setup our own class to inherit from QSortFilterProxyModel as shown in this forum post, and then write our own filterAcceptsRow method which will do three things –

  1. First, it’ll check if the filter accepts the row itself (which is the default behaviour),
  2. After that it traverses all the way upto root to check if any of them match, and finally
  3. Check if any of the children match

The filterAcceptsRow method will call a method for the three steps and return True if any of those return True

class LeafFilterProxyModel(QtGui.QSortFilterProxyModel):
    ''' Class to override the following behaviour:
            If a parent item doesn't match the filter,
            none of its children will be shown.

        This Model matches items which are descendants
        or ascendants of matching items.
    '''

    def filterAcceptsRow(self, row_num, source_parent):
        ''' Overriding the parent function '''

        # Check if the current row matches
        if self.filter_accepts_row_itself(row_num, source_parent):
            return True

        # Traverse up all the way to root and check if any of them match
        if self.filter_accepts_any_parent(source_parent):
            return True

        # Finally, check if any of the children match
        return self.has_accepted_children(row_num, source_parent)

Writing the code for first function – test if a row matches a filter – is easy, because the parent class already implements it:

    def filter_accepts_row_itself(self, row_num, parent):
        return super(LeafFilterProxyModel, self).filterAcceptsRow(row_num, parent)

Second function is to check if any of the ancestors (root to current node) match the filter. Traverse all the way to the root, and use method 1 for testing if any item matches the filter:

    def filter_accepts_any_parent(self, parent):
        ''' Traverse to the root node and check if any of the
            ancestors match the filter
        '''
        while parent.isValid():
            if self.filter_accepts_row_itself(parent.row(), parent.parent()):
                return True
            parent = parent.parent()
        return False

Lastly, check if any of the children match the filter. The method here is very inefficient (recursive depth first search), any large data will need some sort of caching.

    def has_accepted_children(self, row_num, parent):
        ''' Starting from the current node as root, traverse all
            the descendants and test if any of the children match
        '''
        model = self.sourceModel()
        source_index = model.index(row_num, 0, parent)

        children_count =  model.rowCount(source_index)
        for i in xrange(children_count):
            if self.filterAcceptsRow(i, source_index):
                return True
        return False

Putting all the pieces together:

from PySide import QtGui

class LeafFilterProxyModel(QtGui.QSortFilterProxyModel):
    ''' Class to override the following behaviour:
            If a parent item doesn't match the filter,
            none of its children will be shown.

        This Model matches items which are descendants
        or ascendants of matching items.
    '''

    def filterAcceptsRow(self, row_num, source_parent):
        ''' Overriding the parent function '''

        # Check if the current row matches
        if self.filter_accepts_row_itself(row_num, source_parent):
            return True

        # Traverse up all the way to root and check if any of them match
        if self.filter_accepts_any_parent(source_parent):
            return True

        # Finally, check if any of the children match
        return self.has_accepted_children(row_num, source_parent)

    def filter_accepts_row_itself(self, row_num, parent):
        return super(LeafFilterProxyModel, self).filterAcceptsRow(row_num, parent)

    def filter_accepts_any_parent(self, parent):
        ''' Traverse to the root node and check if any of the
            ancestors match the filter
        '''
        while parent.isValid():
            if self.filter_accepts_row_itself(parent.row(), parent.parent()):
                return True
            parent = parent.parent()
        return False

    def has_accepted_children(self, row_num, parent):
        ''' Starting from the current node as root, traverse all
            the descendants and test if any of the children match
        '''
        model = self.sourceModel()
        source_index = model.index(row_num, 0, parent)

        children_count =  model.rowCount(source_index)
        for i in xrange(children_count):
            if self.filterAcceptsRow(i, source_index):
                return True
        return False
Posted in python, tech Tagged with: , , , , ,