Tuesday, February 22, 2011

Vicnum The Game Challenge -- OWASP AppSec EU 2011

Just a quick post about this nice and quick challenge. You can still play here, and congratulations to the winner Steve van der Baan (see his solution here).

Basically, the webapp choose a 3 digits random number that we need to guess, but finding a way to:
  • Hack the Game: Guess with 0 tries  (yep, better than a lucky guess)  and with a number > 3 digits (1337, for example)
  • Find a database player with the worst possible score and place another record in the database with that player’s name concatenated to your name and with a positive score. Something like "worst_player+"
First step: Hack the Game

Looking at the server response, it can be observed a base64 encoded value:
   
If you decode it (469) and enter this number... you'll guess the value :)

However, it'll put that you tried once... how to put "0 guesses"? Easy, once you get one valid guess the server issues the following cookies
       Milano=0012AA9B12good_username_ Brussels=0029A9B91crisp1; Geneva=92BEF345Apecan469
It's easy to spot the meaning of the cookies
  • Milano==username
  • Brussels==number_of_tries
  • Geneva==guessed_number

So, lauching the following request will get us hack the first part:
GET /vicnum4.php HTTP/1.1
Host: vicnum.ciphertechs.com
Referer: http://vicnum.ciphertechs.com/cgi-bin/vicnum2.pl

Cookie: Milano=0012AA9B12good; Brussels=0029A9B91crisp0; Geneva=92BEF345Apecan1337


Second step: Hack the Database

After guessing properly the number, the webapp allows to search for all the users that have guessed the number. It's a simple SQL injection

So, injecting the following query on the search feature will list all the users
  • player='+or+'1'%3d'1    => ' or '1'='1
And, after wasting some minutes trying to parse the server response I realized that I could be able to use the own SQL Injection to give me the "worst player"...
  • player='+union+select+min(count),'1','1','1'+from+results+where+'1'%3d'1  => ' union select min(count),'1','1','1' from results where '1'='1

So, we'll spot the user "appseceu" with -214748348 guesses, so seems that we have now the username. Finally, using the trick used on first part (changing cookies), we solve the challenge:

GET /vicnum4.php HTTP/1.1
Host: vicnum.ciphertechs.com
Referer: http://vicnum.ciphertechs.com/cgi-bin/vicnum2.pl
Cookie: Milano=0012AA9B12goodappseceu_username_; Brussels=0029A9B91crisp1; Geneva=92BEF345Apecan1337


Solved :). Thanks for this mini-webchallenge. As a reminder, the 21st of each month will be published a new challenge until the conference in June, so stay tuned to @appseceu.

Bonus Track:
Btw, a nice XSS :)

Regards,
Alex

Sunday, February 20, 2011

Getting fun with Android

After playing a bit with Android, i realized two things:
  • I like it
  • It´ll offer new interesting security-related challenges.

Also, I found two interesting things that, even public, I was not aware, so hope that help anyone.

HttpOnly cookies are not working on Android


It was already known (here and here), but I was happily surprised that Android doesn´t implement the HttpOnly cookies. So, right now, any XSS attack against this platform is much more easier as we have access to the cookies using JS code.

The good news is that, at least, "secure cookies" are implemented :).


Nmap SIGSEGV (or getting fun with Android libc implementation)

First thanks to @k0st as he was really helpful since my finding (and @timb_machine for pointing me to @k0st). Now, this bug is fixed on the latest Nmap SVN.

Basically, latest version of nmap was crashing on my Android when and inexistent hostname was entered:
./nmap --datadir /data/local/nmap/share/nmap  -oA scan  www.google.com =>  OK
./nmap --datadir /data/local/nmap/share/nmap  -oA scan  eeeeeeeeeeeeeee =>  SIGSEV

Program received signal SIGSEGV, Segmentation fault.
0xafd2d3f4 in freeaddrinfo () from /system/lib/libc.so
(gdb) bt
#0  0xafd2d3f4 in freeaddrinfo () from /system/lib/libc.so
#1  0x000b2c54 in TargetGroup::parse_expr ()
#2  0x0007bb24 in nexthost ()
#3  0x00077248 in nmap_main ()
#4  0x00072154 in main ()

it was easy to recognize a NULL dereference, but I had one question in my head... why this is not happening on Linux?. That´s because Android implements it's own tiny and optimized version of the libc (Bionic)... On Linux libc a check is done in order to avoid a NULL dereference, but not in Bionic.

Linux
 freeaddrinfo(NULL)  -> Safe
Android (and maybe another platforms)
 freeaddrinfo(NULL)  -> Crash

This issue is public (here and here) but I guess that this is not the first (nor the last) bug that we´ll see on applications "ported" to Android, so just take extra care to not introduce new bugs because of the different implementations.

As a reference, I append the original text.

PoC (Android 2.2)
./nmap --datadir ../share/nmap asdfasdfasdf

Basically the problem is the different implementation between libc and Android "bionic libc". See here


Linux
 freeaddrinfo(NULL)  -> Safe
Android
 freeaddrinfo(NULL)  -> Crash

So, in Android systems the problem is a NULL dereference in TargetGroup.cc TargetGroup::parse_expr (lines 214-223)

      addrs = resolve_all(target_net, AF_INET);
      for (addr = addrs; addr != NULL; addr = addr->ai_next) {
        if (addr->ai_family != AF_INET)
          continue;
        if (addr->ai_addrlen < sizeof(ss)) {
          memcpy(&ss, addr->ai_addr, addr->ai_addrlen);
          resolvedaddrs.push_back(ss);
        }
      }
      freeaddrinfo(addrs);

The resolve_all() function is calling the libc function getaddrinfo. (tcpip.cc line 371)
  rc = getaddrinfo(hostname, NULL, &hints, &result);
  if (rc != 0)
    return NULL;

If the system try to resolve an unknown host like "asdfasdf", the Android bionic libc will return an EAI_NODATA (7). See here
So, as the rc !=0 (is EAI_NODATA==7) , the resolve_all query returns NULL, and then, when it's called the following piece of code
 freeaddrinfo(addrs);  //addrs is NULL in this case

it will create a NULL dereference raising the SIGSEV, as the "bionic libc" doesn't check for NULL. Another curiosity is that Android is returning EAI_NODATA (7) that differs from  Linux EAI_ADDRFAMILY (1).

Patch:
if (addrs)
 freeaddrinfo(addrs)