Securing Business Apps
Rise of the White Collar Crackers
Section 2 - Bypassing the login
Database Security
When an attacker first comes to the application, they will need to gain
primary access. Lets assume a condition where the attacker is in after hours
or does not have a valid login of his own. The first level of security comes
with how much it takes just to gain access to the rest of the application.
There are many login and authentication schemes available. Our sample application
uses a simple portable small business scheme based around data stored in an
access database.
In order to provide a portable demonstration for these and other techniques,
I was forced to use an Access database. As we will see shortly, that is a poor
choice for sensitive data.
If the computer we are working on has Access installed, we could actually just
open up the database and see what all the user names and passwords were.
In our example the Access database does not have a password required to open it,
however even if it had, it would provide little to no real security for the database.
There are many programs on the market, and even open source code available on the
internet to crack Accesses weak authentication scheme. Even if the attacker was unable
to open the database, they could open it in a hex editor and search for table and field
names which appear unencrypted.
The above concerns all have the precondition of physical access to the database and
are commonly understood security factors. In this next section we are going to begin to
look at what can be done with only physical access to the application.
The login mechanism of our sample app first connects to the database and then
compares our login values to the usernames and passwords found in a users table.
If the username/password combination is found it loads the usergroup into a global
variable and loads the main form. If a correct username and password is not given
then it will alert them of an invalid login and again prompt for valid credentials.
The next level of attack we are going to look at comes through the use of cracking
techniques. That is binary analysis and manipulation of the compiled executable.
Query Manipulation - Null Byte String Truncation
Since our authentication model stores the users in the database, our Visual Basic
code must first open a connection to verify the login information. In this classic
example, our developers have chosen to implement application layer security controls
rather than information layer security controls.
Because all application security is handled with code running on our local machine,
and because we have physical access to the executable, we have the power to explore
the application as it runs and manipulate the code as it executes.
The first bypass we are going to discuss is actually very simple. As we step through
the application code in our debugger we can make out the SQL statement used to query
the database with the username and password we supplied. In Visual Basic it looks
like this:
"Select * from tblUsers where " & _
"username='" & txtUserName & "' And " & _
"password='" & txtPassword & "'"
Another thing we can see in the debugger is where these strings are loaded from.
All of the strings we see in the app are plainly readable in the executable in
the form of unicode strings compiled in as data in the executable file. The first
fragment to be loaded for this sql statement is "Select * from tblUsers where " which
is located at offset 5C74.
We can see from the SQL syntax that if either the password or username does
not match no records will be returned. The developer will then test this condition.
If no records are found in the recordset, then its an invalid login. Since username/
password pairs have to be unique, only one record could be returned for a successful
login thus identifying the user.
But here’s the catch...we know how the sql query string is built up and from where.
The thing that limits the result of the query is that where clause that gets appended
onto it with our values from the textboxes.
If we say...replace the space just before "where" with a null character what will happen
then?
In C and many other programming languages the end of a string is denoted by the presence
of the first null character found. Visual Basic does not have this limitation because it
uses an OLE type to hold its strings, so VB can contain embedded nulls with out a problem.
It happily uses the modified string, appends on the username and password just as it was
designed to do then hands the query off to the database engine to return the recordset.
The catch is, the database engines are not written in VB, they locate the length of the
strings passed to them by the occurrence of the null terminator, which in our case, now
truncates our sql query to be "Select * from tblUsers" which will always return a
result set.
You can try this for yourself by starting up PatchManager.exe and selecting
the first Sql Null char truncation sample and hitting Patch & startup button.
After the binary is patched, it will execute for you and you will be able to
login with any username and password you want.
This may sound like alot of work...but really this much can be accomplished in under
5 minutes by a skilled hand.
Lets say I knew a users login and I wanted to login as them but did not know thier
password. This could also be readily accomplished by changing the space before "And"
to a null in effect limiting the results to just checking for a valid username.
If we knew there was an account named admin, we could now login with his privledge
level without having his password. Try the second Sql truncation demo from the
patch manager with user "admin" and see for yourself.
Debugging and Byte Patching
The next manipulation is more of a classic example of cracking and actual command modifications.
Above, we didn’t really change the logic of the program flow, just exploited some knowledge
of how languages handle string manipulation.
After the standard Sql query is made, the recordset either contains a record, or it
doesn’t. If not, then its an invalid login. In the end...this comes down to one processor
command. Valid? go on, Invalid? Stop and warn
Because of the unique string used for the message box warning, it is actually extremely
simple to locate this processor command. Running the executable through a debugger you
you can find the go-nogo command very easily by just scrolling up from the API call
for the messagebox.
The debug window shown above lists the raw assembler commands that make up the executable.
For vb developers, this view of their application is worlds away from they see and
write. The top line highlighted you see the conditional jump that will bypass the failed
login message if there were no records returned from the query.
What if this conditional jump was turned into an unconditional one thus making any login,
good or bad goto the login verified code instead?
Such a manipulation is trivial and can be made right in the debugger.
If you tried it out, you will find that the program now crashes unexpectedly on us with
some recordset related error. Ok, so this isn’t quite as easy as it could be. Apparently
because it takes a filled recordset to bypass the failed login message, some results
from that recordset are also being used to load variables to determine user privilege
level or something.
For some application code, just simply switching the kind of jump that is taken will
lead to immediate results. Such would be the result in a simpler case where there was
just a test like
if userGroup <> Administrator then
Msgbox "You do not have permission to perform this action"
exit sub
else
'do some privileged stuff
end if
As for our login example...Is this a stumbling block? yes, Does it put us out of
the game? No. With some time and experimentation we could look through the disassembly
and try to make sense of what was going on. In our case, to bypass the login using
this technique, we have to not only modify the type of jump that is executed, we also have to modify
the address it takes us to.
We know it does some recordset manipulations that are messing us up but don’t really
know what its trying to do. A logical assumption would be that its trying to get
some information for our user level to use through out the rest of the program.
Going back to our debugger, we stop execution to right after our patched jump occurs.
With out really having to know to much of what is going we can look at what calls to
the VB runtime and what strings are being used.
We see the string "group" being loaded. If we find its string offset and change its
name, and let it run then we get a new error about not being to find that field in the
recordset. So we know the error has to do with recordset manipulation trying to access
the group the logged in user is from.
If we scroll down a ways in the debug window we see this
You dont really have to understand assembler to see what is going on, Look at the names
of the highlighted VB runtime calls. We can see that some object is being accessed, then
freed, then finally at the bottom a new object is being created with a couple arguments
(the two pushs before the call to _vbaNew)
We cant know for sure, but its a good shot in the dark to try to just jump directly
to where the grey highlighted line where it is preparing for the _vbaNew function call.
At this point in the code the recordset looks like it has been disposed of, and something
new is happening. What we are hoping is hat we aren’t jumping over any critical initialization
code, and that a default value for whatever usergroup variable there is valid.
Going back to the place where we patched our jump, we now change the address it jumps to
to be our new location of 40AD6A. Running the program again and hey were in.
If you would like to try this patch for yourself fire up the PatchManager program
and select the first option and hit patch it. You will now be able to login with
any username and password combination with the default level privledges.
Because of the extra guess work and fishing we had to do to dodge the subsequent recordset
query, we got kind of lucky. Had the post jump code been much more complex bypassing the
login with this method could have become a very very arduous task. As is, figuring out
where to make it jump to bypass the recordset took me on the order of an hour or so with
trying to play with a couple different techniques to get through it. (what I really wanted
to do was to insert my own error handler into the function mais c'est la vie oui?)
|