The script after the fold searches for setuid and setgid files on a disk image or in a virtual machine. When you run it you will see output like this:
# chmod +x findsuid.ml # ./findsuid.ml RHEL60 /dev/vg_rhel6brewx64/lv_root:/sbin/mount.nfs is setuid file /dev/vg_rhel6brewx64/lv_root:/sbin/netreport is setgid file /dev/vg_rhel6brewx64/lv_root:/sbin/pam_timestamp_check is setuid file /dev/vg_rhel6brewx64/lv_root:/sbin/unix_chkpwd is setuid file /dev/vg_rhel6brewx64/lv_root:/usr/bin/Xorg is setuid file /dev/vg_rhel6brewx64/lv_root:/usr/bin/at is setuid file /dev/vg_rhel6brewx64/lv_root:/usr/bin/chage is setuid file /dev/vg_rhel6brewx64/lv_root:/usr/bin/chfn is setuid file /dev/vg_rhel6brewx64/lv_root:/usr/bin/chsh is setuid file etc
You could make simple adaptions to this script to audit for all sorts of things of interest: public writeable directories, unusual SELinux labels, hard links to setuid files, over-sized files. With more work you could look for files with unusual/changed checksums, infected files and so on.
#!/usr/bin/ocamlrun /usr/bin/ocaml #load "unix.cma";; #directory "+guestfs";; #load "mlguestfs.cma";; open Printf let (//) = Filename.concat let () = let prog = Filename.basename Sys.executable_name in if Array.length Sys.argv < 2 then ( eprintf "Usage: %s guest\n" prog; exit 1 ); (* Open guest domain. *) let g = new Guestfs.guestfs () in ignore (g#add_domain ~readonly:true Sys.argv.(1)); g#launch (); (* If VMs could be encrypted you would have to * add additional calls to luks* here. * See fish/inspect.c:inspect_do_decrypt() *) (* Useful functions to test file types. Note * can't use the regular stat functions, because * these modes are the ones defined in the Linux * ABI, not the host OS. *) let rec file_type mask mode = Int64.logand mode 0o170000L = mask (*and is_socket mode = file_type 0o140000L mode and is_symlink mode = file_type 0o120000L mode*) and is_regular_file mode = file_type 0o100000L mode (*and is_block mode = file_type 0o060000L mode*) and is_directory mode = file_type 0o040000L mode (*and is_char mode = file_type 0o020000L mode and is_fifo mode = file_type 0o010000L mode*) and is_suid mode = test_bit 0o4000L mode and is_sgid mode = test_bit 0o2000L mode (*and is_svtx mode = test_bit 0o1000L mode*) and test_bit mask mode = Int64.logand mode mask = mask in (* Visit each file in the mounted filesystem. * TODO: Add libguestfs APIs to make this simpler * and faster. *) let rec visit f dir = let names = g#ls dir in let stats = lstatlist dir names in let entries = List.combine (Array.to_list names) stats in List.iter (f dir) entries; let dirs = List.filter entry_is_dir entries in List.iter ( fun (name, stat) -> visit f (dir // name) ) dirs and entry_is_dir (_, { Guestfs.mode = mode }) = is_directory mode and lstatlist dir = function | [| |] -> [] | names -> (* Split large requests so we don't overrun * the libguestfs protocol limit. *) let len = Array.length names in let first, rest = if len <= 1000 then names, [| |] else ( Array.sub names 0 1000, Array.sub names 1000 (len-1000) ) in let stats = g#lstatlist dir first in Array.to_list stats @ lstatlist dir rest in (* Test each file, printing those that match * the criteria. *) let search dev dir (name, { Guestfs.mode = mode }) = if is_regular_file mode && is_suid mode then printf "%s:%s is setuid file\n%!" dev (dir // name) else if is_regular_file mode && is_sgid mode then printf "%s:%s is setgid file\n%!" dev (dir // name) in (* Search every mountable filesystem. *) let fses = g#list_filesystems () in List.iter ( fun (dev, _) -> g#umount_all (); let mounted_ok = try g#mount_ro dev "/"; true with exn -> false in if mounted_ok then visit (search dev) "/" ) fses