During a project working with Hydra, a Network Login Auditor, we discovered and corrected a buffer overrun issue with possible security implications that might include the auditor being attacked by the auditee.

TL;DR Attacker using Hydra or Medusa can get pwn’d by the victim website responding with remote code execution via buffer overrun exploit.

Note: The original version of this article appears on my company’s Blog at Aspect Security on 20 March 2017. If you have commentary or feedback, please add them on that website. What appears below is a mirror.

Background

Aspect Security is currently co-developing an open source software product to automate penetration testing. The solution relies on novel methodologies for automatically understanding application design, then simulates targeted attacks in search of exploits. Once an exploit is found, countermeasures are automatically determined and simulated to determine their effectiveness.

A small facet of the larger project is making dictionary and brute-force authentication auditing of web sites easier. We chose to build this automated authentication testing on top of Hydra, an AGPLv3 credential checker (Note: some browsers display an ominous warning message about Hydra’s website as it contains many infosec tools).

While I was coding the automation glue, Hydra excelled at the task of breaking into my target test website. But networking issues kept cropping up and I needed to start debugging at a much lower level. Thankfully, Hydra has a wonderfully detailed set of -v -V -d command line switches which show the network nitty-gritty.

I configured Hydra to use a POST which would contain the following headers and data:

POST /c/portal_public/login HTTP/1.0
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Cookie: {Hydra will pre-fetch cookies}
 
my_account_cmd=auth&referer=%2Fc&my_account_r_m=false
&password=bill&my_account_login=bill@example.org&

After launching Hydra with -d, I started to see this fun bit of raw HTTP:

