ninja-0.1.3.orig/0000755000000000000000000000000011306164652010443 5ustar ninja-0.1.3.orig/Makefile0000644000000000000000000000077710305400723012104 0ustar OBJS = main.o mloop.o proc.o user.o log.o wlist.o signal.o config.o DESTDIR = /usr/local/bin/ MANDIR = /usr/local/man/man8/ ETCDIR = /etc/ninja/ CC = gcc INSTALL = install all: ninja ninja: cd src && make && mv ninja .. clean: rm -f ninja src/*.o install: mkdir -p $(DESTDIR) mkdir -p $(MANDIR) mkdir -p $(ETCDIR) $(INSTALL) ninja $(DESTDIR) $(INSTALL) ninja.8 $(MANDIR) $(INSTALL) examples/config/default.conf $(ETCDIR) chmod 700 $(ETCDIR) uninstall: rm -f $(DESTDIR)/ninja $(MANDIR)/ninja.8 ninja-0.1.3.orig/src/0000755000000000000000000000000011306164652011232 5ustar ninja-0.1.3.orig/src/Makefile0000644000000000000000000000033710300760174012667 0ustar OBJS = main.o mloop.o proc.o user.o log.o wlist.o signal.o config.o file.o CC = gcc CFLAGS = -Wall -O2 all: ninja %.o: %.c $(CC) $(CFLAGS) -c $^ -o $@ ninja: $(OBJS) $(CC) $(CFLAGS) $^ -o $@ clean: rm -f *.o ninja ninja-0.1.3.orig/src/mloop.c0000644000000000000000000001450310301200101012475 0ustar #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "proc.h" extern struct options global_opts; void main_loop(int mgroup, int cycledelay, char *wlist) { int root_pids[PID_MAX]; int *active_pids; int i, j; int rinitw_trap; int file_mess; struct proc_info pi; struct proc_info ppi; pid_t evil_pid; pid_t evil_ppid; char evil_pid_name[FILENAME_MAX+1]; char evil_ppid_name[FILENAME_MAX+1]; LOG("generating initial pid array.."); /* run through all possible pids and set up our inital pid array */ for (i = 0; i < PID_MAX; i++) { root_pids[i] = 0; if ( (get_proc_info(i, &pi) != -1) && (pi.uid == 0 || pi.gid == 0) ) root_pids[i] = 1; } LOG("now monitoring process activity"); /* this is the main loop and we should never abort from it.. */ for (;;) { active_pids = get_active_pids(); /* run though all possible pids.. */ for (i = global_opts.soffset; i < PID_MAX; i++) { if (active_pids[i] == 0) continue; /* look for pids running as root who are not already in our root * pid array */ if ( (get_proc_info(i, &pi) != -1) && (pi.uid == 0 || pi.gid == 0) ) { if (root_pids[i] != 1) { /* we found a new unknown root process, let's gather and log some * information about it and its parent */ root_pids[i] = 1; /* XXX: this should perhaps be handled better */ if (get_proc_info(pi.ppid, &ppi) == -1) { root_pids[i] = 0; continue; } /* require processes controlled by init to be whitelisted? */ rinitw_trap = 0; if ( (global_opts.rinitw == TRUE) && (pi.ppid == 1) && (!wlist_match(wlist, pi.pid, ppi.uid, ppi.gid)) ) { LOG("INIT CONTROLLED PROCESS NOT WHITELISTED -- JUMPING TO KILL"); rinitw_trap = 1; } else { /* ignore root spawned process? */ if (ppi.uid == 0 && global_opts.noroot == TRUE) goto loop_end; /* ignore whitelisted process? */ if (global_opts.wlist[0] != '\0' && wlist_match(wlist, pi.pid, ppi.uid, ppi.gid)) { if (global_opts.logwlist == TRUE) LOG("WHITELIST: %s[%d] ppid_uid=%d", pi.name, pi.pid, ppi.uid); goto loop_end; } } LOG("NEW ROOT PROCESS: %s[%d] ppid=%d uid=%d gid=%d", pi.name, pi.pid, pi.ppid, pi.uid, pi.gid); evil_pid = pi.pid; evil_ppid = pi.ppid; snprintf(evil_pid_name, FILENAME_MAX, "%s", pi.name); if (get_proc_info(pi.ppid, &pi) != -1) { LOG(" - ppid uid=%d(%s) gid=%d ppid=%d", pi.uid, get_user(pi.uid), pi.gid, pi.ppid); snprintf(evil_ppid_name, FILENAME_MAX, "%s", pi.name); if (get_user(pi.uid) == NULL) LOG(" ! WARNING: unable to get username for uid %d", pi.uid); /* sanity check passwd and group files */ file_mess = 0; if (file_isregular("/etc/passwd") != 1) { LOG(" ! WARNING: /etc/passwd has been modified: no longer a regular file"); file_mess++; } if (file_isregular("/etc/group") != 1) { LOG(" ! WARNING: /etc/group has been modified: no longer a regular file"); file_mess++; } if (file_size("/etc/passwd") < 1) { LOG(" ! WARNING: /etc/passwd has a size of 0 bytes"); file_mess++; } if (file_mess != 0) { LOG(" + Detected problems with passwd and/or group file, jumping to kill"); goto kill; } /* check if the user who spawned this root process is in our * "magic group" */ if (!check_group(get_user(pi.uid), mgroup) && rinitw_trap == 0) { LOG(" + %s is in magic group, all OK!", get_user(pi.uid)); } else { /* the user was NOT in our magic group.. let's kill the * offending process, and its parent */ kill: LOG(" + UNAUTHORIZED PROCESS DETECTED: %s[%d] (parent: %s[%d])", evil_pid_name, evil_pid, evil_ppid_name, evil_ppid); if (global_opts.nokill == FALSE) { LOG(" - sending signal SIGKILL to pid %d", evil_pid); kill(evil_pid, SIGKILL); if (global_opts.nokillppid == FALSE && rinitw_trap == 0) { LOG(" - sending signal SIGKILL to ppid %d", evil_ppid); kill(evil_ppid, SIGKILL); } LOG(" * offending process(es) terminated"); } else { LOG(" - nokill option set, no signals sent"); } if (global_opts.command[0] != 0x00) { char extcmd[256]; snprintf(extcmd, sizeof extcmd, "%s %s", global_opts.command, get_user(pi.uid)); LOG(" - executing '%s'", extcmd); switch(fork()) { case 0: system(extcmd); _exit(EXIT_SUCCESS); break; case -1: LOG(" - ERROR: fork() failed when trying to execute command"); break; } } /* .. */ } } } } } loop_end: /* remove lost root pids form root pid array */ for (j = 0; j < PID_MAX; j++) { if (root_pids[j] == 1) { if (active_pids[j] != 1) root_pids[j] = 0; } } /* sleep */ if (cycledelay <= 0) usleep(50000); else sleep(cycledelay); } } int* get_active_pids(void) { DIR *dp; struct dirent *d; static int active_pids[PID_MAX]; int pidnr; int i; if ((dp = opendir("/proc")) == NULL) return NULL; for (i = 0; i < PID_MAX; i++) active_pids[i] = 0; while ((d = readdir(dp)) != NULL) { pidnr = strtol(d->d_name, NULL, 0); if (pidnr > 0) active_pids[pidnr] = 1; } closedir(dp); return active_pids; } ninja-0.1.3.orig/src/wlist.c0000644000000000000000000001012611306164643012540 0ustar #include #include #include #include #include #include #include #include #include #include #include "common.h" off_t fd_len(int fd) { off_t len; len = lseek(fd, 0, SEEK_END); lseek(fd, 0, SEEK_SET); return len; } char* wlist_read(char *path) { int wlist_fd; char *wlist; if ((wlist_fd = open(path, O_RDONLY)) == -1) return NULL; if ((wlist = mmap(0, fd_len(wlist_fd), PROT_READ, MAP_PRIVATE, wlist_fd, 0)) == MAP_FAILED) return NULL; return wlist; } char* get_map_exec_offset(void) { FILE *fp; static char buf[16] = "\0"; if (!buf[0] == '\0') return buf; if ((fp = fopen("/proc/1/maps", "r")) == NULL) { LOG("warning: unable to get map_exec_offset from /proc/1/maps: whitelisting will not work"); return NULL; } fgets(buf, 9, fp); fclose(fp); return buf; } char* get_map_name(pid_t pid) { FILE *fd; char mfile[64]; char mfield[5][25]; char mbuf[PATH_MAX+8+(25*5)+1]; static char map_name[PATH_MAX+8]; snprintf(mfile, sizeof mfile, "/proc/%d/maps", pid); if ((fd = fopen(mfile, "r")) == NULL) return NULL; for (;;) { memset(map_name, '\0', sizeof map_name); memset(mbuf, '\0', sizeof mbuf); if (fgets(mbuf, sizeof mbuf, fd) == NULL) return NULL; sscanf(mbuf, "%24s %24s %24s %24s %24s %s", mfield[0], mfield[1], mfield[2], mfield[3], mfield[4], map_name); if (!map_name) { /* return NULL; */ continue; } if (!strncmp(mfield[0], get_map_exec_offset(), 8)) return map_name; } return NULL; } int wlist_match(char *wlist, pid_t pid, uid_t uid, gid_t gid) { unsigned long wlist_pos = 0; int i = 0; #ifdef USE_READLINK char map_name[PATH_MAX+8]; char link_path[64]; #else char *map_name; #endif char wlist_entry[512]; char wlist_exec[512]; char wlist_groups[512]; char wlist_users[512]; #ifdef USE_READLINK memset(map_name, '\0', sizeof map_name); snprintf(link_path, sizeof link_path, "/proc/%d/exe", pid); if (readlink(link_path, map_name, sizeof map_name - 1) == -1) return 0; #else if ((map_name = get_map_name(pid)) == NULL) return 0; #endif for (;;) { wlist_entry[i++] = wlist[wlist_pos++]; if (wlist[wlist_pos] == '\0') return 0; if (wlist[wlist_pos] == '\n') { wlist_entry[i] = '\0'; memset(wlist_exec, '\0', sizeof wlist_exec); memset(wlist_groups, '\0', sizeof wlist_groups); memset(wlist_users, '\0', sizeof wlist_users); chreplace(wlist_entry, ':', ' '); sscanf(wlist_entry, "%511s %511s %511s", wlist_exec, wlist_groups, wlist_users); /* map name (executable) matces */ if (!strcmp(map_name, wlist_exec)) { /* check users */ if (user_match(get_user(uid), wlist_users) > 0) return 1; /* check groups */ if (group_match(uid, wlist_groups, gid) > 0) return 1; } i = 0; wlist_pos++; if (wlist_pos >= strlen(wlist)) break; } } return 0; } void chreplace(char *s, char c1, char c2) { int i; int slen; slen = strlen(s); for (i = 0; i < slen; i++) if (s[i] == c1) s[i] = c2; return; } int user_match(char *item, char *users) { int i, j; int match = 0; char mtr[32]; // XXX: ... memset(mtr, '\0', sizeof mtr); for (i = 0, j = 0; i < strlen(users) + 1; i++) { if (users[i] == ',' || i >= strlen(users)) { if (!strcmp(mtr, item)) match++; memset(mtr, '\0', sizeof mtr); j = 0; } else { mtr[j++] = users[i]; } } return match; } int group_match(uid_t uid, char *groups, gid_t pwent_gid) { int i, j; char mtr[32]; // XXX: ... memset(mtr, '\0', sizeof mtr); for (i = 0, j = 0; i < strlen(groups) + 1; i++) { if (groups[i] == ',' || i >= strlen(groups)) { if (!check_group(get_user(uid), get_gid(mtr)) || get_gid(mtr) == pwent_gid) return 1; memset(mtr, '\0', sizeof mtr); j = 0; } else { mtr[j++] = groups[i]; } } return 0; } ninja-0.1.3.orig/src/log.c0000644000000000000000000000241011306164544012154 0ustar #include #include #include #include #include #include #include "common.h" #define DIE_PREFIX "die: " #define LOG_PREFIX "log: " extern struct options global_opts; void LOG(char *fmt, ...) { va_list args; FILE *fd; if (global_opts.quiet == FALSE) { va_start(args, fmt); #ifdef LOG_PREFIX fprintf(stdout, "%s", LOG_PREFIX); #endif vfprintf(stdout, fmt, args); puts(""); va_end(args); } if (global_opts.logfile[0] != 0x00 && file_isregular(global_opts.logfile) == 1) { va_start(args, fmt); fd = fopen(global_opts.logfile, "a"); if (fd != NULL) { fprintf(fd, "[%s ", replace(timestamp(), '\n', ']')); vfprintf(fd, fmt, args); fprintf(fd, "\n"); fclose(fd); } va_end(args); } return; } void die(char *fmt, ...) { va_list args; #ifdef DIE_PREFIX fprintf(stderr, "%s", DIE_PREFIX); #endif va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); puts(""); exit(EXIT_FAILURE); } char* timestamp(void) { time_t t = time(0); return ctime(&t); } char* replace(char *string, char c1, char c2) { int i; for (i = 0; i < strlen(string); i++) if (string[i] == c1) string[i] = c2; return string; } ninja-0.1.3.orig/src/proto.h0000644000000000000000000000201310301155214012526 0ustar #include "proc.h" /* main.c */ int main(int argc, char **argv); void help(void); /* mloop.c */ void main_loop(int mgroup, int cycledelay, char *wlist); int* get_active_pids(void); /* user.c */ int check_group(const char *user, const gid_t gid); char* get_user(uid_t uid); gid_t get_gid(char *name); /* proc.c */ int get_proc_info(const pid_t pid, struct proc_info *pi); /* log.c */ void LOG(char *fmt, ...); void die(char *fmt, ...); char* timestamp(void); char* replace(char *string, char c1, char c2); /* wlist.c */ off_t fd_len(int fd); char* wlist_read(char *path); int wlist_match(char *wlist, pid_t pid, uid_t uid, gid_t gid); char* get_map_name(pid_t pid); void chreplace(char *s, char c1, char c2); int user_match(char *item, char *users); int group_match(uid_t uid, char *groups, gid_t pwent_gid); char* get_map_exec_offset(void); /* signal.c */ void sighandler(int s); /* config.c */ int conf_read(const char *path); void stolower(char *s); /* file.c */ int file_isregular(char *fpath); long file_size(char *fpath); ninja-0.1.3.orig/src/proc.h0000644000000000000000000000021010206232504012325 0ustar #ifndef _PROC_H #define _PROC_H struct proc_info { pid_t pid; pid_t ppid; uid_t uid; gid_t gid; char name[128]; }; #endif ninja-0.1.3.orig/src/config.c0000644000000000000000000000630610305376324012647 0ustar #include #include #include #include #include "common.h" extern struct options global_opts; int conf_read(const char *path) { FILE *fp; char cf_option[65]; char cf_eq; char cf_value[161]; char buf[256]; unsigned long line_num = 0; int i; int ignore_line; if ((fp = fopen(path, "r")) == NULL) return -1; for (;;) { memset(buf, 0x00, sizeof buf); if (fgets(buf, sizeof buf, fp) == NULL) break; line_num++; ignore_line = 0; for (i = 0; i < strlen(buf); i++) { if (buf[i] == ' ' || buf[i] == '\t') continue; if (buf[i] == '#' || buf[i] == '\n') ignore_line = 1; break; } if (ignore_line == 1) continue; cf_option[0] = 0x00; cf_eq = 0x00; cf_value[0] = 0x00; sscanf(buf, "%64s %c %160s\n", cf_option, &cf_eq, cf_value); stolower(cf_option); stolower(cf_value); if (!strcmp(cf_value, "true")) sprintf(cf_value, "yes"); if (!strcmp(cf_value, "false")) sprintf(cf_value, "no"); if (!strcmp(cf_option, "daemon") && !strcmp(cf_value, "yes")) global_opts.daemon = TRUE; else if (!strcmp(cf_option, "daemon") && !strcmp(cf_value, "no")) global_opts.daemon = FALSE; else if (!strcmp(cf_option, "interval")) global_opts.interval = atoi(cf_value); else if (!strcmp(cf_option, "group")) global_opts.group = atoi(cf_value); else if (!strcmp(cf_option, "logfile")) snprintf(global_opts.logfile, sizeof global_opts.logfile, "%s", cf_value); else if (!strcmp(cf_option, "whitelist")) snprintf(global_opts.wlist, sizeof global_opts.wlist, "%s", cf_value); else if (!strcmp(cf_option, "external_command")) snprintf(global_opts.command, sizeof global_opts.command, "%s", cf_value); else if (!strcmp(cf_option, "proc_scan_offset")) global_opts.soffset = atoi(cf_value); else if (!strcmp(cf_option, "no_kill") && !strcmp(cf_value, "yes")) global_opts.nokill = TRUE; else if (!strcmp(cf_option, "no_kill") && !strcmp(cf_value, "no")) global_opts.nokill = FALSE; else if (!strcmp(cf_option, "no_kill_ppid") && !strcmp(cf_value, "yes")) global_opts.nokillppid = TRUE; else if (!strcmp(cf_option, "no_kill_ppid") && !strcmp(cf_value, "no")) global_opts.nokillppid = FALSE; else if (!strcmp(cf_option, "ignore_root_procs") && !strcmp(cf_value, "yes")) global_opts.noroot = TRUE; else if (!strcmp(cf_option, "ignore_root_procs") && !strcmp(cf_value, "no")) global_opts.noroot = FALSE; else if (!strcmp(cf_option, "log_whitelist") && !strcmp(cf_value, "yes")) global_opts.logwlist = TRUE; else if (!strcmp(cf_option, "log_whitelist") && !strcmp(cf_value, "no")) global_opts.logwlist = FALSE; else if (!strcmp(cf_option, "require_init_wlist") && !strcmp(cf_value, "yes")) global_opts.rinitw = TRUE; else if (!strcmp(cf_option, "require_init_wlist") && !strcmp(cf_value, "no")) global_opts.rinitw = FALSE; else LOG("warning: %s:%d: unknown option and/or value", path, line_num); } return 0; } void stolower(char *s) { int i; for (i = 0; i < strlen(s); i++) s[i] = tolower(s[i]); } ninja-0.1.3.orig/src/config.h0000644000000000000000000000022010301155603012630 0ustar /* undefine this if you want to use /proc//maps instead of /proc//exe to get the full path of executables */ #define USE_READLINK ninja-0.1.3.orig/src/file.c0000644000000000000000000000101610265711206012307 0ustar #include #include #include #include #include #include "common.h" int file_isregular(char *fpath) { struct stat st; if (lstat(fpath, &st) == 0) { if (!S_ISREG(st.st_mode)) return 0; } else { return -1; } return 1; } long file_size(char *fpath) { FILE *fp; long fsize; if ((fp = fopen(fpath, "r")) == NULL) return -1; if (fseek(fp, 0, SEEK_END) != 0) return -2; fsize = ftell(fp); fclose(fp); return fsize; } ninja-0.1.3.orig/src/common.h0000644000000000000000000000101311306164352012663 0ustar #include "proto.h" #include "config.h" #define PROG_NAME "ninja" #define PROG_VERSION "0.1.3" #define TRUE 1 #define FALSE 0 struct options { int daemon; int interval; int quiet; int nokill; int nokillppid; int noroot; int logwlist; int rinitw; gid_t group; char logfile[128]; char command[128]; char wlist[128]; int soffset; }; #ifndef PID_MAX #define PID_MAX 0x8000 /* found in linux/threads.h */ #endif #ifndef FILENAME_MAX #define FILENAME_MAX 255 #endif ninja-0.1.3.orig/src/user.c0000644000000000000000000000176510265712660012366 0ustar #include #include #include #include #include #include #include #include #include "common.h" int check_group(const char *user, const gid_t gid) { size_t group_count; gid_t gid_list[NGROUPS_MAX+1]; int i; if (user == NULL) return 1; initgroups(user, -1); group_count = getgroups(0, gid_list); getgroups(group_count, gid_list); for (i = 0; i < group_count; i++) { if (gid_list[i] == gid) return 0; } return 1; } char *get_user(uid_t uid) { struct passwd *pwent; setpwent(); while ((pwent = getpwent()) != NULL) { if (pwent->pw_uid == uid) { endpwent(); return pwent->pw_name; } } endpwent(); return NULL; } gid_t get_gid(char *name) { struct group *grent; setgrent(); while ((grent = getgrent()) != NULL) { if (!strcmp(grent->gr_name, name)) { endgrent(); return grent->gr_gid; } } endgrent(); return -1; } ninja-0.1.3.orig/src/main.c0000644000000000000000000000646110305400142012312 0ustar #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "proc.h" #include "grp.h" #define DEFAULT_CYCLE_DELAY 1 struct options global_opts; int main(int argc, char **argv) { int i; struct group *gr; FILE *fd; char *wlist = NULL; global_opts.daemon = FALSE; global_opts.interval = DEFAULT_CYCLE_DELAY; global_opts.quiet = FALSE; global_opts.group = 0; global_opts.nokill = FALSE; global_opts.nokillppid = FALSE; global_opts.noroot = TRUE; global_opts.logwlist = FALSE; global_opts.rinitw = FALSE; global_opts.soffset = 0; memset(global_opts.logfile, 0x00, sizeof global_opts.logfile); memset(global_opts.command, 0x00, sizeof global_opts.command); memset(global_opts.wlist, 0x00, sizeof global_opts.wlist); if (argc <= 1) LOG("warning: no configuration file specified, using default values"); if (argc > 1 && argv[1][0] != '-') { LOG("reading configuration file: %s", argv[1]); if (conf_read(argv[1]) != 0) die("error: unable to read configuration file"); } else { if (argc > 1 && !strcmp(argv[1], "-h")) { help(); exit(EXIT_FAILURE); } } LOG("ninja version %s initializing", PROG_VERSION); if (getuid() != 0) die("error: must run as root"); /* sanity check /etc/passwd and /etc/group */ if (file_isregular("/etc/passwd") != 1) LOG("warning: /etc/passwd is not a regular file -- this will cause problems"); if (file_isregular("/etc/group") != 1) LOG("warning: /etc/group is not a regular file -- this will cause problems"); if (file_size("/etc/passwd") < 1) LOG("warning: /etc/passwd has a size of 0 bytes -- this will cause problems"); if (global_opts.group == -1) die("error: no magic group specified"); if (global_opts.rinitw == TRUE && global_opts.wlist[0] == 0x00) die("error: require_init_wlist option set, but no whitelist is specified"); for (i = 0; i < 100; i++) signal(i, sighandler); if ((gr = getgrgid(global_opts.group)) == NULL) die("error: magic group not found by getgrgid()"); LOG("magic group: gid=%d (%s)", gr->gr_gid, gr->gr_name); if (global_opts.logfile[0] != 0x00) { LOG("logfile: %s", global_opts.logfile); if (file_isregular(global_opts.logfile) != 1) die("error: `%s' is not a regular file", global_opts.logfile); if ((fd = fopen(global_opts.logfile, "a")) == NULL) die("error: unable to open log file"); else fclose(fd); } if (global_opts.command[0] != 0x00) { LOG("external command: %s ", global_opts.command); } if (global_opts.wlist[0] != 0x00) { if ((wlist = wlist_read(global_opts.wlist)) == NULL) die("failed to read whitelist"); LOG("whitelist mapped in memory at %p", (void *)wlist); } if (global_opts.soffset != 0) { LOG("proc scanning offset: %d", global_opts.soffset); } if (global_opts.daemon == TRUE) { LOG("entering daemon mode"); if (daemon(0, 0) == -1) die("error: daemon() failed"); global_opts.quiet = TRUE; } LOG("entering main loop"); main_loop(global_opts.group, global_opts.interval, wlist); return EXIT_SUCCESS; } void help(void) { printf("usage: %s \n", PROG_NAME); } ninja-0.1.3.orig/src/signal.c0000644000000000000000000000076110222626136012653 0ustar #include #include #include #include #include "common.h" void sighandler(int s) { switch(s) { case SIGCHLD: wait(NULL); break; case SIGSEGV: die("got signal SIGSEGV -- segmentation fault"); break; case SIGINT: die("got signal SIGINT -- terminating"); break; case SIGQUIT: die("got signal SIGQUIT -- terminating"); break; default: LOG("ignored signal: %d", s); } } ninja-0.1.3.orig/src/proc.c0000644000000000000000000000256110305376146012346 0ustar #include #include #include #include "common.h" #include "proc.h" /* get_proc_info() -- parse /proc//status for a given pid */ /* this should be pretty self explained.. we parse /proc//status and * put the name, pid, ppid, uid and gid fields into a structure (proc_info) */ int get_proc_info(const pid_t pid, struct proc_info *pi) { FILE *fp; char filename[32]; char rbuf[128]; char pbuf[5][33]; int i; snprintf(filename, sizeof filename, "/proc/%d/status", pid); if ((fp = fopen(filename, "r")) == NULL) return -1; while (fgets(rbuf, sizeof rbuf, fp) != NULL) { for (i = 0; i < 5; i++) memset(pbuf[i], 0x00, sizeof pbuf[i]); sscanf(rbuf, "%32s %32s %32s %32s %32s", pbuf[0], pbuf[1], pbuf[2], pbuf[3], pbuf[4]); if (!strcmp(pbuf[0], "Name:")) snprintf(pi->name, 127, "%s", pbuf[1]); if (!strcmp(pbuf[0], "Pid:")) pi->pid = atoi(pbuf[1]); if (!strcmp(pbuf[0], "PPid:")) pi->ppid = atoi(pbuf[1]); if (!strcmp(pbuf[0], "Uid:")) { pi->uid = atoi(pbuf[1]); for (i = 1; i < 4; i++) { if (atoi(pbuf[i]) == 0) pi->uid = 0; } } if (!strcmp(pbuf[0], "Gid:")) { pi->gid = atoi(pbuf[1]); for (i = 1; i < 4; i++) { if (atoi(pbuf[i]) == 0) pi->gid = 0; } } } fclose(fp); return 0; } ninja-0.1.3.orig/ninja.80000644000000000000000000000577010300772544011642 0ustar .TH NINJA "8" "August 2005" "" "" .SH NAME ninja \- Privilege escalation detection system for GNU/Linux .SH SYNOPSIS .B ninja \fIfilename\fR .SH DESCRIPTION .PP Ninja is a privilege escalation detection and prevention system for GNU/Linux hosts. While running, it will monitor process activity on the local host, and keep track of all processes running as root. If a process is spawned with UID or GID zero (root), ninja will log necessary information about this process, and optionally kill the process if it was spawned by an unauthorized user. A "magic" group can be specified, allowing members of this group to run any setuid/setgid root executable. Individual executables can be whitelisted. Ninja uses a fine grained whitelist that lets you whitelist executables on a group and/or user basis. This can be used to allow specific groups or individual users access to setuid/setgid root programs, such as su(1) and passwd(1). .SH CONFIGURATION Ninja requires a configuration file to run. For more information about the configuration, please refer to the "default.conf" file, located at "examples/config/" in the source tree. There, all the available options are explained in detail. .SH WHITELIST .PP The whitelist is a plain text file, containing new-line separated entries. Entries consists of three fields, separated by colons. The first field is the full path to the executable you wish to whitelist. The second field is a comma separated list of groups that should be granted access to the executable. The third field is a comma separated list of users. .TP :: .PP The second or third field can be left empty. Please refer to the example whitlist located in "examples/whitelist/". Remember that it is a good idea to whitelist programs such as passwd(1) and other regular setuid applications that users require access to. .SH SECURITY The goal of this application is to be able to detect and stop local, and possibly also remote exploits. It is important to note that ninja cannot prevent attackers from running exploits, as a successful exploitation only will be detected AFTER the attacker has gained root. However, when ninja is running with a short scanning cycle, this detection happens nearly immediately. The security lies in the fact that we stop the attacker before he/she has time to do anything nasty to the system, and it gives us the opportunity to disable the attacker's shell access, and lock him/her out of the system. In an ideal environment, ninja should be run together with kernel hardening systems such as grsecurity (www.grsecurity.net) as this will allow for some protection of the ninja process. This is not a complete security system. Do not rely on it to keep your system safe. .SH BUGS .PP Please let me know if you should stumble across any bugs or other weirdness. I greatly appreciate all bug reports, patches, ideas, suggestions and comments. .SH LICENSE .PP Ninja is released under the General Public License (GPL) version 2 or higher. .SH AUTHOR .PP Tom Rune Flo ninja-0.1.3.orig/ChangeLog0000644000000000000000000001136011306164335012214 0ustar version 0.1.3 (03-12-2009): * general: bumped version to 0.1.3 * log.c do va_start() before writing to logfile to fix segfault on x86_64 platforms. (Reported by Francois Marier & William Vera) version 0.1.2 (31-08-2005): * general: bumped version to 0.1.2 * main.c: now prints a warning when no config file has been specified, to inform the user that default values are being used. (Thanks to ports for the suggestion) * Makefile: 'install' will now create a $ETCDIR location for config files, and whitelists. $ETCDIR is set to "/etc/ninja/" as default. * proc.c config.c wlist.c: fixed some harmless mistakes in the sscanf() field widths. (Thanks to William Vera for reporting this) version 0.1.1 (19-08-2005): * general: bumped version to 0.1.1 * config.h: new file. * general: you can now choose to use get_name_name() or readlink() to get full path of executables in the whitelist system. this can be done by modifying config.h -- USE_READLINK is default. * wlist.c: fixed a bug in get_map_name() that could allow attackers to fool the whitelist system, by mmap()ing whitelisted executables at a low address. (Thanks to Martin Mikkelsen) * wlist.c: added support for using readlink() on /proc//exe to get the full path of executables, instead of using the old get_map_name(). * wlist.c: now using PATH_MAX buffersize when reading map names from /proc//maps to avoid possible overflows with really long paths (Thanks to Martin Mikkelsen) version 0.1.0 (18-08-2005): * general: bumped version to 0.1.0 * ninja.8: rewritten to reflect changes made from 0.0.x to 0.1.0 * general: moved example configuration and whitelist to "examples/" * general: minor changes to logging messages. version 0.1.0-pre2 (14-07-2005): * general: bumped version to 0.1.0-pre2 * mloop.c: verify that /etc/passwd and /etc/group exists and are regular files before trying to access them with getpwent() and getgrent(). glibc is far from great. * user.c: check_group() will now return error if it was passed a NULL pointer, instead of passing NULL on to initgroups(). version 0.1.0-pre1 (22-06-2005): * general: bumped version to 0.1.0-pre1 * general: added a 'require_init_wlist' option to force all processes controlled by root to be whitelisted properly. * wlist.c: whitelisting is now done in a fine-grained user and/or group fashion. * mloop.c: updated/fixed removal of lost root pids. * config.c: new file. * main.c: added config file support, updated usage information. now waits with activating quiet mode till entering daemon mode. * src/: new directory. moved all source code here. * Makefile: modified to reflect code move to src/ version 0.0.9 (13-03-2005): * general: bumped version to 0.0.9 * main.c: the initialization process now makes sure the log file is a regular file, and not e.g. a fifo. * log.c: LOG() now makes sure logfile is a regular file; it was possible to stall the ninja process by quickly replacing the logfile with a fifo when gaining root. (thanks to Martin Mikkelsen) version 0.0.8 (12-03-2005): * general: bumped version to 0.0.8 * user.c: fixed error in check_group() that caused the first group of each user to be ignored. (thanks to Terje Tinnion) * mloop.c: the execution of external commands are now done in a separate process to avoid blocking. * signal.c: new file. * ninja.8: fixed minor error in examples. updated information to reflect changes to the -c option. version 0.0.7 (10-03-2005): * general: bumped version to 0.0.7 * Makefile: now executing mkdir(1) before installing, in case of copying to non-existent directories. * general: log() renamed to LOG() to avoid confusion with log() found in math.h, as this was causing compiler warnings on debian systems. * main.c: ignoring procs spawned by root is now the default behaviour, the '-r' option now has the reverse meaning. * ninja.8: updated to reflect some changes, added more info on whitelist. fixed spelling (thanks to Michael Mansour) version 0.0.6 (09-03-2005): * general: bumped version to 0.0.6 * mloop.c: removed all dynamic buffers, and thus all malloc/free calls to save cpu time. ninja-0.1.3.orig/examples/0000755000000000000000000000000010300761000012240 5ustar ninja-0.1.3.orig/examples/whitelist/0000755000000000000000000000000010300761314014264 5ustar ninja-0.1.3.orig/examples/whitelist/simple.wlist0000644000000000000000000000006210300761314016637 0ustar /usr/bin/passwd:admins,users: /bin/su:admins:tom ninja-0.1.3.orig/examples/config/0000755000000000000000000000000010300766003013515 5ustar ninja-0.1.3.orig/examples/config/default.conf0000644000000000000000000000772410300765733016033 0ustar # This sample ninja(8) configuration file shows and explains all the available # options, with all the values set to default. # # Use this file as a guide when creating your own configuration. # DO NOT USE THIS CONFIGURATION AS IT IS! # # Configuration syntax: # #