This is probably the oldest hole in authentication, and it's still floating around.
How should a 2-tier application verify the password entered by the user? Three common approaches are:
Invoke a database stored procedure, pass it the username/password - let the stored procedure verify the credentials.
Execute a dynamic SQL statement that checks if a row exists with the username/password combination
Get the password of the user from the database and compare it at the client.
The first is a relatively safe approach. The second, with dynamic SQL statements, is a candidate for SQL Injection. If the input validation is weak, an adversary could mess with the dynamic query's logic.
The third, however, is the most insecure, and that's what I saw in a test last week. When the user entered his username/password, the client fetched the right password from the database and compared it with the input. If they matched, the client considered the user authenticated.
Why is the third approach so unsafe?
One, with a sniffer, I could see the password of any user: all I had to do was type in any valid username, say admin, with a wrong password and the client would fetch the right password to compare. My sniffer could see the right password. Network layer encryption like IPSEC does not help - an adversary could intercept the data when it was in plain-text by using WPE Pro, a packet editor that intercepts socket calls in memory.
Second, the client verified the user - not the server! The client checked the response from the server to decide if the user is authenticated and enabled all the menus accordingly. An adversary could now fool the client into accepting any password by modifying the response from the server. It's easy: tools like Interactive TCP Relay (ITR) provide a GUI environment to edit requests and responses.