HTTP request sent:[0A]POST /c/portal_public/login HTTP/1.0[0D][0A]Host: 127.0.0.1[0D][0A]User-Agent: Mozilla/5.0 (Hydra)[0D][0A]Content-Length: 126[0D][0A]Content-Type: application/x-www-form-urlencoded[0D][0A]Cookie: JSESSIONID=9B6B0B52AA9E6C84E4AF7900AB01D415; SHARED_SESSION_ID=P7BESW28FTVV[0D][0A][0D][0A]my_account_cmd=auth&referer=%2Fc&my_account_r_m=false
&password=bill&my_account_login=bill@example.org&[0A][95][98]h|7[C9][BB][10][94][EF][[FF][7F][B8][86]-t[FF][7F]o[A3][D3][03][01][FF][FF][FF][FF][FF][FF][FF][FF][F0][93][EF][[FF][7F][C1]Vo[88][FF][7F]BodVisib[F0][96][EF][[FF][7F]etTimeout( “dotMt( “dotMakeBodVisible“,2000);[0A][09][0A][09]</script>[0D][0A]</body>[0D][0A]</html>[0D][0A][0A][95][98]h|7[C9][BB]@[FF][FF][FF][FF][FF][FF][FF][FF]P[97][EF][[FF][7F][B8][86]-t[FF][7F][F0][95][EF][[FF][7F][0D][99]m[88][FF][7F]o[A3][D3][03][01]h[97][EF][[FF][7F]'[08][02][FF][FF]P[97][EF][[FF][7F]?[C8]5[0C]e[FF][7F]`[97][EF][[FF][7F][90][94][EF][[FF][7F][E2] [0A]e[FF][7F]H.[0C]e[FF][7F][C8]5[0C]e[FF][7F][A2][AB][AA]2[03][95][98]h|7[C9][BB]H[93]-t[FF][7F]H[93]-t[FF][7F][B8][86]-t[FF][7F]$[DC][A4][D3][03][01][A0][95][EF][[FF][7F][8D][B3]l[88][FF][7F]H[93]-t[FF][7F][07][DC][A4][D3][03][01][95][98]h|7[C9][BB][A0][96][EF][[FF][7F][1C][92]l[88][FF][7F]P[97][EF][[FF][7F][18][02][84][A3][D3][03][01]o[A3][D3][03][01]@0[96][EF][[FF][7F] … Tons more data…

In the raw data, there is the original POST, CRLF transliterated to [0D][0A] bytes, an added User-Agent line, the content length, the body data as well as a whole bunch of things that should not be there. Why is there a </script> tag in my POST? And dotMakeBodVisible is a function call in the response from on the pre-fetch page when we got the cookies; this is not something that I’m sending. There are many bits of unprintable binary data. What’s going on here?

The Analysis

In C, a string is terminated with a NULL byte (\0). If a NULL is not detected, the string continues. This causes problems when the software tries to read the string and continues past the end string and into another running program’s memory space. Though the operating system is likely to prevent program_A from entering program_B’s memory space, overwriting the memory within the same program is still a serious issue. Buffer Overflow Attacks, Stack Smashing and other bits of fun allow an attacker tool to inject malicious data and possibly run their code on a victim’s machine. But, Hydra is the “attack tool” and not the “victim machine.” So why does it look like my attack tool is under attack?

In my situation above, Hydra first does a GET to the website to obtain the JSESSIONID cookie. Then Hydra POSTs that cookie along with the username & password to the server to see if that credential pair is valid. In debug mode, Hydra displays the data from the POST submitted on stdout, but the printing of this data does not consider the actual length of the data. Thus, the buffer is walked past the end, thus overrun. To exploit the above buffer overrun, a really good hacker must craft a website that returns pre-fetch cookie page responses in a certain manner that causes the person using Hydra (with the -d flag) to run the code embedded in the response.

In other words, the attacker using Hydra can get pwn’d by the victim website fighting back with an RCE exploit. But remember, you’re only using Hydra for testing on approved websites, right?

With all the other factors involved, this is unlikely to be exploitable at all, but it is still safer to fix. There is a check to prevent walking past the maximum buffer size and there is no apparent means to smash the stack.

The Fix

To correct the issue, the file hydra-mod.c was modified. The function hydra_report_debug() contains a few bugs that allowed this buffer overrun happen. First, a temporary buffer is statically allocated with 8200 bytes and only the first 512 bytes are zero’d out to NULL. Thus, if the data is longer than 512 bytes, no NULL character will be present to indicate the end of the string. Next, when performing the printf-style string expansion/replacement, the return value of vsnprintf() showing how long the string is was ignored. Then the entire 8200-byte array is walked and “pretty-print-copied” into a second buffer — even if amount of data needed was less than 8200 bytes. Below is the diff of the hydra-mod.c code:

692
693
694
695
696
697
698
698
699
700
701
702
703
704
705
706
705
706
707
708
709
709
710
711
712
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
void hydra_report_debug(FILE * st, char *format, ...) {
   va_list ap;
   char buf[8200];
   char bufOut[33000];
   char temp[6];
   unsigned char cTemp;
-  int i = 0;
+  int i = 0, len;
 
   if (format == NULL) {
     fprintf(stderr, "[ERROR] no msg passed.\n");
   } else {
     va_start(ap, format);
     memset(bufOut, 0, sizeof(bufOut));
-    memset(buf, 0, 512);
-    vsnprintf(buf, sizeof(buf), format, ap);
+    memset(buf, 0, sizeof(buf));
+    len = vsnprintf(buf, sizeof(buf), format, ap);
 
// Convert any chars less than 32d or greater than 126d to hex
-      for (i = 0; i < sizeof(buf); i++) {
+      for (i = 0; i < len; i++) {
       memset(temp, 0, 6);
       cTemp = (unsigned char) buf[i];
-      if ((cTemp < 32 && cTemp > 0) || cTemp > 126) {
+      if ((cTemp < 32 && cTemp >= 0) || cTemp > 126) {
         sprintf(temp, "[%02X]", cTemp);
       } else
         sprintf(temp, "%c", cTemp);
 
       if (strlen(bufOut) + 6 < sizeof(bufOut))
         strncat(bufOut, temp, 6);
       else
         break;
     }
     fprintf(st, "%s\n", bufOut);
     va_end(ap);
   }
   return;
}

After making my changes, the same POST call as above now prints as:

HTTP request sent:[0A]POST /c/portal_public/login HTTP/1.0[0D][0A]Host: 127.0.0.1[0D][0A]User-Agent: Mozilla/5.0 (Hydra)[0D][0A]Content-Length: 126[0D][0A]Content-Type: application/x-www-form-urlencoded[0D][0A]Cookie: JSESSIONID=970911342D87ED153D415A6BA5F5AED2; SHARED_SESSION_ID=J3W12XKDXS36[0D][0A][0D][0A]my_account_cmd=auth&referer=%2Fc&my_account_r_m=false
&password=bill&my_account_login=bill@example.org&[0A]

As an additional nicety, I also changed > to >= on line 712 so that \0 displays as [00] too instead of directly writing NULL to stdout. Since the return value of vsnprintf() is now read to see how many chars are used – this should also allow printf-%c with null chars to be included in the output from a hydra_report_debug() print call.

The updated code is reflect in GitHub check-in 795e9c7 & Merge / Pull and will become part of mainline Hydra once version 8.5 is released.

Code Etymology

Hydra’s hydra_report_debug() function is based off of the writeError() function from different credential auditing software, Medusa. The latest version of Medusa is v2.2-rc1 from around 2012, but the software is apparently not actively maintained. From the code snippet here and the 2007 release date announcement of version 1.4, we know that this security issue in Hydra & Medusa has been around for at least 10 years.

The current version of Medusa in GitHub writes the whole buffer with NULLs but still walks the entire buffer even when unneeded. I’m unsure when this whole-buffer-NULLing code change occurred, but reading the return value from vsnprintf() as shown above is still a better option so that NULLs are not printed to stdout. I believe Medusa isn’t injectable in its current un-finalized form, but I backported my updates anyhow and submitted a Merge / Pull Request to the maintainer.

Wrap-Up

When coding in C, be aware of the boundaries of data buffers and with NULL termination on strings. Tools such as Valgrind of Electric Fence provide some assistance in catching these issues. Though not every issue will be identified, some potentially damaging issues may be detected and then corrected before release by using this free tools.