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
