Security by obscurity never works. Assume the attacker has your source code. If you are doing some super cool obscuring of the data (like storing the account number in the URL in some obscured manner like the Citi folks apparently did), someone can and will break your algorithm and breach your system.
Excuse my language, but what on this God’s mother-fucking Earth were the Citi folks thinking? Really? REALLY?!?
But I can see the conversation amongst the developers at Citi now. “We really need to implement our web site using modern REST techniques, meaning we cannot store state on the servers, but have the complete state representation in the client, and use stateless protocols to communicate between the client and server.
Hell, I’ve been part of this conversation at a number of jobs over the past few years.
But here are the problems with a truly stateless protocol.
Stateless protocols are subject to replay attacks.
If a request sent to the back end is stateless, then (unless there is a timestamp agreed to between the client and server which expires the transactions) it must necessarily follow that the same request, when sent to the server, will engage the same action or obtain the same results. This means if, for example, I send a request to transfer $100 from my checking account to pay a bill, that request, when sent again, will transfer another $100 from my checking account to the bill account. If sent 10 times, $1,000 will be transferred.
And so forth.
The biggest problem to replay attacks, of course, is a black-hat sniffing packets: theoretically, since the protocol is stateless, the black-hat doesn’t even have to be able to decrypt the stateless request. He just needs to echo it over and over a bunch of times to clean out your account.
Stateless protocols are insecure protocols.
As we saw in the case of Citi, vital information specifying your account was part of the request. This means that, if the bad guys can figure out how the account information was encoded, they can simply resend the request using a different account and–volia!–instant access to every account at Citi.
Combine that with a reworked request to transfer money–and you can now write a simple script that instantly cleans out every Citi bank and transfers the money to some off-shore account, preferably in a country with banking laws that favor privacy and no extradition treaties.
See, the problem with a stateless protocol is that if the protocol requres authentication, somewhere buried in every request is a flag (or a condition of variables) which say “yes, I’m logged into this account.”
Figure out how to forge that, and you can connect to any account just by saying “um, yeah, sure; I’m logged in.”
Stateless protocols expose vital information.
Similarly, a black-hat who has managed to decrypt a stateless protocol can now derive a lot of information about the people using a service. For Citi, that was bank account information. Other protocols may be sending (either in the clear or in encrypted format) other vital, sensitive information.
But we can just use SSH to guarantee that the packets are only being sent to our servers.
When was the last time you got a successful SSH handshake from a remote web server where you took the time to actually look at the authentication keys to see that they were signed by the entity you were connecting to? And when was the last time you logged into a secure location, got a “this key has expired” warning, and just clicked “ignore?”
SSH only works against a man-in-the-middle attack (where our black-hat inserts himself into the transaction rather than just sniffs the data) when users are vigilant.
And users are not.
But we can use a PKI architecture to secure our packets.
If your protocol is truly stateless, then your client must hold a public key to communicate with your server, and that public key must be a constant: the server cannot issue a key per client because that’s–well, that’s state. Further, you can’t just work around this by having the client hold a private key for information sent by the server; the private key is no longer private when a user can download the key.
So at best PKI encrypts half of the communications. And you have the key to decrypt the data coming off the server. And since all state is held by the client, that implies if you listen to the entire session, you will eventually hear all of the state that the client holds–including account information and login information. You may not know specifically what the client is sending–but then, you have the client, so with state information and detailed information about the protocol (contained in the client code), you’re pretty much in like Flynn.
My own take:
REST with a stateless protocol should be illegal when handling monetary transactions.
For all the reasons above, putting out a web site that allows account holders to access their information via a stateless RESTful protocol is simply irresponsible. It’s compromising account holder’s money in the name of some sort of protocol ideological purity.
Just fucking don’t do it.
You can eliminate most of these risks by maintaining a per-session† cookie (or some other per-session token) which ties in with account information server-side.
It’s actually quite easy to use something like HttpSession to store session information. And if your session objects are serializable, many modern Java Servlet engines support serializing state information across a cluster of servers, so you don’t lose scalability.
Personally I would store some vital stateful information with the session, stored as a single object. (For example, I would hold the account number, the login status, the user’s account ID, and the like in that stateful object, and tie it in with a token on the client.) This is information that must not be spoof-able across the wire, and information that is only populated on a successful login request.
The flip side is that you should only store the session information you absolutely need to keep around, for memory considerations: there is no need to keep the user’s username, e-mail address, and other stuff which can be derived from a quick query using an integer user ID. Otherwise, if you have a heavily used system, your server’s memory may become flooded with useless state information.
† And don’t just generate some constant token ID associated with the account. That doesn’t prevent against replay attacks, and if your ID is small enough, it just substitutes one account number for another–which is, well, pretty damned close to worthless.
Use a two-state login process to access an account.
The idea is simple enough. When the user attempts to log in, a first request is made to the remote server requesting a token. This token can be the timestamp from the server, a UUID generated for the purpose, or some other (ideally cryptographically secure) random number. Client-side, the password is then hashed using a one-way hash (like MD5 or SHA-1) along with the token number. That is, we calculate, client-side: passHash = SHA1(password + “some-random-salt” + servertoken);.
We then send this along with the username to the server, which also performs the same password hash calculation based on the transmitted token. (And please don’t send the damned token back to the server; that simply allows someone to replay the login by replaying the second half of the login protocol rather than requesting a new token for this specific session to be issued from the server.) If the server-side hash agrees with the client-side hash, then the user is logged in.
Create a ‘reaper’ thread which deletes state information on a regular basis.
If you are using the HttpSession object, by default many Servlet containers will expire the object in about 30 minutes or so. If you’re using a different mechanism, then you may consider storing a timestamp with your session information, touching the timestamp on each access, and in the background have a reaper thread delete session objects if they grow older than some window of time.
In the past I’ve built reaper threads which invalidated my HttpSessions, to deal with older JSP containers which apparently weren’t reaping the sessions when I was expecting them to. (I’ve seen this behavior on shared JSP server services such as the ones provided by LunarPages, and better to be safe than sorry.)
For God’s sake, just say “NO!” to REST. Especially when dealing with banking transactions. Especially if you’re the banking services I use: I don’t want my money to be transferred to some random technologically sophisticated mob syndicate, simply because you needed your fucking ideological purity of a stateless RESTful protocol and didn’t think through the security consequences.