def rkhunter(EMAIL): # Rootkit Detection With Rkhunter f_rkhunter_conf = '/etc/rkhunter.conf' if not General.answer('Do you want to install and configure rkhunter?'): return General.run_cmd('apt install rkhunter -y') General.error_message( '[ALERT] If this is the first installation select yes to the three questions you will be asked to answer ' ) time.sleep(5) General.output_message('Configuring rkhunter..') General.change_lines( f_rkhunter_conf, _1=['UPDATE_MIRRORS=0', 'UPDATE_MIRRORS=1\n'], _2=['MIRRORS_MODE=1', 'MIRRORS_MODE=0\n'], _3=['#MAIL-ON-WARNING=root', f'MAIL-ON-WARNING={EMAIL}\n'], _4=['#COPY_LOG_ON_ERROR=0', 'COPY_LOG_ON_ERROR=1\n'], _5=['#PKGMGR=NONE', 'PKGMGR=DPKG\n'], _6=['#PHALANX2_DIRTEST=0', 'PHALANX2_DIRTEST=1\n'], _7=['#USE_LOCKING=0', 'USE_LOCKING=1\n'], _8=[ '#SHOW_SUMMARY_WARNINGS_NUMBER=0', 'SHOW_SUMMARY_WARNINGS_NUMBER=1\n' ], _9=['WEB_CMD="/bin/false"', 'WEB_CMD=""\n']) subprocess.check_call(['dpkg-reconfigure', 'rkhunter'], stderr=subprocess.STDOUT) General.run_cmd('rkhunter --propupd', 'rkhunter --update') General.output_message('Rkhunter Done')
def proc(): # Securing /proc | /proc mounted with hidepid=2 so users can only see information about their processes error = 'Fatal Error: PROC will have to be configured manually' f_ntp_conf = "/etc/ntp.conf" response = General.answer('Securing /proc so users can only see information about their processes?') if not response: return General.output_message('Configuring proc') if not os.path.exists(f_ntp_conf): General.error_message(f"could not localte the file {f_ntp_conf}. Exiting /proc configuration.") return with open(f_ntp_conf, 'a') as file: file.write(f''' # added by debian9_Hardening.py on {datetime.today().strftime('%Y-%m-%d')} @ {datetime.now().strftime( '%H:%M:%S')} proc /proc proc defaults,hidepid=2 0 0 ''')
def ntp(): # installing NTP client and keeping server time in-sync error = 'Fatal Error: NTP will have to be configured manually' response = General.answer( 'Install NTP client and keeping server time in-sinc ?') check_attempt = 0 f_ntp_conf = '/etc/ntp.conf' if not response: return General.run_cmd('apt install ntp -y') if not os.path.exists(f_ntp_conf): General.error_message( f"could not localte the file {f_ntp_conf}. Exiting ntp configuration." ) return General.output_message('Configuring NTP') with open(f_ntp_conf, 'w') as file: file.write(f''' # added by debian9_Hardening.py on {datetime.today().strftime('%Y-%m-%d')} @ {datetime.now().strftime('%H:%M:%S')} driftfile /var/lib/ntp/ntp.drift statistics loopstats peerstats clockstats filegen loopstats file loopstats type day enable filegen peerstats file peerstats type day enable filegen clockstats file clockstats type day enable restrict -4 default kod notrap nomodify nopeer noquery limited restrict -6 default kod notrap nomodify nopeer noquery limited restrict 127.0.0.1 restrict ::1 restrict source notrap nomodify noquery pool pool.ntp.org iburst ''') General.run_cmd('service ntp restart')
def lynis(): if not General.answer( 'Do you want install Lynis for upgrade your security level?'): return General.run_cmd('git clone https://github.com/CISOfy/lynis /opt/lynis') General.output_message('Lynis installed in > /opt/lynis') General.output_message('Running lynis scan..') output = str( subprocess.check_output(['./lynis', 'audit', 'system', '-Q'], cwd="/opt/lynis", stderr=subprocess.STDOUT), 'utf-8') General.output_message('Scan completed') output = General.remove_ansi_escape(output) lynis_output_sections = dict() output = output.split('[+]') for section in output: if ('------------------------------------') in section: section = section.split('------------------------------------') if section[0].strip() == 'Plugins (phase 2)': section[1] = section[1].split( '================================================================================' ) lynis_output_sections[ section[0].strip()] = section[1][0].strip() section[1][1] = section[1][1].split('Suggestions') section[1][1][1] = section[1][1][1].split( '----------------------------') lynis_output_sections['Suggestions'] = section[1][1][1][1] else: lynis_output_sections[section[0].strip()] = section[1].strip() if ('Kernel Hardening' in lynis_output_sections): section = lynis_output_sections['Kernel Hardening'] section = section.split('-') for line in section: line = line.strip() if '[ DIFFERENT ]' in line: line = line.split() for word in line: if ')' in word: word = word.strip('()exp: ') if len(word) == 1: word = word[0] elif len(word) == 2: word = word[1] elif len(word) == 3: word = word[2] print(f'sysctl -w {line[0].strip()}={word}') General.run_cmd(f'sysctl -w {line[0].strip()}={word}') print('\n\n\n') if ('Suggestions' in lynis_output_sections): suggestions = lynis_output_sections['Suggestions'].split('*') suggestions_list = [] for suggestion in suggestions: suggestion = suggestion.split('\n') suggestions_list.append(suggestion[0].strip()) if 'Protect rescue.service by using sulogin [BOOT-5260]' in suggestions_list: task = 'BOOT-5260' General.output_message(f'Configuring {task}') with open('/usr/lib/systemd/system/rescue.service', 'r') as file: lines = file.readlines() for index, line in enumerate(lines): if 'ExecStart' in line.strip().split('='): lines[index] = 'ExecStart=-/usr/sbin/sulogin' with open('/usr/lib/systemd/system/rescue.service', 'w') as f_w: for line in lines: f_w.write(line) General.output_message(f'{task}') if 'Determine priority for available kernel update [KRNL-5788]' in suggestions_list: # https://phoenixnap.com/kb/how-to-update-kernel-ubuntu task = 'KRNL-5788' General.output_message(f'Configuring {task}') General.run_cmd('apt-get update') General.output_message('System updated') General.run_cmd('apt-get dist-upgrade') if 'Configure minimum password age in /etc/login.defs [AUTH-9286]' in suggestions_list or\ 'Configure maximum password age in /etc/login.defs [AUTH-9286]' in suggestions_list or \ 'Default umask in /etc/login.defs could be more strict like 027 [AUTH-9328]' in suggestions_list: task = 'AUTH-9286' General.change_lines( '/etc/login.defs', _1=['UMASK 022', 'UMASK 027\n'], _2=['PASS_MAX_DAYS 99999', 'PASS_MAX_DAYS 30\n'], _3=['PASS_MIN_DAYS 0', 'PASS_MIN_DAYS 1\n'], _4=['PASS_WARN_AGE 7', 'PASS_WARN_AGE 7\n'], ) General.output_message(f'Configuring {task}') else: General.error_message( 'Something went wrong, cannot analize lynis scan output')
def clamAv(EMAIL): # Anti-Virus Scanning With ClamAV f_clam_conf = '/etc/clamav/freshclam.conf' if not General.answer('Do you want to use ClamAV for Anti-virus Scanning with root permissions?'): return General.run_cmd('apt install clamav clamav-freshclam -y') General.change_lines( f_clam_conf, _1=[ '# Check for new database 24 times a day', '# Check for new database 1 times a day\n' ], _2=[ 'Checks 24', 'Checks 1\n' ] ) General.run_cmd('freshclam -v') user = '' while True: user = General.input_message( "Using clamscan as root is dangerous because if a file is in fact a virus there is risk that it could use the root privileges.\n" "You can create another user by writing [U] \nPlease chose a user that can run clamscan" ) if UsersConfig.user_exists(user): break if user.upper() == 'U': UsersConfig.create_users() else: General.error_message("User doesn't exists") General.output_message('For create a new user type [U]') dir_to_scan = '' all_directories_exists = False while not all_directories_exists: all_directories_exists = True dir_to_scan = General.input_message('Which direcotry should be scanned by clamAV? (ex. /var/www /var/vmail): ') for dir in dir_to_scan.split(): if not os.path.exists(dir): General.error_message(f"The directory {dir} doesn't exists, select another path") all_directories_exists = False General.output_message('Configuration Anti-virus Scanning tools..') f_clam_daily = f'/home/{user}/clamscan_daily.sh' with open(f_clam_daily, 'w') as file: file.write(f''' #!/bin/bash LOGFILE="/var/log/clamav/clamav-$(date +'%Y-%m-%d').log"; EMAIL_MSG="Please see the log file attached."; EMAIL_FROM="''' + EMAIL + '''"; DIRTOSCAN="''' + dir_to_scan + '''"; for S in ${DIRTOSCAN}; do DIRSIZE=$(du -sh "$S" 2>/dev/null | cut -f1); echo "Starting a daily scan of "$S" directory. Amount of data to be scanned is "$DIRSIZE"."; clamscan -ri "$S" >> "$LOGFILE"; # get the value of "Infected lines" MALWARE=$(tail "$LOGFILE"|grep Infected|cut -d" " -f3); # if the value is not equal to zero, send an email with the log file attached if [ "$MALWARE" -ne "0" ];then # using heirloom-mailx below echo "$EMAIL_MSG"|mail -A "$LOGFILE" -s "Malware Found" "$EMAIL_FROM"; fi done exit 0 ''') General.run_cmd('chmod 0755 /root/clamscan_daily.sh') General.run_cmd('ln /root/clamscan_daily.sh /etc/cron.daily/clamscan_daily') General.run_cmd('service clamav-freshclam start') if not General.answer("Do you want to run the virus scan on the given directories now? This can take some time"): return General.output_message('Running first scan for check if all is ok') subprocess.run(['clamscan', '-r', dir_to_scan])
sys.path.insert( 0, os.path.abspath(__file__ + "/../../")) # add the current module to sys.path from SSLock import General, UsersConfig, Mail, SSHConfig, Nvidia, Ntp, Proc, EntropyPool, Ufw, Fail2Ban, Aide, ClamAv, Maldet, \ RkHunter, Logwatch, Audit, Psad, Alerts, Lynis, WordpresServerSetup, WPVirtualHostSetup if General.internet_on(): print("Internet connection active") # Installing non present packages # Checking if script runs as root General.output_message('Script is running as root?...') if os.geteuid() != 0: General.error_message( "You need to have root privileges to run this script.\nRe-run the script using 'sudo'. Exiting..." ) exit() General.output_message('YES') # Store global informations EMAIL_ENABLE = General.answer( "To receive notifications from the system and for the correct configuration of some tools, email and Exim4 will" " be used ok? \n[if your system is already configured to send mail, you don't have to follow this step] " ) if EMAIL_ENABLE: EMAIL = General.input_message("Email") else: EMAIL = General.input_message(
def ssh(SSH_ENABLE, EMAIL_ENABLE, SSH_PORT, EMAIL): f_ssh_config = '/etc/ssh/sshd_config' if not SSH_ENABLE or not EMAIL_ENABLE: return General.run_cmd("apt install ssh -y") options_list = [ 'Port', 'ClientAliveCountMax', 'ClientAliveInterval', 'LoginGraceTime', 'MaxAuthTries', 'MaxSessions', 'MaxStartups', 'PasswordAuthentication', 'AllowGroups', 'PermitRootLogin' ] for option in options_list: status1 = General.check_lines_in_file(f_ssh_config, option) status2 = General.check_lines_in_file(f_ssh_config, '#' + option) if not status1 and not status2: with open(f_ssh_config, 'a') as file: file.write(option + '\n') generate_key = General.answer('Do you want enable ssh for some users?') if generate_key: generate_root_key = General.answer( f'Do you also want to enable ssh for Current user [{General.USER}] ? ' ) if generate_root_key: key_path = f'/{General.USER}/.ssh/id_rsa' if not os.path.exists(key_path): key_pass = General.input_message( 'Enter a passphrase for the key (empty for no passphrase):' ) General.run_cmd( f'ssh-keygen -C "{EMAIL}" -b 2048 -t rsa -f {key_path} -q -N "{key_pass}"' ) General.warning_message( f'[ALERT] your private key has been sent by email to {EMAIL}, please retrieve and save it, after destroy the email\n' ) time.sleep(3) General.run_cmd( f'echo " DANGER here you will find your private key attached for your device, destroy this mail and its contents instantly." | mail -s "Private key for: {General.USER} on {General.HOSTNAME}" {EMAIL} -A {key_path}' ) else: General.error_message( 'A key pair for the current user yet exists') else: General.change_lines( f_ssh_config, _19=['#PermitRootLogin yes', 'PermitRootLogin no\n'], _20=['PermitRootLogin yes', 'PermitRootLogin no\n']) General.run_cmd('groupadd sshusers') General.output_message("which user do you want to have ssh enabled?") while True: user = General.input_message("User to enable ssh [S to skip]: ") if user.upper() in ['S', 'SKIP']: break if not UsersConfig.user_exists(user): General.error_message("User doesn't exists") else: General.run_cmd(f'usermod -a -G sshusers {user}') generate_key_for_user = General.answer( f'[{user}] Do you want a ssh key pair for this user?') if generate_key_for_user: key_path = f'/home/{user}/.ssh/id_rsa' if not os.path.exists(f'/home/{user}/.ssh'): os.mkdir(f'/home/{user}/.ssh') if not os.path.exists(key_path): key_pass = General.input_message( 'Enter a passphrase for the key (empty for no passphrase):' ) General.run_cmd( f'ssh-keygen -C "{EMAIL}" -b 2048 -t rsa -f {key_path} -q -N "{key_pass}"' ) General.warning_message( f'[ALERT] your private key has been sent by email to {EMAIL}, please retrieve and save it, after destroy the email\n' ) time.sleep(3) General.run_cmd( f'echo " DANGER here you will find your private key attached for your device, destroy this mail and its contents instantly." | mail -s "Private key for: {user} on {General.HOSTNAME}" {EMAIL} -A {key_path}' ) else: General.error_message( f'A key pair for the user "{user}" yet exists') General.change_lines( f_ssh_config, _1=['Port', f"Port {SSH_PORT}\n"], _2=['#Port', f"Port {SSH_PORT}\n"], _3=['ClientAliveCountMax', "ClientAliveCountMax 0\n"], _4=['#ClientAliveCountMax', "ClientAliveCountMax 0\n"], _5=['ClientAliveInterval', "ClientAliveInterval 300\n"], _6=['#ClientAliveInterval', "ClientAliveInterval 300\n"], _7=['LoginGraceTime', "LoginGraceTime 30\n"], _8=['#LoginGraceTime', "LoginGraceTime 30\n"], _9=['MaxAuthTries', "MaxAuthTries 2\n"], _10=['#MaxAuthTries', "MaxAuthTries 2\n"], _11=['MaxSessions', "MaxSessions 2\n"], _12=['#MaxSessions', "MaxSessions 2\n"], _13=['MaxStartups', "MaxStartups 2\n"], _14=['#MaxStartups', "MaxStartups 2\n"], _15=['PasswordAuthentication', "PasswordAuthentication no\n"], _16=['#PasswordAuthentication', "PasswordAuthentication no\n"], _17=['AllowGroups', "AllowGroups sshusers\n"], _18=['#AllowGroups', "AllowGroups sshusers\n"], ) General.output_message('These changes have been made to the config') General.warning_message('/etc/ssh/sshd_config') General.output_message( f'''If u need more advanced options feel free to modify it. - Port {SSH_PORT}; - ClientAliveCountMax 0; - ClientAliveInterval 300; - LoginGraceTime 30; - MaxAuthTries 2; - MaxSessions 2; - MaxStartups 2; - PasswordAuthentication no; - AllowGroups sshusers; - PermitRootLogin no; ''') General.output_message( 'removing all Diffie-Hellman keys that are less than 3072 bits long' ) General.run_cmd( "awk '$5 >= 3071' /etc/ssh/moduli | sudo tee /etc/ssh/moduli.tmp") General.run_cmd('mv /etc/ssh/moduli.tmp /etc/ssh/moduli') response = General.answer('do you want to enable 2FA/MFA for SSH?') if response: General.warning_message( 'Note: a user will only need to enter their 2FA/MFA code if they are logging on with their password but not if they are using SSH public/private keys.\n' ) General.run_cmd('apt install libpam-google-authenticator -y') General.warning_message( '\n[ALERT] Notice this can not run as root, at the next machine restart RUN "google-authenticator"\n' ) time.sleep(5) General.output_message( 'Select default option (y in most cases) for all the questions it asks and remember to save the emergency scratch codes.' ) time.sleep(2) General.run_cmd( 'echo -e "\nauth required pam_google_authenticator.so nullok # added by $(whoami) on $(date +"%Y-%m-%d @ %H:%M:%S")" | tee -a /etc/pam.d/sshd' )