3.1. Extending HydraHydra is a popular tool written by Van Hauser (http://www.thc.org/) for testing networked services for weak username and password combinations. This technique, commonly known as brute-force testing, is valuable for ensuring that network services and systems are not vulnerable to password-guessing attacks due to weak username and password combinations. Although Hydra supports a number of different protocols for testing, most likely you'll want to test services available on your network that Hydra doesn't support. In this section we will demonstrate how to add a module for testing Simple Mail Transport Protocol (SMTP) authentication. You could use this to determine if weak passwords exist in your email user base and close this potential exposure before a spammer takes advantage of it. Hydra is freely available for noncommercial use and for commercial use with proper acknowledgment. You can download it from http://www.thc.org/thc-hydra/. The module described in this section is included in Hydra Version 4.2. 3.1.1. Overview of HydraHydra is a very popular tool primarily because of the wide variety of protocols it supports and because its parallel nature divides password-testing tasks among a user-definable number of tasks. As of Version 4.4, Hydra supports the following protocols:
Hydra is primarily a command-line security-testing tool, and as such you can call it from within recent versions of Nessus to perform login (username) and password testing on services identified by Nessus. In addition to using the tool through Nessus, recent versions of Hydra also come with a graphical GTK user interface for platforms supporting the GTK toolkit. 3.1.2. Overview of SMTP AuthenticationIn this section we will demonstrate how to add SMTP authentication protocol support to Hydra. Mail servers commonly use SMTP authentication to identify a user as being valid prior to accepting email for delivery. A number of different standards for SMTP authentication exist, many of which are not RFC standards. We are demonstrating an authentication method using the AUTH LOGIN method, as shown in Example 3-1. Example 3-1. An SMTP AUTH session220-mail.xxxxxxxx.com ESMTP Exim 4.34 #1 Wed, 23 Jun 2004 17:35:13 -0700 EHLO mail.myserver.com 250-mail.xxxxxxxx.com Hello mail.myserver.com [192.168.0.156] 250-SIZE 52428800 250-PIPELINING 250-AUTH PLAIN LOGIN 250-STARTTLS 250 HELP AUTH LOGIN 334 VXNlcm5hbWU6 bXl1c2VybmFtZQ== 334 UGFzc3dvcmQ6 bXlwYXNzd29yZA== 235 Authentication succeeded The AUTH LOGIN authentication method is well supported by many common SMTP servers, and as such, it is a good protocol to use. The protocol is a simple process that uses unencrypted credentials. Even though the protocol is insecure, a number of mail servers support it in their default configurations as a lowest-common-denominator protocol for SMTP authentication. The protocol can be demonstrated by using the telnet command to port 25 on an available mail server. The mail server then responds with a connection message: 220-mail.xxxxxxxx.com ESMTP Exim 4.34 #1 Wed, 23 Jun 2004 17:35:13 -0700 The mail server responds with a header containing the SMTP response code 220. Similar to the HTTP protocol, SMTP uses a numbered response code system, as shown in Table 3-1.
In this case, the mail server (or more accurately, the MTA, or Mail Transfer Agent) is running the open source Exim service. Then we need to start an email session with the mail server by using the EHLO command with our Internet hostname, as shown here: 250-mail.xxxxxxxx.com Hello mail.myserver.com [192.168.0.156] 250-SIZE 52428800 250-PIPELINING 250-AUTH PLAIN LOGIN 250-STARTTLS 250 HELP The EHLO command informs the server that we want to use the Extended Simple Mail Transfer Protocol (ESMTP) and determines the SMTP extensions supported by the mail server, including the types of authentication (if any) supported by the server we are interrogating. The AUTH keyword is followed by two different types of authentication, indicating that this server supports both the PLAIN and LOGIN authentication methods. This command is important, as RFC-compliant mail servers should respond with an error message such as 503 AUTH command used when not advertised if the AUTH keyword is used without a preceding EHLO command. Then we send the mail server an AUTH LOGIN command to start the authentication process with the server: AUTH LOGIN 334 VXNlcm5hbWU6 The AUTH LOGIN command instructs the server that the client wants to begin SMTP authentication using the LOGIN method. The server has responded with the 334 status code, and a Base64-encoded representation of the string Username: to prompt the client to supply the username. The client supplies the username for authentication encoded using Base64 encoding. The username used here is myusername: bXl1c2VybmFtZQ== 334 UGFzc3dvcmQ6 Then the server responds with a Base64-encoded representation of the string Password: to prompt the client to supply the password. The client supplies the password encoded using Base64 encoding. The password used in this example is mypassword: bXlwYXNzd29yZA== 235 Authentication succeeded Providing the username and password supplied are correct, the server responds with a 2xx status code. If the username and password combination is incorrect the server responds with a 5xx response code. 3.1.3. Adding Additional Protocols to HydraHydra is structured in a very modular way, and therefore adding support for an additional protocol requires that Hydra support the defined module interface. Each protocol is implemented in a file called hydra-<service name>.c containing a function prototype: void service_<service name> (unsigned long int ip, int sp, unsigned char options, char *miscptr, FILE *fp, int port); The options passed to the service function are outlined in Table 3-2.
Once the service file has been written, integrating the modules into Hydra is simple:
Note that this will not add the new module into the xhydra graphical interface. Also note that you will need to patch this to support the ability to call the new module. 3.1.4. Implementing SMTP-AUTH in HydraEvery protocol Hydra supports needs to define the following variables and include files: #include "hydra-mod.h" extern char *HYDRA_EXIT; char *buf; The hydra-mod.h include file defines the functions the module accesses while running. The HYDRA_EXIT string is a value returned by some Hydra functions. The buf pointer is used in hydra-smtpauth.c as a temporary buffer for data received. void service_smtpauth(unsigned long int ip, int sp, unsigned char options, char *miscptr, FILE * fp, int port) { int run = 1, next_run, sock = -1; int myport = PORT_SMTPAUTH, mysslport = PORT_SMTPAUTH_SSL; char *buffer = "EHLO hydra\r\n"; The run and next_run variables are used to control the state of the testing session. service_smtpauth follows a convention similar to many of the other text-based protocols supported in Hydra, whereby it is possible to connect and try multiple sets of credentials. The run values are specified in Table 3-3.
The sock variable is used to track the status of the connection to the service. The PORT_SMTPAUTH and PORT_SMTPAUTH_SSL values have been added to the hydra.h file, and they are the ports SMTP runs on normally and when run over SSL (ports 25 and 465, respectively). The string buffer is the SMTP EHLO command to be sent to the server. /* keep track of socket for login/password */ hydra_register_socket(sp); /* get the next login/password pair to test */ if (memcmp(hydra_get_next_pair( ), &HYDRA_EXIT, sizeof(HYDRA_EXIT)) == 0) return; The hydra_register_socket()function is required to register the socket sp supplied to the module with the Hydra functions used to obtain the login (username) and password pairs for testing. Due to the parallelized structure of Hydra, each running task obtains separate login (username) and password combinations to optimize testing. The hydra_get_next_pair( ) function is used to obtain the next pair of credentials for testing. This function returns HYDRA_EXIT on failure. These credentials are later obtained as strings using the functions hydra_get_next_login() and hydra_get_next_password(). /* permanent loop keyed on the run variable */ while (1) { switch (run) { case 1: /* connect and service init function */ /* if we are already connected */ if (sock >= 0) sock = hydra_disconnect(sock); usleep(300000); The run variable is used here in a switch statement to control the state of the connection to the server. The values used for the run variable are shown in Table 3-3. This functionality ensures that if a connection to the server is already in place, it is disconnected with hydra_disconnect(). In this way, the module can ensure that a new connection is made if an error occurs by ensuring the run variable is set to 1. /* determine port to connect to */ if ((options & OPTION_SSL) == 0) { if (port != 0) myport = port; sock = hydra_connect_tcp(ip, myport); port = myport; } else { if (port != 0) mysslport = port; sock = hydra_connect_ssl(ip, mysslport); port = myport; } If the user has not specified the use of SSL, the module connects to the default port for the service, or it connects to the user-defined port if it has been supplied using hydra_connect_tcp(). If SSL has been specified, the default SSL port is used unless the user has specified a custom port, and the connection is made using hydra_connect_ssl() . For protocols using UDP, such as SNMP, Hydra also supports the hydra_connect_udp() function. /* see if connect succeeded */ if (sock < 0) { hydra_report(stderr, "Error: Child with pid %d can't connect\n", (int) getpid( )); hydra_child_exit(1); } If the connection did not succeed, Hydra will print an error to STDERR. The hydra_report( ) function is a synonym for fprintf. The hydra_child_exit() function reports the exit status of the child task, as in Table 3-4.
Once the connection is made, many protocols send some form of data as a banner or to begin authentication. /* consume any data waiting in buffer */ while (hydra_data_ready(sock)) { if((buf = hydra_receive_line(sock)) == NULL) exit(-1); free(buf); } The hydra_data_ready() function returns regardless of whether data is to be read from the connected socket. If data is to be read, hydra_receive_line() reads the data in the receive buffer from the socket, and the data is thrown away. This is done to ensure that any banner messages are consumed from the buffer prior to any other actions. Note that we free the buffer that was read. It is important to perform this step on all data reads to avoid memory leaks. In addition to the hydra_receive_line( ) function, Hydra also has the simpler hydra_recv( ) function that is useful if using a binary protocol. /* send EHLO command */ if (hydra_send(sock, buffer, strlen(buffer), 0) < 0) exit(-1); The hydra_send( ) function is used to send the EHLO command to the server. /* see if there was any response */ if ((buf = hydra_receive_line(sock)) == NULL) exit(-1); /* see if the LOGIN keyword is in the response */ if (strstr(buf, "LOGIN") == NULL) { /* check AUTH LOGIN supported */ hydra_report(stderr, "Error: SMTP AUTH LOGIN not supported: %s\n", buf); hydra_child_exit(2); exit(-1); } free(buf); next_run = 2; /* run crack next */ break; The buf buffer received in response to the EHLO command is checked to see if it contains the word LOGIN. This is done to validate whether the server advertises the presence of the AUTH LOGIN command. If the command is present, the next_run value (and therefore the next value of the run variable) is set to 2, which initiates the testing process on the next cycle through the loop. case 2: /* run the cracking function */ next_run = start_smtpauth(sock, ip, port, options, miscptr, fp); break; Where the run variable is 2, the connection has been established and the testing function is started, as per Table 3-3. case 3: /* clean exit */ /* if connected */ if (sock >= 0) sock = hydra_disconnect(sock); hydra_child_exit(0); return; Where the run variable is 3, the socket is disconnected and the task exits cleanly, as per Table 3-3. default: hydra_report(stderr, "Caught unknown return code, exiting!\n"); hydra_child_exit(0); exit(-1); } run = next_run; /* next step dependant on return from cracking function */ } } The service_smtpauth() function exits if the start_smtpauth() function returns a value other than 1, 2, or 3. This ensures that the simple state machine controlled by the run variable is always in one of the three defined statesconnecting/reconnecting, testing, or disconnecting. Where the connection has been established successfully, and the run variable is set to 2, the service_smtpauth( ) function calls the start_smtpauth( ) function to perform a single testing instance. int start_smtpauth(int s, unsigned long int ip, int port, unsigned char options, char *miscptr, FILE *fp) Here the start_smtpauth( ) function is passed the same values as those passed to the service_smtpauth( ) function. This function is not called from outside of this module; however, the naming and structure throughout the existing protocols supported in Hydra largely follow this convention. char *empty = ""; char *login, *pass, buffer[300], buffer2[300]; /* get login and password from the pair fetched */ if (strlen(login = hydra_get_next_login( )) == 0) login = empty; if (strlen(pass = hydra_get_next_password( )) == 0) pass = empty; The hydra_get_next_login() and hydra_get_next_password() functions are used to obtain the login (username) and password pair to be used for this instance of testing. These functions rely on the hydra_get_next_pair() function having been run to first read the login and password pair from the internal socket. /* consume any remaining data in the buffer */ while (hydra_data_ready(s) > 0) { if ((buf = hydra_receive_line(s)) == NULL) return (1); free(buf); /* make sure we free memory we use */ } Any data returned from the server remaining in the buffer is read and thrown away. If an error occurs while reading data, the function returns 1, which causes the service_smtpauth( ) function to attempt to reconnect to the server. /* send AUTH LOGIN command */ sprintf(buffer, "AUTH LOGIN\r\n"); if (hydra_send(s, buffer, strlen(buffer), 0) < 0) { return 1; } The AUTH LOGIN command is sent to start an authentication attempt. If this fails, you should try to reconnect again. /* if no response received */ if ((buf = hydra_receive_line(s)) == NULL) return 1; /* make sure we got a 334 response code (asking for username) */ if (strstr(buf, "334") == NULL) { hydra_report(stderr, "Error: SMTP AUTH LOGIN error: %s\n", buf); free(buf); return 3; } free(buf); If the response from the mail server is something other than 334 VXNlcm5hbWU6, you have experienced a protocol error, so you should exit. This might occur if the mail server does not support the authentication method you are attempting. /* base64 encode the username - also making sure string is < 250 */ sprintf(buffer2, "%.250s", login); hydra_tobase64((unsigned char *) buffer2); sprintf(buffer, "%.250s\r\n", buffer2); /* send the username */ if (hydra_send(s, buffer, strlen(buffer), 0) < 0) { return 1; } Send the login (username) obtained from hydra_get_next_login(). This is Base64-encoded using hydra_tobase64() . A hydra_conv64( ) function exists for Base64-encoding single characters, if required. Note that we are ensuring that the user-supplied data is cut off at 250 characters to avoid a potential buffer overflow issue. /* if no response received */ if ((buf = hydra_receive_line(s)) == NULL) return (1); /* make sure we get a 334 - asking for password */ if (strstr(buf, "334") == NULL) { hydra_report(stderr, "Error: SMTP AUTH LOGIN error: %s\n", buf); free(buf); return (3); } free(buf); /* base64 encode the password */ sprintf(buffer2, "%.250s", pass); hydra_tobase64((unsigned char *) buffer2); sprintf(buffer, "%.250s\r\n", buffer2); /* send the password */ if (hydra_send(s, buffer, strlen(buffer), 0) < 0) { return 1; } /* if no response received */ if ((buf = hydra_receive_line(s)) == NULL) return (1); The password received from hydra_get_next_password() is sent to the mail server the same way in which the username was sent. /* if authentication was successful */ if (strstr(buf, "235") != NULL) { /* report the found credentials */ hydra_report_found_host(port, ip, "smtpauth", fp); hydra_completed_pair_found( ); free(buf); if (memcmp(hydra_get_next_pair( ), &HYDRA_EXIT, sizeof(HYDRA_EXIT)) == 0) return 3; return 1; } If the 235 Authentication succeeded response is received from the mail server, the successful login (username) and password combination is reported using the hydra_report_found_host() function. The hydra_completed_pair_found() function is used to communicate on the internal socket that the current credentials were successful. Then the hydra_get_next_pair() function fetches the next pair of credentials for use and causes the module to exit cleanly if no credential pairs remain. free(buf); /* otherwise, we're finished with this pair anyway */ hydra_completed_pair( ); if (memcmp(hydra_get_next_pair( ), &HYDRA_EXIT, sizeof(HYDRA_EXIT)) == 0) return 3; return 2; } If the authentication attempt was not successful, the completed status of the pair is communicated using the hydra_completed_pair() function. Then the hydra_get_next_pair( ) function is used to fetch the next pair, and causes the module to exit cleanly if no credential pairs remain to be tested by this task. 3.1.5. Complete Source to hydra-smtpauth.cThe complete source to the SMTP authentication module as described earlier in Section 3.1.4 is contained in the src/hydra-smtpauth.c file in the Hydra distribution in Versions 4.2 and above. 3.1.6. Quick Reference to Hydra FunctionsAlthough the SMTP authentication module highlighted most of the functionality Hydra supplies for use in modules, we have not yet covered all of Hydra's functionality. Because developer documentation of the functions is not available for Hydra modules, this section provides a quick reference to the Hydra functions available as of Version 4.4. In addition to the functions described next, Hydra also contains files for supporting the MD4 and DES algorithms. These files are not part of the Hydra module structure, and as such are not covered here.
Valid values for code are shown in Table 3-4. Supply the value for code as 0 for normal exit, 1 for no connection possible, and 2 for protocol or service error.
hydra_register_socket( ) should be called with the sp variable passed into the module.
The hydra_get_next_pair( ) function returns a pointer to the next credential pair with the pair formatted as login\0password. These can then be fetched cleanly using hydra_get_next_login( ) and hydra_get_next_password( ). The hydra_get_next_pair( ) function returns HYDRA_EXIT on failure, and HYDRA_EMPTY where no value was supplied (for example, when testing for blank passwords).
This function returns a pointer to the login value fetched by the hydra_get_next_pair( ) function.
This function returns a pointer to the password value fetched by the hydra_get_next_pair( ) function.
This is run when the current pair does not appear to be a valid login/password combination on the service being tested.
This is run when the current pair has been found to be a valid login/password combination on the service being tested.
This function is used to output the found credentials to the user. port is the port the service was tested on, svc is the name of the service (commonly a literal string such as smtpauth), and fp is the fp value Hydra supplied to the module.
This function is similar to hydra_report_found( ), except the IP address of the server tested is displayed. It is used to output the found credentials to the user. port is the port the service was tested on, ip is the IP address, svc is the name of the service (commonly a literal string such as smtpauth), and fp is the fp value Hydra supplied to the module.
This function is similar to hydra_report_found_host(), with the addition of a message to be displayed. It is used to output the found credentials to the user. port is the port the service was tested on, ip is the IP address, svc is the name of the service (commonly a literal string such as smtpauth), fp is the fp value Hydra supplied to the module, and msg is a message to be displayed to the user.
This function makes a connection to the host defined by the IP address host, on port port, using TCP. host is the ip value passed into the module, and the port value usually is a standard port for the service; however, it also can be user-defined. The function returns a socket value used in sending and receiving operations, or -1 on error.
This function makes a connection to the host defined by the IP address host, on port port, using SSL. host is the ip value passed into the module, and the port value is either the standard SSL port for the service, or user-defined. The function returns a socket value used in sending and receiving operations, or -1 on error.
This function sets up a socket for communicating to the host defined by the IP address host on port port, using UDP. host is the ip value passed into the module, and the port value is either the standard port for the service, or user-defined. The function returns a socket value used in sending and receiving operations, or -1 on error.
This function closes the socket supplied and returns -1.
This function waits up to sec seconds and usec microseconds to see if the socket socket is available for writing. This function returns a value greater than zero if the socket is ready for writing, 0 if the socket is not ready for writing, and -1 on error.
This function calls hydra_data_ready_writing_timed() to see if the socket socket is available for writing. This function returns a value greater than zero if the socket is ready for writing, 0 if the socket is not ready for writing, and -1 on error.
This function waits up to sec seconds and usec microseconds to see if the socket socket has data available for reading. This function returns a value greater than zero if the socket has data for reading, 0 if no data is available, and -1 on error.
This function calls hydra_data_ready_timed( ) to see if the socket socket has data to be read. This function returns a value greater than zero if the socket has data for reading, 0 if no data is available, and -1 on error.
This function reads up to length data from the socket socket into the buffer buf. The function returns the amount of data read, or -1 on error. No translation of any type is done to the data received. This function should be used for binary protocols, as hydra_receive_line( ) performs some translation on data read.
This function attempts to read all data available from the socket socket. It returns a pointer to a buffer which is allocated within the function. These buffers should be deallocated using a free( ) call after use to conserve memory usage. All NULL characters in the data received are translated into space characters (0x20).
This function sends the data in the buffer buf, of length size, out on the socket defined by socket. The options variable is not commonly used (it is set to 0), but is the flags variable for the underlying socket's API send( ) command. This function returns the amount of data sent, or -1 on error.
This function converts the buffer pointed to by buf to lowercase. The function always returns 1.
This function returns the Base64-encoded representation of the character supplied to the function in the in parameter, or 0 on error.
This function converts the string pointed to by buf to Base64 encoding. If an error occurs during encoding, the value pointed to by buf is in an undefined state.
This function takes the data in string, of length length, and prints a hex and ASCII table to standard output. This can be very useful for debugging a module under development . |