#!/usr/bin/perl -w # # Tighten Security for Anonymous FTP # ================================== # # xx-Dec-98 amo Date-of-Birth # 11-Jun-01 amo Cleanup # 06-Jul-01 amo added .project,.procmailrc,.login,.profile # # # --------------------------------------------------------------- # # Lastest Copy: http://www.Linux-Sec.net/harden/ftp_check.pl # ------------ # # LICENSE: http://www.Linux-Sec.net/harden/ftp_check.LICENSE.txt # ======= # # --------------------------------------------------------------- # # TODO # ---- # - fix permission <--> octal # - fix different ftp daemon check # # --------------------------------------------------------------- # my ( $NM ) = "ftp_check.pl"; # # # # # Which FTPd Versions # -------------------- # ftp stream tcp nowait root /usr/sbin/tcpd in.ftpd -l -a # grep ftp /etc/inetd.conf # # # ~ftp account in /etc/passwd ( shell == /bin/false ) # --------------------------- # ftp:*:14:50:FTP User:/home/ftp:/bin/false # grep ftp /etc/passwd # # # The Entire FTP tree should be owned by root # ------------------------------------------- # - ~ftp should NOT own the tree # # chown -R root.ftp ~ftp # chmod 555 ~ftp # chmod 555 ~ftp # # Some binaries for ~ftp # ----------------------- # chmod 111 ~ftp/bin # cp /bin/ls ~ftp/bin/ls # chmod 111 ~ftp/bin/ls # # Some ftp passwd/group info # -------------------------- # chmod 111 ~ftp/etc # chmod 444 ~ftp/etc/passwd # root and ftp user accounts only # chmod 444 ~ftp/etc/group # ftp group only # # chmod 555 ~ftp/pub # # chmod 555 ~ftp/incoming # # Prevent hacker from creating it # ------------------------------- # touch ~ftp/{.forward,.rhosts,.plan,.project,.procmailrc,.login,.profile} # chown root.ftp ~ftp/{.forward,.rhosts,.plan,.project,.procmailrc,.login,.profile} # chmod 400 ~ftp/{.forward,.rhosts,.plan,.project,.procmailrc,.login,.profile} # # Additional Files # ---------------- # chmod 600 /etc/{ftpaccess,ftpconversions,ftpgroups,ftphosts,ftpusers} # # /etc/hosts.allow # /etc/hosts.deny # # ------------- # Some binaries # ------------- my ( $DIFF ) = &get_bin ( "diff" ); my ( $GREP ) = &get_bin ( "grep" ); my ( $LS ) = &get_bin ( "ls" ); my ( $WGET ) = &get_bin ( "wget" ); # # my ( $FTP ) = "/home/ftp"; my ( $FTP_ustr ) = "root"; # preferred owner of the entire FTP tree ( /home/ftp ) my ( $FTP_gstr ) = "ftp"; # preferred group of the FTP tree # my ( $FTP_uid ) = 0; my ( $FTP_gid ) = 0; my ( $FTP_Home ) = $FTP; # # my ( $FTP_Off ) = ""; my ( $FTP_anon ) = "real user"; # real user access allowed, unless "real" is removed in /etc/ftpaccess # # # FTP files # --------- my ( $FTPaccess ) = "/etc/ftpaccess"; my ( $FTPuser ) = "/etc/ftpuser"; # users NOT allowed to login my ( $FTPhosts ) = "/etc/ftp/hosts"; # hosts that are(not?) allowed to login ??? my ( $FTgroups ) = "/etc/ftpgroups" ; # # # TCP wrapper stuff # ----------------- my ( $FTPinetd ) = "/etc/inetd.conf"; # my ( $FTPwrapallow ) = "/etc/hosts.allow"; # read before deny my ( $FTPwrapdeny ) = "/etc/hosts.deny"; # my ( $FTP_tcpd ) = ""; # wrapper my ( $FTP_ftpd ) = ""; # ftp variables my ( $FTP_dae ) = ""; # ftp daemon my ( $FTP_opt ) = ""; # ftp options # # # Check the Top_Level ftp tree # ---------------------------- # my ( %FTP_CheckPerm ) = (); # # Minimum Permissions # $FTP_CheckPerm{ ".forward" } = "-r--------"; $FTP_CheckPerm{ ".login" } = "-r--------"; $FTP_CheckPerm{ ".plan" } = "-r--------"; $FTP_CheckPerm{ ".project" } = "-r--------"; $FTP_CheckPerm{ ".procmailrc" } = "-r--------"; $FTP_CheckPerm{ ".profile" } = "-r--------"; $FTP_CheckPerm{ ".rhosts" } = "-r--------"; # $FTP_CheckPerm{ "bin" } = "d--x--x--x"; $FTP_CheckPerm{ "etc" } = "d--x--x--x"; $FTP_CheckPerm{ "incoming" } = "d--x--x--t"; $FTP_CheckPerm{ "pub" } = "dr-xr-xr-x"; $FTP_CheckPerm{ "welcome.msg" } = "-r--r--r--"; # # # Current Patch levels for FTP daemons # ------------------------------------ # my ( %FTP_Version ) = (); $FTP_Version{ "wu-ftpd" } = "wu-2.6.2"; # # # Download the Lastest copy and compare # ------------------------------------- # my ( $Latest ) = "/tmp/$NM.new"; my ( $Wget ) = "$WGET -O $Latest http://www.Linux-Sec.net/harden/$NM"; my ( $wget_stat ) = ` $Wget `; my ( $diff_stat ) = ` $DIFF $Latest $NM `; # printf "\n"; printf "..Download the Latest copy into $Latest..stat=$wget_stat..\n"; printf "..Compare with the $Latest..stat=$diff_stat..\n"; printf "\n"; # # # ============================================================================= # # Start FTP Checking # # ============================================================================= # printf "\n"; printf "Usage: $NM [-fixit]\n"; printf "\t... it simply checks your FTP configuration files\n"; # # # Check FTP accounts # ------------------ # &check_acct ; # # # Check FTP access # ---------------- # &check_ftpaccess ; # # # Check Versions # &check_versions ; # # # Check Real User access # ---------------- # &check_ftpreal ; # # # Check FTP wrappers # ---------------- # &check_ftpwrap ; # # # Check FTP permissions # ---------------------- # &check_ftpperm ; # # # Check FTP owners # ---------------- # &check_ftpowner ; # # printf "\n"; printf "..DONE..\n"; # exit 0; # # ============================================================================= # # Get Account Info # ---------------- # sub check_acct { # my ( $ftp ) = ` $GREP ftp /etc/passwd `; # my ( $acct, $pass, $uid, $gid, $comm, $home, $shell ) = split ( /:/, $ftp ); chomp ( $shell ); # $FTP_uid = $uid; $FTP_gid = $gid; $FTP_Home = $home; # my ( $grp, $x, $giddd ) = split ( /:/, ` $GREP $gid /etc/group ` ); # my ( $sh ) = ""; $sh = ` $GREP $shell /etc/shell ` if ( $shell ne "" ); # # printf "\n"; printf "..Checking FTP Accounts.. ( /etc/passwd, /etc/group )\n"; # if ( $gid == $giddd ) { printf "....OK.... $acct GroupId=$gid from /etc/group..\n"; } else { printf "....$acct user does NOT have a valid ftp groupID=$gid..\n" } # if ( $sh eq "" ) { printf "....OK.... shell for $acct account=$sh .. good, it's NOT a regular shell account..\n"; } else { printf "....$acct user should NOT have a real login shell=$shell..\n" } # # } # check_acct # # # Check Access # ------------ sub check_ftpaccess { # my ( $err ) = "......OK......"; # # printf "\n"; printf "..Checking FTP services ( $FTPinetd )..\n"; # # ftp stream tcp nowait root /usr/sbin/tcpd in.ftpd -l -a my ( $ftp, $str, $ctp, $wait, $uid ) = ""; # ( $ftp, $str, $ctp, $wait, $uid, $FTP_tcpd, @FTP_ftpd ) = split ( /\s+/, ` $GREP ^ftp $FTPinetd ` ); # # # which FTP daemon # ---------------- $FTP_dae = $FTP_ftpd[0]; # # if ( $ftp eq "" ) { $FTP_Off = "( even if FTP is turned off )"; $err = "....Good...... FTP is turned off" } else { $err = "......OK...... Found FTP service enabled ( /usr/sbin/$FTP_dae ), needs more checking"; } printf "..$err..\n" ; # } # check_ftpaccess # # # Check FTP daemon version and tcpwrapper version # ------------------------------------------------ # sub check_versions { # # my ( $tcpv ) = `strings $FTP_tcpd | $GREP tcpd.c `; chomp ( $tcpv ); # my ( $errt ) = "....OK.... Current TCPwrapper"; my ( $vert ) = $FTP_Version{ "tcpd" }; # $_ = $tcpv; $errt = "==== OBSOLETE ====" if ( ! /$vert/ ); # # # my ( $ftpv ) = `strings /usr/sbin/$FTP_dae | $GREP -i ^Version `; chomp ( $ftpv ); # my ( $errf ) = "....OK.... Current FTP daemon.."; my ( $verf ) = $FTP_Version{ "wu-ftpd" }; # # $_ = $ftpv; # if ( /wu-/ ) { # $errf = "==== OBSOLETE ====" if ( ! /$verf/ ); } # printf "\n"; printf "..Checking FTP daemon and wrapper versions..\n"; printf "..$errt version=$tcpv..\n"; printf "..$errf version=$ftpv..\n"; # # } # check_versions # # # Disallow Real user FTP access # ----------------------------- # sub check_ftpreal { # my ( $err ) = ""; # printf "\n"; printf "..Checking User Access.. ( $FTPaccess ) $FTP_Off\n"; # # # Disallow for real users # ----------------------- # my ( $real ) = ""; $real = ` $GREP ^class $FTPaccess | $GREP real ` if ( -f "$FTPaccess" ) ; # if ( "$real" eq "" ) { $err = "....Good..... Real users is NOT allowed FTP connections ( \"real\" NOT in class definitions )"; $FTP_anon = "anonymous"; } else { $err = "..You should disallow 'real' (user) ftp access ... use scp(ssh) instead.." } # printf "..$err..\n" ; # # # } # check_ftpacess # # # Check FTP Wrappers # ------------------ # sub check_ftpwrap { # my ( $ftpd, $foo ) = ""; my ( @opt ) = (); my ( $d_s, $d_w ) = ""; my ( $deny ) = 0; # # my ( $err ) = "......OK......"; # # printf "\n"; printf "..Checking FTP wrappers ( $FTPwrapallow $FTPwrapdeny )..\n"; # # Look for "All: All" ( $d_s, $d_w ) = split ( /:/, ` $GREP -i ^ALL $FTPwrapdeny `); # # # Deny all Services by default # ---------------------------- # #__ $_ = $d_s; #__ $deny = 1 if ( /All/i ); #__ # #__ $_ = $d_w; #__ $deny += 1 if ( /All/i ); # if ( $deny == 2 ) { # Found "All: ALl" $err = "..Good.... All Services is denied by default" } else { $err = "===== BAD ===== You should DENY all services by default ( add this \"All: All\" to $FTPwrapdeny )" } printf "..$err..\n" ; # # # Now check if specifically allowed in hosta.allow # ------------------------------------------------- # ( $ftpd, $foo ) = split ( /:/, ` $GREP -i $FTP_dae $FTPwrapallow ` ); # &allow_deny ( "$FTPwrapallow", "$foo" ); # # # Now check if an exception in hosts.deny # --------------------------------------- # ( $ftpd, $foo ) = split ( /:/, ` $GREP -i $FTP_dae $FTPwrapdeny ` ); # &allow_deny ( "$FTPwrapdeny", "$foo" ); # # } # check_ftpwrap # # # sub allow_deny { my ( $file ) = $_[0]; my ( $foo ) = $_[1]; $foo =~ s/^\s+//; # my ( @f ) = split ( /\s+/, $foo ); my ( $id ) = 0; # my ( $f_exp ) = 0; my ( $allow, $except ) = ""; # # if ( $#f == -1 ) { printf "....FTP services not defined in $file..\n"; return ; } # # Process wrapper # # while ( $id <= $#f ) { # if ( $f_exp == 0 ) { if ( $f[$id] ne "EXCEPT" ) { $allow .= "$f[$id] "; } else { $f_exp = 1; } } else { $except .= "$f[$id] "; } # $id += 1; } # printf "....in $file..\n"; printf "........You are Allowing $FTP_anon FTP connection to \"$allow\" .... and ...\n"; printf "........You are explicitly Denying(Except) $FTP_anon FTP connection to \"$except\"..\n"; # } # allow_deny # # # Check Owners # ------------ # sub check_ftpowner { # # FTP tree should be owned by root.ftp # printf "\n"; printf "..Checking FTP Owner/Groups..\n"; # # my ( @ftpown1 ) = `cd $FTP_Home ; find . \\\( ! -uid 0 \\\) -ls | $GREP -v www-linux `; my ( @ftpown2 ) = `cd $FTP_Home ; find . \\\( -uid 0 -a ! -gid 0 -a ! -gid $FTP_gid \\\) -ls | $GREP -v www-linux `; my ( @ftpown ) = ( @ftpown1, @ftpown2 ); # my ( $id ) = 0; my ( $ftp ) = 0; # # printf "....FTP directory/files should be root-owned....found $#ftpown1 (potential) problems..\n"; printf "....FTP directory/files should be group ftp....found $#ftpown2 (potential) problems..\n"; # # while ( $id <= $#ftpown ) { # # # chomp ( $ftp = $ftpown[$id] ); # # # printf "..id=$id..$ftp..\n"; # # # $id += 1; # } # } # check_ftpowner # # # Check Permissions # ----------------- # sub check_ftpperm { # # Get the ftp diectory Tree # my ( @ftp_res ) = ` $LS -la $FTP/ `; # my ( $ftp ) = 0; my ( $id ) = 1; # id=0 is total number of file # my ( $perm, $nd, $uid, $gid, $sz, $mon, $date, $time, $file, $str, $err ) = 0; my ( %ftp_tree ) = (); # # printf "\n..Checking $FTP Permissions..\n"; ; # # # ftp top level # ------------- # while ( $id <= $#ftp_res ) { # # -r-------- 1 root root 0 Aug 31 1996 .forward chomp ( $str = $ftp_res[$id] ); # ( $perm, $nd, $uid, $gid, $sz, $mon, $date, $time, $file ) = split ( /\s+/, $str ); # # save the variables we will be checking later # $ftp_tree{ $file } = "$perm; $uid; $gid; $str"; # use illegal filename char as separator # $id += 1; # } # each ftp entry # # # Now search for stuff we wanna check # ----------------------------------- # foreach $file ( sort keys %FTP_CheckPerm ) { # $exp = $FTP_CheckPerm{ $file }; # expected value # # if ( $ftp_tree{ $file } ) { # Look for ftp variables that should be defined for "secured ftp servers" # # -r-------- 1 root root 0 Aug 31 1996 .forward # ( $perm, $uid, $gid, $str ) = split ( /;/, $ftp_tree{ $file } ); # } else { # $perm = ""; $str = "$FTP/$file"; # Missing } # $err = &check_perm ( $exp, $perm, $file, $str ); # } # each ftp entry # # } # check_ftpperm # # # Check Permissions # ----------------- sub check_perm { my ( $exp_perm ) = $_[0]; # expected permissions my ( $perm ) = $_[1]; # test my ( $file ) = $_[2]; # file to check my ( $str ) = $_[3]; # my ( $stat ) = 0; my ( $err ) = "....OK...." ; # if ( $perm eq "" ) { $err = "..MISSING."; } else { # $_ = $perm; $stat = "0400" if ( /^-r/ ); } # printf "....$err... $str\n"; # return ( $err ); # } # check_perm # # # Get Path to Binaries # -------------------- # sub get_bin { # my ( $bin ) = $_[0]; # my ( $path ) = ""; # if ( -x "/sbin/$bin" ) { $path = "/sbin/$bin" ; # } elsif ( -x "/bin/$bin" ) { $path = "/bin/$bin" ; # } elsif ( -x "/usr/sbin/$bin" ) { $path = "/usr/sbin/$bin" ; # } elsif ( -x "/usr/bin/$bin" ) { $path = "/usr/bin/$bin" ; # } elsif ( -x "/usr/local/sbin/$bin" ) { $path = "/usr/local/sbin/$bin" ; # } elsif ( -x "/usr/local/bin/$bin" ) { $path = "/usr/local/bin/$bin" ; } else { printf "\n"; printf "ERROR: No such cmd=$bin..\n"; printf "\n"; # exit 1; # } # return ( $path ); # } # get_bin # # # end of file