├── doc ├── doc_html.zip └── hntool.1 ├── HnTool ├── __init__.py ├── output │ ├── __init__.py │ ├── terminal.py │ └── html.py ├── modules │ ├── __init__.py │ ├── rule.py │ ├── filesystems.py │ ├── ports.py │ ├── util.py │ ├── proftpd.py │ ├── postgresql.py │ ├── vsftpd.py │ ├── php.py │ ├── remote.py │ ├── selinux.py │ ├── system-wide.py │ ├── authentication.py │ ├── ssh.py │ └── apache.py └── core.py ├── .gitignore ├── TODO ├── setup.py ├── hntool.py ├── NEWS ├── AUTHORS ├── README.md └── LICENSE /doc/doc_html.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hdoria/HnTool/HEAD/doc/doc_html.zip -------------------------------------------------------------------------------- /HnTool/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # change this before any new release! 4 | __version__ = "0.1.2" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # binary files and temporary 2 | *.pyc 3 | *~ 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Installer logs 9 | pip-log.txt 10 | 11 | # Unit test / coverage reports 12 | .coverage 13 | .tox 14 | nosetests.xml 15 | 16 | # Translations 17 | *.mo 18 | 19 | # Mr Developer 20 | .mr.developer.cfg 21 | .project 22 | .pydevproject -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | TODO list for HnTool 2 | ===================== 3 | 4 | This is the TODO list for HnTool. If you have a suggestion, please, file an issue on Google Code: 5 | 6 | http://code.google.com/p/hntool/issues/list 7 | 8 | 9 | For hntool 0.1.3 10 | ----------------- 11 | 12 | HnTool core: 13 | 14 | * Add modules for mysql, iptables and squid 15 | * Use exceptions (try/except) 16 | * Show more information for each check 17 | * Add support for translations -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | from os.path import join 5 | from sys import prefix 6 | from HnTool import __version__ 7 | 8 | DATAFILES = [ 9 | (join(prefix, 'share', 'man', 'man1'), [join('doc', 'hntool.1')]), 10 | ('share/doc/hntool-%s' % __version__, ['AUTHORS', 'LICENSE', 'NEWS', 'README', 'TODO'])] 11 | 12 | setup(name='HnTool', 13 | version=str(__version__), 14 | license='GPL-2', 15 | description='A hardening tool for *nixes', 16 | long_description=open('README').read(), 17 | author='Hugo Doria', 18 | author_email='hugodoria@gmail.com', 19 | url='http://code.google.com/p/hntool/', 20 | packages = ['HnTool', 'HnTool.output', 'HnTool.modules'], 21 | entry_points={'console_scripts': ['hntool=HnTool.core:main'] }, 22 | # scripts=['hntool'], 23 | data_files=DATAFILES) 24 | -------------------------------------------------------------------------------- /HnTool/output/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # hntool output - __init__ 3 | # Copyright (C) 2009-2010 Aurelio A. Heckert 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | # 19 | 20 | __formats__ = ['terminal', 'html'] 21 | 22 | -------------------------------------------------------------------------------- /hntool.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # HnTool - A hardening tool for Linux/BSD 5 | # Copyright (C) 2009 Authors 6 | # Authors: 7 | # * Hugo Doria 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | # 23 | # 24 | 25 | from HnTool.core import HnToolCore 26 | 27 | hn = HnToolCore() 28 | hn.run_tests() 29 | -------------------------------------------------------------------------------- /HnTool/modules/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # hntool rules - __init__ 4 | # Copyright (C) 2009-2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | __files__ = ['authentication', 'filesystems', 'php', 'remote', 'system-wide', 'selinux'] 22 | __services__ = ['apache', 'proftpd', 'ports', 'postgresql', 'ssh', 'vsftpd'] 23 | 24 | __all__ = __files__ + __services__ 25 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | HnTool (0.1.2) 2 | - New modules: 3 | proftpd 4 | vsftpd 5 | 6 | - Sorting the output when using -l option 7 | - Add some statistics to html report 8 | - Add a way to check if we should run the module 9 | - Add more documentation in the code 10 | - Add a option to run only specific modules 11 | - Add a option to exclude specific modules when running HnTool 12 | - Fix the output and rule list options 13 | - Several new checks 14 | - Bug fixes 15 | 16 | HnTool (0.1.1) 17 | - Fixes on authentication module 18 | - Fixes on PHP module 19 | - Add colors to check's title 20 | - Make root be required only when running the checks 21 | - Fixes the version declaration (using __version__) 22 | - Started to implement i18n support 23 | - Several bug fixes 24 | 25 | HnTool (0.1) 26 | - New modules: 27 | apache 28 | authentication 29 | filesystems 30 | php 31 | ports 32 | postgresql 33 | remote 34 | ssh 35 | system-wide 36 | util 37 | 38 | - Outputs: 39 | html 40 | terminal 41 | 42 | - Initial structure created 43 | - Several bug fixes 44 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | MAIN AUTHORS 2 | =========================== 3 | 4 | - Hugo Doria 5 | - Rafael Gomes 6 | 7 | 8 | IMPORTANT CONTRIBUTORS 9 | =========================== 10 | 11 | - Alexandro Silva 12 | * Tests and bug report 13 | - Aurélio Heckert 14 | * Html output 15 | - Candido Vieira 16 | * Module contribution (php) 17 | - Elton Pereira 18 | * Code improvement and suggestions 19 | - Filipe Rosset 20 | * Code improvement and suggestions 21 | - Gustavo Picoloto 22 | * Code improvement and suggestions 23 | - Italo Valcy 24 | * Code improvement and suggestions 25 | - Mauricio Vieira 26 | * Code improvement and suggestions 27 | - Késsia Pinheiro 28 | * Module contribution (ports) 29 | - Rafael Gonçalves Martins 30 | * Code improvement and suggestions 31 | - Sebastian SWC 32 | * Module contribution (postgresql) 33 | - Silas Ribas Martins 34 | * Bug reports and suggestions -------------------------------------------------------------------------------- /HnTool/modules/rule.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - base class 4 | # Copyright (C) 2010 Mauricio Vieira 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | 23 | class Rule: 24 | def __init__(self, options): 25 | '''Type must be "config" or "files" 26 | short_name 27 | long_name 28 | ''' 29 | self.check_results = {'ok': [], 'low': [], 'medium': [], 'high': [], 'info': []} 30 | self.type = "" 31 | self.short_name = "" 32 | self.long_name = "" 33 | pass 34 | 35 | def requires(self): 36 | '''This method should return all the required files to run 37 | the module. Usually, it's the same as self.conf_file''' 38 | return None 39 | 40 | def analyze(self, options): 41 | '''Do your magic and fill self.check_results with strings''' -------------------------------------------------------------------------------- /doc/hntool.1: -------------------------------------------------------------------------------- 1 | .TH HNTOOL 1 local 2 | .SH NAME 3 | HnTool \- Hardening tool for Unix. 4 | .SH SYNOPSIS 5 | .ll +8 6 | .B hntool 7 | .RB [ " \-ln " ] 8 | .SH DESCRIPTION 9 | .I hntool 10 | Hardening tool for Unixes. It scans the system and available software, 11 | to detect security. 12 | 13 | .SH OPTIONS 14 | .TP 15 | .B \-e EXCLUDE_LIST, --exclude=EXCLUDE_LIST 16 | don't run the tests specified by MODULES_LIST 17 | .TP 18 | .B \-l --list 19 | returns list of available rules 20 | .TP 21 | .B \--list_output_type 22 | list the avaliable output formats 23 | .TP 24 | .B \-m MODULES_LIST, --modules=MODULES_LIST 25 | run only the tests specified by MODULES_LIST 26 | .TP 27 | .B \-n --nocolors 28 | does not use colors on output 29 | .SH "UNDERSTANDING THE OUTPUT" 30 | .TP 31 | There are 5 types of results: 32 | .TP 33 | .B OK : 34 | Means that the item checked is fine and that you do not need to worry 35 | .TP 36 | .B INFO: 37 | Means that you should know the item status, but probably it is fine. A port 38 | opened, for example. 39 | .TP 40 | .B LOW: 41 | Means that a security problem was found, but it does not provides a high risk 42 | for your system. 43 | .TP 44 | .B MEDIUM: 45 | Things are getting worse and you should start to worry about these itens. 46 | .TP 47 | .B HIGH: 48 | You have an important security hole/problem on your system and you 49 | should fix it NOW or run and save your life. 50 | .SH "COPYRIGHT NOTICE" 51 | Copyright \(co 1998, 1999, 2001, 2002 Free Software Foundation, Inc. 52 | .br 53 | Copyright \(co 1992, 1993 Jean-loup Gailly 54 | .PP 55 | Permission is granted to make and distribute verbatim copies of 56 | this manual provided the copyright notice and this permission notice 57 | are preserved on all copies. 58 | .ig 59 | Permission is granted to process this file through troff and print the 60 | results, provided the printed document carries copying permission 61 | notice identical to this one except for the removal of this paragraph 62 | (this paragraph not being relevant to the printed manual). 63 | .. 64 | .PP 65 | Permission is granted to copy and distribute modified versions of this 66 | manual under the conditions for verbatim copying, provided that the entire 67 | resulting derived work is distributed under the terms of a permission 68 | notice identical to this one. 69 | .PP 70 | Permission is granted to copy and distribute translations of this manual 71 | into another language, under the above conditions for modified versions, 72 | except that this permission notice may be stated in a translation approved 73 | by the Foundation. 74 | -------------------------------------------------------------------------------- /HnTool/modules/filesystems.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - filesystems 4 | # Copyright (C) 2009-2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import commands 23 | from HnTool.modules.rule import Rule as MasterRule 24 | 25 | class Rule(MasterRule): 26 | def __init__(self, options): 27 | MasterRule.__init__(self, options) 28 | self.short_name="filesystems" 29 | self.long_name="Checks filesystems for security problems" 30 | self.type="config" 31 | 32 | def analyze(self, options): 33 | check_results = self.check_results 34 | locate_database = {'nix': '/var/lib/mlocate/mlocate.db', \ 35 | 'bsd': '/var/db/locate.database'} 36 | updatedb_command = {'nix': '/usr/bin/updatedb', \ 37 | 'bsd': '/usr/libexec/locate.updatedb'} 38 | 39 | # Checking if the locate database exists 40 | if not os.path.isfile(locate_database['nix']) and \ 41 | not os.path.isfile(locate_database['bsd']): 42 | if os.path.isfile(updatedb_command['nix']): 43 | check_results['info'].append('%s not found. Please run %s' % \ 44 | (locate_database['nix'], updatedb_command['nix'])) 45 | os_type = 'nix' 46 | elif os.path.isfile(updatedb_command['bsd']): 47 | check_results['info'].append('%s not found. Please run %s' % \ 48 | (locate_database['bsd'], updatedb_command['bsd'])) 49 | os_type = 'bsd' 50 | elif os.path.isfile(locate_database['bsd']): 51 | check_results['ok'].append('locate.database found.') 52 | os_type = 'bsd' 53 | elif os.path.isfile(locate_database['nix']): 54 | check_results['ok'].append('mlocate.db found.') 55 | os_type = 'nix' 56 | # Checking for old files 57 | datafile = locate_database[os_type] 58 | files_old = ['/tmp', datafile] 59 | 60 | for files in files_old: 61 | find_status, find_results = \ 62 | commands.getstatusoutput('find %s -type f -atime +30' % files) 63 | if find_status != 0: 64 | check_results['low'].append('Found old file(s) (+30 days) in ' + files) 65 | if files == datafile: 66 | check_results['info'].append('Please run %s' % updatedb_command[os_type]) 67 | else: 68 | check_results['ok'].append('Did not found old file(s) (+30 days) in ' + files) 69 | 70 | return check_results -------------------------------------------------------------------------------- /HnTool/modules/ports.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - port checks 4 | # Copyright (C) 2009-2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import re 23 | from HnTool.modules.rule import Rule as MasterRule 24 | 25 | class Rule(MasterRule): 26 | def __init__(self, options): 27 | MasterRule.__init__(self, options) 28 | self.short_name="ports" 29 | self.long_name="Checks for open ports" 30 | self.type="config" 31 | 32 | def check_ports(self, lsof_command, check_results): 33 | ''' Check for open ports and services ''' 34 | 35 | out = os.popen('LC_ALL=C ' + lsof_command + ' -i -nP').read() 36 | services = {} 37 | 38 | # the regex need some improvement 39 | for i in re.finditer(r'([A-Za-z0-9_-]+).*:([0-9]+) \(LISTEN\)', out): 40 | service_name = i.group(1) 41 | service_port = i.group(2) 42 | if service_name not in services: 43 | services[service_name] = [service_port] 44 | elif service_port not in services[service_name]: 45 | services[service_name].append(service_port) 46 | 47 | if len(services) > 0: 48 | for service in services: 49 | if len(services[service]) == 1: 50 | tmp_msg = 'port "' + services[service][0] + '"' 51 | else: 52 | tmp_msg = 'ports "' + '" and "'.join(services[service]) \ 53 | + '"' 54 | check_results['info'].append('Service "' + service + '" using ' \ 55 | + tmp_msg + ' found') 56 | else: 57 | check_results['ok'].append("Could not find any open door") 58 | 59 | return check_results 60 | 61 | def analyze(self, options): 62 | check_results = self.check_results 63 | lsof_bin_path = ['/bin/lsof', '/sbin/lsof', '/usr/bin/lsof', '/usr/sbin/lsof'] 64 | 65 | # checks using lsof 66 | # checking if we can find the lsof command 67 | lsof_command = '' 68 | for lsof in lsof_bin_path: 69 | if os.path.isfile(lsof): 70 | lsof_command = lsof 71 | break 72 | 73 | if len(lsof_command) > 0: 74 | self.check_ports(lsof_command, check_results) 75 | else: 76 | check_results['info'].append('Could not find the \'lsof\' command') 77 | 78 | return check_results -------------------------------------------------------------------------------- /HnTool/modules/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool - utility functions 4 | # Copyright (C) 2009-2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | # 21 | 22 | import os 23 | import sys 24 | import re 25 | import shlex 26 | 27 | # Functions 28 | 29 | def is_root(): 30 | '''Method to check if hntool is running as root.''' 31 | if os.getuid() == 0: 32 | return True 33 | 34 | def is_unix(): 35 | '''Method to check if we have power''' 36 | if os.name == 'posix': 37 | return True 38 | return False 39 | 40 | def term_len(): 41 | return int(os.popen('stty size', 'r').read().split()[1]) 42 | 43 | def split_len(seq, length): 44 | result = [] 45 | p = re.compile("(.{,"+str(length)+"})\s") 46 | while len(seq) > 0: 47 | if len(seq) < length: 48 | result.append(seq) 49 | break 50 | else: 51 | tmp,seq = (p.split(seq,1))[1:] 52 | result.append(tmp) 53 | return result 54 | 55 | def hntool_conf_parser(pfile): 56 | '''This method parses a config file and returns a list with 57 | all the lines that aren't comments or blank''' 58 | 59 | result = {} 60 | 61 | if os.path.isfile(pfile): 62 | fp = open(pfile,'r') # reading the file 63 | for line in fp: 64 | # getting all the lines that aren't comments 65 | line = shlex.split(line, comments=True) 66 | if len(line) >= 2: 67 | result[line[0]] = line[1] 68 | fp.close() #closing the file 69 | 70 | # returns a list with all the lines 71 | # [['option1', 'value1'], ['option2', 'value2']] 72 | # and so on 73 | return result 74 | 75 | def hntool_conf_parser_equals(pfile): 76 | '''This method parses a config file and returns a list with 77 | all the lines that aren't comments or blank''' 78 | 79 | result = {} 80 | 81 | if os.path.isfile(pfile): 82 | fp = open(pfile,'r') # reading the file 83 | for line in fp: 84 | # getting all the lines that aren't comments 85 | if not line.startswith('#') and not line == '\n': 86 | line = line.split('=') 87 | line[1] = line[1].rstrip() 88 | if len(line) >= 2: 89 | result[line[0]] = line[1] 90 | fp.close() #closing the file 91 | 92 | # returns a list with all the lines 93 | # [['option1', 'value1'], ['option2', 'value2']] 94 | # and so on 95 | return result 96 | 97 | def requirements_met(pfile): 98 | '''This method should check if all the requirements (files) 99 | are met (one or more files can be found on the system)''' 100 | 101 | for f in pfile: 102 | if not os.path.isfile(f): 103 | return False 104 | 105 | return True 106 | -------------------------------------------------------------------------------- /HnTool/modules/proftpd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - protftpd 4 | # Copyright (C) 2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import HnTool.modules.util 23 | from HnTool.modules.rule import Rule as MasterRule 24 | 25 | class Rule(MasterRule): 26 | def __init__(self, options): 27 | MasterRule.__init__(self, options) 28 | self.short_name="proftpd" 29 | self.long_name="Checks security problems on ProFTPd servers" 30 | self.type="config" 31 | 32 | def analyze(self, options): 33 | check_results = self.check_results 34 | proftpd_conf_file = ['/etc/proftpd.conf'] 35 | proftpd_conf_file_found = False 36 | 37 | for proftpd_conf in proftpd_conf_file: 38 | if os.path.isfile(proftpd_conf): 39 | # dict with all the lines 40 | lines = HnTool.modules.util.hntool_conf_parser(proftpd_conf) 41 | proftpd_conf_file_found = True 42 | 43 | # Checking if ProFTPd is using the default port 44 | if 'Port' in lines: 45 | if int(lines['Port']) == 21: 46 | check_results['info'].append('ProFTPd is running under default port (21)') 47 | elif int(lines['Port']) != 21: 48 | check_results['info'].append('ProFTPd is running under port ' + 49 | lines['Port']) 50 | else: # if we didn't found 'Ports' in lines than ProFTPd uses the default one 51 | check_results['info'].append('ProFTPd is running under default port (21)') 52 | 53 | # Checking if ProFTPd allows more than 3 login attempts 54 | if 'MaxLoginAttempts' in lines: 55 | if int(lines['MaxLoginAttempts']) > 3: 56 | check_results['medium'].append('ProFTPd allows more than 3 login attempts') 57 | elif int(lines['MaxLoginAttempts']) <= 3: 58 | check_results['ok'].append('ProFTPd does not allows more than 3 login attempts') 59 | else: 60 | # if we didn't found 'MaxLoginAttempts' in lines than ProFTPd uses the 61 | # default value for this, which is 3 62 | check_results['medium'].append('ProFTPd allows more than 3 login attempts') 63 | 64 | # Checking if ProFTPd allows root login 65 | if 'RootLogin' in lines: 66 | if lines['RootLogin'] == 'on': 67 | check_results['medium'].append('ProFTPd allows root login') 68 | elif lines['RootLogin'] == 'off': 69 | check_results['ok'].append('ProFTPd does not allows root login') 70 | else: 71 | # if we didn't found 'RootLogin' in lines than ProFTPd uses the 72 | # default value for this. By default proftpd disallows root logins 73 | check_results['ok'].append('ProFTPd does not allows root login') 74 | 75 | # Checking if ProFTPd allows footprinting 76 | if 'ServerIdent' in lines: 77 | if lines['ServerIdent'] == 'on': 78 | check_results['medium'].append('ProFTPd allows footprinting') 79 | elif lines['ServerIdent'] == 'off': 80 | check_results['ok'].append('ProFTPd does not allows footprinting') 81 | else: 82 | check_results['ok'].append('ProFTPd allows footprinting') 83 | 84 | # Checking if we chroot users into the ftp users' home directory 85 | if 'DefaultRoot' in lines: 86 | if lines['DefaultRoot'] != '~': 87 | check_results['medium'].append('ProFTPd does not chroot users') 88 | elif lines['DefaultRoot'] != '~': 89 | check_results['ok'].append('ProFTPd chroot users') 90 | else: 91 | check_results['medium'].append('ProFTPd does not chroot users') 92 | 93 | if not proftpd_conf_file_found: 94 | check_results['info'].append('Could not find a proftpd.conf file') 95 | 96 | return check_results -------------------------------------------------------------------------------- /HnTool/modules/postgresql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - PostgreSQL 4 | # Copyright (C) 2009-2010 Sebastian SWC 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import shlex 23 | from HnTool.modules.rule import Rule as MasterRule 24 | 25 | class Rule(MasterRule): 26 | def __init__(self, options): 27 | MasterRule.__init__(self, options) 28 | self.short_name="postgresql" 29 | self.long_name="Check security problems on PostgreSQL configuration files" 30 | self.type="config" 31 | self.required_files = ['/var/lib/pgsql/data/pg_hba.conf','/var/lib/pgsql/data/postgresql.conf'] 32 | 33 | def requires(self): 34 | return self.required_files 35 | 36 | def analyze(self, options): 37 | check_results = self.check_results 38 | pgsql_conf_file = self.required_files 39 | 40 | for pgsql_conf in pgsql_conf_file: 41 | if os.path.isfile(pgsql_conf): 42 | try: 43 | fp = open(pgsql_conf,'r') 44 | except IOError, (errno, strerror): 45 | check_results['info'].append('Could not open %s: %s' % (pgsql_conf, strerror)) 46 | continue 47 | 48 | # pg_hba.conf validation 49 | if 'pg_hba' in pgsql_conf: 50 | for line in fp.readlines(): 51 | if line[0] != '#' and len(line.strip()) > 0: 52 | line_conf = shlex.split(line) 53 | 54 | # check unix sockets authentication 55 | if line_conf[0] == 'local': 56 | if 'trust' in line: 57 | check_results['medium'].append('Trusted local Unix authentication are allowed') 58 | else: 59 | check_results['ok'].append('Trusted local Unix authentication are not allowed') 60 | 61 | # check tcp/ip host authentication 62 | if 'host' in line_conf[0]: 63 | if 'trust' in line: 64 | check_results['high'].append('Trusted connection on ' + line_conf[3] + ' are allowed') 65 | else: 66 | check_results['ok'].append('Trusted connection on ' + line_conf[3] + ' are not allowed') 67 | 68 | elif 'postgresql' in pgsql_conf: 69 | for line in fp.readlines(): 70 | if len(line.strip()) > 0: 71 | line_conf = shlex.split(line) 72 | 73 | # check the default port 74 | if 'port =' in line: 75 | if '5432' in line: 76 | check_results['low'].append('Server are running on default port (5432)') 77 | else: 78 | check_results['ok'].append('Server are not running at default port (5432)') 79 | # check sshl connections 80 | if 'ssl =' in line: 81 | if 'off' in line: 82 | check_results['low'].append('Server are running without ssl connections support') 83 | else: 84 | check_results['ok'].append('Server are running with ssl connections support') 85 | fp.close() 86 | 87 | return check_results -------------------------------------------------------------------------------- /HnTool/modules/vsftpd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - vsftpd 4 | # Copyright (C) 2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | from HnTool.modules.rule import Rule as MasterRule 23 | 24 | class Rule(MasterRule): 25 | def __init__(self, options): 26 | MasterRule.__init__(self, options) 27 | self.short_name="vsftpd" 28 | self.long_name="Checks security problems on VsFTPd servers" 29 | self.type="config" 30 | self.required_files = ['/etc/vsftpd.conf', '/etc/vsftpd/vsftpd.conf'] 31 | 32 | def requires(self): 33 | return self.required_files 34 | 35 | def vsftpdParser(self, pfile): 36 | '''Method to parse a vsftpd.conf file. Returns a dict with 37 | all [key, value] of the file.''' 38 | 39 | if os.path.isfile(pfile): 40 | fp = open(pfile,'r') 41 | 42 | keysValues = {} 43 | for line in fp.readlines(): 44 | if not line.startswith('#'): 45 | line = line.strip().split('=') 46 | 47 | if len(line) >= 2: 48 | keysValues[line[0]] = line[1] 49 | 50 | fp.close() 51 | 52 | return keysValues 53 | 54 | def analyze(self, options): 55 | check_results = self.check_results 56 | vsftpd_conf_file = self.required_files 57 | 58 | # getting the lines in a [key. value] format 59 | for vsftpd_conf in vsftpd_conf_file: 60 | if os.path.isfile(vsftpd_conf): 61 | lines = self.vsftpdParser(vsftpd_conf) 62 | 63 | # checking if VsFTPd is running on Standalone method 64 | if 'listen' in lines: 65 | if lines['listen'].upper() == 'YES': 66 | check_results['info'].append('Running on StandAlone') 67 | else: 68 | check_results['info'].append('Not running on StandAlone') 69 | else: 70 | check_results['info'].append('Running on StandAlone') 71 | 72 | # checking if VsFTPd is using the default port 73 | if 'listen_port' in lines: 74 | if int(lines['listen_port']) == 21: 75 | check_results['info'].append('Using the default port (21)') 76 | else: 77 | check_results['info'].append('Not using the default port (21)') 78 | else: 79 | check_results['info'].append('Using the default port (21)') 80 | 81 | # checking if chroot is enabled on VsFTPd 82 | if 'chroot_local_user' in lines: 83 | if lines['chroot_local_user'].upper() == 'YES': 84 | check_results['ok'].append('Chrooting local users is enabled') 85 | else: 86 | check_results['high'].append('Chrooting local users is disabled') 87 | else: 88 | check_results['high'].append('Chrooting local users is disabled') 89 | 90 | # checking if anonymous login is enabled 91 | if 'anonymous_enable' in lines: 92 | if lines['anonymous_enable'].upper() == 'YES': 93 | check_results['info'].append('Anonymous login is allowed') 94 | else: 95 | check_results['info'].append('Anonymous login is not allowed') 96 | else: 97 | check_results['info'].append('Anonymous login is allowed') 98 | 99 | # checking if ascii_download_enable or ascii_upload_enable is enabled 100 | if 'ascii_download_enable' in lines or 'ascii_upload_enable' in lines: 101 | if lines['ascii_download_enable'].upper() == 'YES' or \ 102 | lines['ascii_upload_enable'].upper() == 'YES': 103 | check_results['high'].append('ASCII mode data transfers is allowed (DoS is possible)') 104 | else: 105 | check_results['ok'].append('ASCII mode data transfers is not allowed') 106 | else: 107 | check_results['high'].append('ASCII mode data transfers is allowed (DoS is possible)') 108 | 109 | return check_results -------------------------------------------------------------------------------- /HnTool/output/terminal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool - output module - treminal 4 | # Copyright (C) 2009-2010 Authors 5 | # Authors: 6 | # * Hugo Doria 7 | # * Aurelio A. Heckert 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # ( at your option ) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | 23 | import HnTool.modules 24 | import string 25 | 26 | 27 | class Format: 28 | 29 | description = "Human friendly output for terminal" 30 | 31 | def __init__(self, options): 32 | options.add_option("-n", "--term_nocolors", 33 | action="store_false", 34 | dest="term_use_colors", default=True, 35 | help="does not use colors on terminal output") 36 | 37 | def format_status(self, token): 38 | use_colors = self.conf.term_use_colors 39 | if token == 'ok': 40 | if use_colors: 41 | return '[\033[1;92m OK \033[0m]' 42 | else: 43 | return '[ OK ]' 44 | elif token == 'low': 45 | if use_colors: 46 | return '[\033[1;30m LOW \033[0m]' 47 | else: 48 | return '[ LOW ]' 49 | elif token == 'medium': 50 | if use_colors: 51 | return '[\033[1;93m MEDIUM \033[0m]' 52 | else: 53 | return '[ MEDIUM ]' 54 | elif token == 'high': 55 | if use_colors: 56 | return '[\033[1;91m HIGH \033[0m]' 57 | else: 58 | return '[ HIGH ]' 59 | elif token == 'info': 60 | if use_colors: 61 | return '[ \033[37m INFO \033[0m ]' 62 | else: 63 | return '[ INFO ]' 64 | 65 | # Method to show the check results 66 | def msg_status(self, msg, status): 67 | ''' 68 | Method to show the check results 69 | ''' 70 | maxmsg_len = HnTool.modules.util.term_len() - 15 71 | # verifica se é str, se for converte para unicode para garantir que 72 | # letras acentuadas nao serao consideradas de tamanho 2 73 | # isso evita o erro de formatacao em strings acentuadas 74 | if isinstance(msg, str): 75 | msg = unicode(msg, 'utf-8') 76 | msg_splited = HnTool.modules.util.split_len(msg, maxmsg_len) 77 | 78 | result = "" 79 | i = 0 80 | while i < len(msg_splited) - 1: 81 | result += u' {0}\n'.format(string.ljust(msg_splited[i], maxmsg_len)) 82 | i += 1 83 | 84 | return result + " " + \ 85 | string.ljust(msg_splited[i], maxmsg_len) + \ 86 | self.format_status(status) 87 | 88 | def output(self, report, conf): 89 | self.conf = conf 90 | # Print all the results, from the 5 types of messages ( ok, low, medium, high and info ). 91 | # First message is the "ok" one ( m['results'][0] ). The second one is 92 | # "low" ( m['results'][1] ). The third ( m['results'][2] ) is for "warnings" 93 | # and the fourth one is "high" ( m['results'][3] ), The last one is for 94 | # info messages. 95 | 96 | for m in report: 97 | if conf.term_use_colors: 98 | print '\n \033[96m' + m['title'] + '\033[0m' 99 | else: 100 | print '\n' + m['title'] 101 | 102 | if m['results']['ok'] != []: 103 | for result in m['results']['ok']: 104 | print self.msg_status(result, 'ok') 105 | if m['results']['low'] != []: 106 | for result in m['results']['low']: 107 | print self.msg_status(result, 'low') 108 | if m['results']['medium'] != []: 109 | for result in m['results']['medium']: 110 | print self.msg_status(result, 'medium') 111 | if m['results']['high'] != []: 112 | for result in m['results']['high']: 113 | print self.msg_status(result, 'high') 114 | if m['results']['info'] != []: 115 | for result in m['results']['info']: 116 | print self.msg_status(result, 'info') 117 | -------------------------------------------------------------------------------- /HnTool/modules/php.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - php 4 | # Copyright (C) 2009-2010 Candido Vieira 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import ConfigParser 23 | import HnTool.modules.util 24 | from HnTool.modules.rule import Rule as MasterRule 25 | 26 | class Rule(MasterRule): 27 | def __init__(self, options): 28 | MasterRule.__init__(self, options) 29 | self.short_name="php" 30 | self.long_name="Checks security problems on php config file" 31 | self.type="config" 32 | self.required_files = ['/etc/php5/apache2/php.ini', '/etc/php5/cli/php.ini', '/etc/php.ini'] 33 | 34 | def requires(self): 35 | return self.required_files 36 | 37 | def analyze(self, options): 38 | check_results = self.check_results 39 | conf_files = self.required_files 40 | 41 | for php_conf in conf_files: 42 | if os.path.isfile(php_conf): 43 | 44 | config = ConfigParser.ConfigParser() 45 | 46 | try: 47 | config.read(php_conf) 48 | except ConfigParser.ParsingError, (errno, strerror): 49 | check_results['info'].append('Could not parse %s: %s' % (php_conf, strerror)) 50 | continue 51 | 52 | if not config.has_section('PHP'): 53 | check_results['info'].append('%s is not a PHP config file' % (php_conf)) 54 | continue 55 | 56 | if config.has_option('PHP', 'register_globals'): 57 | rg = config.get('PHP', 'register_globals').lower() 58 | if rg == 'on': 59 | check_results['medium'].append('Register globals is on (%s)' % (php_conf)) 60 | elif rg == 'off': 61 | check_results['ok'].append('Register globals is off (%s)' % (php_conf)) 62 | else: 63 | check_results['info'].append('Unknown value for register globals (%s)' % (php_conf)) 64 | else: 65 | check_results['info'].append('Register globals not found (%s)' % (php_conf)) 66 | 67 | if config.has_option('PHP', 'safe_mode'): 68 | sm = config.get('PHP', 'safe_mode').lower() 69 | if sm == 'on': 70 | check_results['low'].append('Safe mode is on (fake security) (%s)' % (php_conf)) 71 | elif sm == 'off': 72 | check_results['info'].append('Safe mode is off (%s)' % (php_conf)) 73 | else: 74 | check_results['info'].append('Unknown value for safe mode (%s)' % (php_conf)) 75 | else: 76 | check_results['info'].append('Safe mode not found (%s)' % (php_conf)) 77 | 78 | if config.has_option('PHP', 'display_errors'): 79 | de = config.get('PHP', 'display_errors').lower() 80 | if de == 'on': 81 | check_results['medium'].append('Display errors is on (stdout) (%s)' % (php_conf)) 82 | elif de == 'off': 83 | check_results['ok'].append('Display errors is off (%s)' % (php_conf)) 84 | elif de == 'stderr': 85 | check_results['info'].append('Display errors set to stderr (%s)' % (php_conf)) 86 | else: 87 | check_results['info'].append('Unknown value for display errors (%s)' % (php_conf)) 88 | else: 89 | check_results['info'].append('Display errors not found (%s)' % (php_conf)) 90 | 91 | if config.has_option('PHP', 'expose_php'): 92 | ep = config.get('PHP', 'expose_php').lower() 93 | if ep == 'on': 94 | check_results['low'].append('Expose PHP is on (%s)' % (php_conf)) 95 | elif ep == 'off': 96 | check_results['ok'].append('Expose PHP is off (%s)' % (php_conf)) 97 | else: 98 | check_results['info'].append('Unknown value for expose PHP (%s)' % (php_conf)) 99 | else: 100 | check_results['info'].append('Expose PHP not found (%s)' % (php_conf)) 101 | 102 | return check_results 103 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HnTool 2 | 3 | ## What is it? 4 | 5 | HnTool is an open source (GPLv2) hardening tool for Unix. It scans your system for vulnerabilities or problems in configuration files allowing you to get a quick overview of the security status of your system. 6 | 7 | To use HnTool download it and run: :: 8 | 9 | # ./hntool 10 | 11 | 12 | ## Supported systems 13 | 14 | HnTool was already tested and is working on: 15 | 16 | * Arch Linux 17 | * CentOS 18 | * Debian 19 | * Fedora 20 | * Gentoo 21 | * Ubuntu 22 | 23 | If you are using HnTool on a system that is not listed above, please, let us know. 24 | 25 | ## How to install 26 | 27 | To install HnTool run the following command, as root: 28 | 29 | # python setup.py install --prefix /usr/ --root / 30 | 31 | ## How to use 32 | 33 | Run HnTool with: 34 | 35 | # ./hntool 36 | 37 | You can also see the hntool(1) manual by typing 'man hntool' at the command line or see the usage help: 38 | 39 | $ hntool -h 40 | 41 | 42 | ## Understanding the output 43 | 44 | There are 5 types of results: 45 | 46 | * OK : 47 | Means that the item checked is fine and that you do not need to worry 48 | 49 | * INFO: 50 | Means that you should know the item status, but probably it is fine. A port 51 | opened, for example. 52 | 53 | * LOW: 54 | Means that a security problem was found, but it does not provides a high risk 55 | for your system. 56 | 57 | * MEDIUM: 58 | Things are getting worse and you should start to worry about these itens. 59 | 60 | * HIGH: 61 | You have an important security hole/problem on your system and you 62 | should fix it NOW or run and save your life. 63 | 64 | 65 | ## How can I help? 66 | 67 | There are several ways that you can contribute and help HnTool's development. 68 | You can contribute with code, patchs, bugs and feature requests. 69 | 70 | To report a bug or a feature request for HnTool, file a issue in our Google Code 71 | page: https://github.com/hdoria/HnTool 72 | 73 | If you're reporting a bug, please give concrete examples of how and where the 74 | problem occurs. 75 | 76 | If you've a patch (fixing a bug or a new HnTool module), then you can file an 77 | issue on Google Code too: http://code.google.com/p/hntool/issues/list 78 | 79 | HnTool's source is available on: 80 | 81 | https://github.com/hdoria/HnTool 82 | 83 | 84 | ## How to create a module 85 | 86 | This section documents the innards of HnTool and specifies how to create 87 | a new module. 88 | 89 | The main HnTool program (hntool.py) runs a list of rules defined in `__files__` 90 | and `__services__`. 91 | 92 | * __files__ : 93 | defines the rules which process simple files and configs. 94 | 95 | * __services__ : 96 | defines the rules which checks the security on services and 97 | daemons. 98 | 99 | Once your module is finalized, remember to add it to the appropriate array 100 | `(__files__ or __services__)` defined in `hntool/__init__.py` 101 | 102 | A sample HnTool module is like this (hntool/ssh.py): 103 | 104 | import os 105 | import HnTool.modules.util 106 | from HnTool.modules.rule import Rule as MasterRule 107 | 108 | class Rule(MasterRule): 109 | def __init__(self, options): 110 | MasterRule.__init__(self, options) 111 | self.short_name="ssh" 112 | self.long_name="Checks security problems on sshd config file" 113 | self.type="config" 114 | self.required_files = ['/etc/ssh/sshd_config', '/etc/sshd_config'] 115 | 116 | def requires(self): 117 | return self.required_files 118 | 119 | def analyze(self, options): 120 | check_results = self.check_results 121 | ssh_conf_file = self.required_files 122 | 123 | for sshd_conf in ssh_conf_file: 124 | if os.path.isfile(sshd_conf): 125 | # dict with all the lines 126 | lines = HnTool.modules.util.hntool_conf_parser(sshd_conf) 127 | 128 | # Checking if SSH is using the default port 129 | if 'Port' in lines: 130 | if int(lines['Port']) == 22: 131 | check_results['low'].append('SSH is using the default port') 132 | else: 133 | check_results['ok'].append('SSH is not using the default port') 134 | else: 135 | check_results['low'].append('SSH is using the default port') 136 | 137 | return check_results 138 | 139 | 140 | Mostly, the code is self-explanatory. The following are the list of the attributes and methods 141 | that each HnTool module must have: 142 | 143 | * self.short_name 144 | String containing a short name of the module. Usually,this is the 145 | same as the basename of the module file. 146 | 147 | * self.long_name 148 | String containing a concise description of the module. This 149 | description is used when listing all the rules using hntool -l. 150 | 151 | * analyze(self) 152 | Should return a list comprising in turn of five lists: ok, low, medium, 153 | high and info. 154 | 155 | * self.type 156 | "files" or "config" for a module processing simple files and configs 157 | "services" for a module processing services and daemons 158 | -------------------------------------------------------------------------------- /HnTool/modules/remote.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - remote access 4 | # Copyright (C) 2009-2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | from HnTool.modules.rule import Rule as MasterRule 23 | 24 | class Rule(MasterRule): 25 | def __init__(self, options): 26 | MasterRule.__init__(self, options) 27 | self.short_name="remote_access" 28 | self.long_name="Checks for services with remote access allowed" 29 | self.type="config" 30 | 31 | def analyze(self, options): 32 | check_results = self.check_results 33 | hosts_allow_file = '/etc/hosts.allow' 34 | hosts_deny_file = '/etc/hosts.deny' 35 | 36 | # Checks about the hosts.deny file 37 | if os.path.isfile(hosts_deny_file): 38 | fp = open(hosts_deny_file, 'r') #open file 39 | # getting all the lines from the file 40 | lines = [x.strip('\n').split(':') for x in fp.readlines()] 41 | 42 | # getting all the lines that are not comments or blanks 43 | all_access = [x for i, x in enumerate(lines) \ 44 | if not (x[0].startswith('#') or not x[0] != '')] 45 | 46 | for indexc, line in enumerate(all_access): 47 | for indexv, col in enumerate(line): 48 | all_access[indexc][indexv] = col.strip() 49 | 50 | if all_access: 51 | for index, service in enumerate(all_access): 52 | 53 | # if len(service) >= 3 then the file is using 3 parameters 54 | if len(service) == 3: 55 | #specific service with all access 56 | if (service[0] == 'ALL' and \ 57 | service[1] == 'ALL' and \ 58 | service[2] == 'DENY'): 59 | check_results['ok'].append("By default, services are rejecting connections") 60 | 61 | # if len(service) == 2 then the file is using 2 parameters 62 | elif len(service) == 2: 63 | #specific service with all access 64 | if (service[0] == 'ALL' and \ 65 | service[1] == 'ALL'): 66 | check_results['ok'].append("By default, services are rejecting connections") 67 | else: 68 | check_results['low'].append('Default policy not found') 69 | 70 | #closing file 71 | fp.close() 72 | 73 | 74 | # Checks about the hosts.allow file 75 | if os.path.isfile(hosts_allow_file): 76 | fp = open(hosts_allow_file,'r') 77 | all_access = [x.strip('\n').split(':') for x in fp.readlines()] 78 | 79 | #check access in hosts.allow and remove comments and whitespaces 80 | all_access = [x for i,x in enumerate(all_access) \ 81 | if not (x[0].startswith('#') or not (x[0] != ''))] 82 | for indexc, line in enumerate(all_access): 83 | for indexv, col in enumerate(line): 84 | all_access[indexc][indexv] = col.strip() 85 | 86 | if all_access: 87 | for index, service in enumerate(all_access): 88 | #specific service with all access 89 | if service[0] != 'ALL' and service[1] == 'ALL': 90 | check_results['medium'].append('Service "' + service[0] + \ 91 | '" accepts remote connections from ALL') 92 | #specific service with all access 93 | elif service[0] != 'ALL' and service[1] != 'ALL': 94 | check_results['medium'].append('Service "' + service[0] + \ 95 | '" accepts remote connections from "' + service[1] + '"') 96 | #any service to specific address 97 | elif service[0] == 'ALL' and service[1] != 'ALL': 98 | check_results['medium'].append('Services are accepting remote access from "' + service[1] + '"') 99 | #any service with all access 100 | elif service[0] == 'ALL' and service[1] == 'ALL': 101 | check_results['high'].append('Services are accepting remote access from ALL') 102 | #closing file 103 | fp.close() 104 | else: 105 | check_results['ok'].append("There's no service accepting remote connections from ALL") 106 | 107 | return check_results -------------------------------------------------------------------------------- /HnTool/modules/selinux.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - selinux 4 | # Copyright (C) 2017 Dan Persons 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import re 23 | import shlex 24 | import HnTool.modules.util 25 | from HnTool.modules.rule import Rule as MasterRule 26 | 27 | class Rule(MasterRule): 28 | def __init__(self, options): 29 | MasterRule.__init__(self, options) 30 | self.short_name="selinux" 31 | self.long_name="Checks SELinux configuration" 32 | self.type="config" 33 | self.required_files = ['/etc/selinux/config'] 34 | # TO DO: add try/except to get live config settings 35 | # sestatus, checklive 36 | # Compare to configured settings 37 | 38 | def requires(self): 39 | return self.required_files 40 | 41 | def analyze(self, options): 42 | check_results = self.check_results 43 | selinux_conf_file = self.required_files 44 | 45 | for selinux_conf in selinux_conf_file: 46 | if os.path.isfile(selinux_conf): 47 | # dict with all the lines 48 | lines = HnTool.modules.util.hntool_conf_parser_equals(selinux_conf) 49 | 50 | # Checking SELinux policy enforcement config 51 | if 'SELINUX' in lines: 52 | if lines['SELINUX'] == 'enforcing': 53 | check_results['ok'].append('Configured in enforcing mode') 54 | elif lines['SELINUX'] == 'permissive': 55 | check_results['med'].append('Configured in permissive mode') 56 | elif lines['SELINUX'] == 'disabled': 57 | check_results['high'].append('Configured as disabled') 58 | else: 59 | check_results['high'].append('Policy enforcement unknown') 60 | else: 61 | check_results['high'].append('Policy enforcement not found') 62 | 63 | # Checking SELinux policy type config 64 | if 'SELINUXTYPE' in lines: 65 | if lines['SELINUXTYPE'] == 'mls': 66 | check_results['ok'].append('Configured using a multi-level security policy') 67 | elif lines['SELINUXTYPE'] == 'mcs': 68 | check_results['ok'].append('Configured using a multi-category security policy') 69 | elif lines['SELINUXTYPE'] == 'strict': 70 | check_results['ok'].append('Configured using a strict security policy') 71 | elif lines['SELINUXTYPE'] == 'targeted': 72 | check_results['low'].append('Configured using a targeted policy') 73 | elif lines['SELINUXTYPE'] == 'standard': 74 | check_results['med'].append('Configured using a standard policy') 75 | elif lines['SELINUXTYPE'] == 'minimum': 76 | check_results['high'].append('Configured using a minimum security policy') 77 | else: 78 | check_results['high'].append('SELinux policy type is unknown') 79 | else: 80 | check_results['high'].append('SELinux policy type not found') 81 | 82 | # To Do: add check to make sure live env matches config 83 | liveconfig = os.popen('sestatus').readlines() 84 | optionformat = re.compile('(.*):') 85 | checklive = {} 86 | 87 | for item in liveconfig: 88 | thing = item.rstrip() 89 | itemname = re.findall(optionformat, thing) 90 | itemval = shlex.split(thing)[-1] 91 | # itemval = thing.split(':')[-1].rstrip() 92 | checklive[itemname[0]] = itemval 93 | 94 | if 'bash' in checklive: 95 | if 'SELINUX' in lines: 96 | check_results['high'].append('SELinux: sestatus command not found') 97 | if 'sestatus' in checklive: 98 | if 'SELINUX' in lines: 99 | check_results['high'].append('SELinux: sestatus command not found') 100 | 101 | if 'SELinux status' in checklive: 102 | if checklive['SELinux status'] == 'enabled': 103 | check_results['ok'].append('SELinux is enabled') 104 | elif checklive[-1] == 'disabled' and \ 105 | lines['SELINUX'] != 'disabled': 106 | check_results['high'].append('SELinux is disabled but should be on') 107 | else: 108 | check_results['high'].append('SELinux is disabled') 109 | 110 | if 'Current mode' and 'Mode from config file' in checklive: 111 | if checklive['Current mode'] and checklive['Mode from config file'] == lines['SELINUX']: 112 | check_results['ok'].append('Enforcement running as configured') 113 | else: 114 | check_results['high'].append('Enforcement not running as configured') 115 | 116 | if 'Loaded policy name' in checklive: 117 | if checklive['Loaded policy name'] == lines['SELINUXTYPE']: 118 | check_results['ok'].append('Policy type running as configured') 119 | else: 120 | check_results['high'].append('Policy type not running as configured') 121 | 122 | if 'Policy MLS status' in checklive: 123 | if checklive['Policy MLS status'] == 'enabled': 124 | check_results['ok'].append('Policy MLS status enabled') 125 | else: 126 | check_results['low'].append('Policy MLS status disabled') 127 | 128 | if 'Policy deny_unknown status' in checklive: 129 | if checklive['Policy deny_unknown status'] == 'denied': 130 | check_results['ok'].append('Policy deny_unkown status set to denied') 131 | else: 132 | check_results['low'].append('Policy deny_unknown status set to allowed') 133 | 134 | return check_results 135 | -------------------------------------------------------------------------------- /HnTool/modules/system-wide.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - system-wide 4 | # Copyright (C) 2009-2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import stat 23 | from HnTool.modules.rule import Rule as MasterRule 24 | 25 | class Rule(MasterRule): 26 | def __init__(self, options): 27 | MasterRule.__init__(self, options) 28 | self.short_name="system-wide" 29 | self.long_name="Checks security problems on system-wide configuration" 30 | self.type="config" 31 | 32 | def analyze(self, options): 33 | check_results = self.check_results 34 | grub_conf_file = ['/boot/grub/menu.lst'] 35 | inittab_file = ['/etc/inittab'] 36 | sysctl_conf_file = ['/etc/sysctl.conf'] 37 | 38 | # Checking GRUB configuration file 39 | for grub_conf in grub_conf_file: 40 | if os.path.isfile(grub_conf): 41 | try: 42 | fp = open(grub_conf,'r') 43 | except IOError, (errno, strerror): 44 | check_results[4].append('Could not open %s: %s' % (grub_conf, strerror)) 45 | continue 46 | 47 | grub_conf_lines = [x.strip('\n') for x in fp.readlines()] 48 | 49 | # Getting only the line that starts with password 50 | password_lines = [x for x in grub_conf_lines if x.startswith('password')] 51 | 52 | # Checking if grub is asking for a password 53 | # if password_lines size is more than 0 then there's a line 54 | # starting with 'passwd' on our grub config file 55 | if len(password_lines) > 0: 56 | check_results['ok'].append('GRUB asks for a password') 57 | else: 58 | check_results['low'].append('GRUB does not ask for a password') 59 | 60 | # Closing the grub_conf file 61 | fp.close() 62 | 63 | # Checking grub_conf permissions 64 | if oct(os.stat(grub_conf)[stat.ST_MODE] & 0777) == oct(0600): 65 | check_results['ok'].append('Permissions on ' + grub_conf + 66 | ' are correct') 67 | elif oct(os.stat(grub_conf)[stat.ST_MODE] & 0777) > oct(0600): 68 | check_results['low'].append('Permissions on ' + grub_conf + 69 | ' are greater than 600') 70 | 71 | # Checking inittab file 72 | for inittab in inittab_file: 73 | if os.path.isfile(inittab): 74 | try: 75 | fp = open(inittab, 'r') # open the inittab file 76 | except IOError, (errno, strerror): 77 | check_results[4].append('Could not open %s: %s' % (inittab, strerror)) 78 | continue 79 | 80 | # Getting the lines from the inititab file 81 | inittab_lines = [x.strip('\n') for x in fp.readlines()] 82 | 83 | if 'su:S:wait:/sbin/sulogin' in inittab_lines: 84 | check_results['ok'].append('Single-User mode requires' + 85 | ' authentication') 86 | else: 87 | check_results['medium'].append('Single-User mode does not' + 88 | ' requires authentication') 89 | 90 | # Closing the inititab file 91 | fp.close() 92 | 93 | # Checking sysctl.conf file 94 | for sysctl in sysctl_conf_file: 95 | if os.path.isfile(sysctl): 96 | try: 97 | fp = open(sysctl, 'r') # open the sysctl.conf file 98 | except IOError, (errno, strerror): 99 | check_results[4].append('Could not open %s: %s' % (sysctl, strerror)) 100 | continue 101 | 102 | # Getting the lines from the sysctl configuration file 103 | # We need to strip all white spaces and \n for each line 104 | # This way we don't need to worry if the user is using 105 | # var = 1 (with spaces) or var=1 (w/o spaces) in sysctl.conf 106 | sysctl_lines = [x.strip('\n').replace(' ', '') for x in fp.readlines()] 107 | 108 | # Checking if core dumps are enabled 109 | # disabled is good 110 | if 'fs.suid_dumpable=0' in sysctl_lines: 111 | check_results['ok'].append('Core dumps are disabled') 112 | else: 113 | check_results['low'].append('Core dumps are enabled') 114 | 115 | # Checking if exec shield is enabled 116 | # enabled is good 117 | if ('kernel.exec-shield=1' and 'kernel.randomize_va_space=1') in sysctl_lines: 118 | check_results['ok'].append('ExecShield is enabled') 119 | else: 120 | check_results['low'].append('ExecShield is disabled') 121 | 122 | # Checking if TCP SYN Cookie Protection is enabled 123 | # enabled is good 124 | if 'net.ipv4.tcp_syncookies=1' in sysctl_lines: 125 | check_results['ok'].append('TCP SYN Cookie Protection is enabled') 126 | else: 127 | check_results['low'].append('TCP SYN Cookie Protection is disabled') 128 | 129 | # Checking if we are ignoring broadcast requests 130 | # enabled is good 131 | if 'net.ipv4.icmp_echo_ignore_broadcasts=1' in sysctl_lines: 132 | check_results['ok'].append('Ignore broadcast request is enabled') 133 | else: 134 | check_results['low'].append('Ignore broadcast request is disabled') 135 | 136 | # Checking if Ping Reply is disabled 137 | # disabled is good 138 | if 'net.ipv4.icmp_echo_ignore_all=1' in sysctl_lines: 139 | check_results['ok'].append('Ping reply is disabled') 140 | else: 141 | check_results['low'].append('Ping reply is enabled') 142 | 143 | # Closing the sysctl file 144 | fp.close() 145 | 146 | return check_results -------------------------------------------------------------------------------- /HnTool/modules/authentication.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - authentication 4 | # Copyright (C) 2009-2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import stat 23 | import HnTool.modules.util 24 | from HnTool.modules.rule import Rule as MasterRule 25 | 26 | class Rule(MasterRule): 27 | def __init__(self, options): 28 | MasterRule.__init__(self, options) 29 | self.short_name="authentication" 30 | self.long_name="Checks users, groups and authentications" 31 | self.type="config" 32 | 33 | def analyze(self, options): 34 | check_results = self.check_results 35 | passwd_file = '/etc/passwd' 36 | shadow_file = '/etc/shadow' 37 | logindefs_file = '/etc/login.defs' 38 | invalid_shell = ['/sbin/nologin', '/bin/false', '/usr/bin/false', \ 39 | '/var/empty/usr/bin/false', '/usr/sbin/nologin'] 40 | 41 | # Checks about the shadow file 42 | if os.path.isfile(shadow_file): 43 | # Checking passwd_file permissions 44 | if oct(os.stat(shadow_file)[stat.ST_MODE] & 0777) == oct(0400): 45 | check_results['ok'].append('Permissions on shadow file are '+ 46 | 'correct (400)') 47 | if oct(os.stat(shadow_file)[stat.ST_MODE] & 0777) == oct(0600): 48 | check_results['ok'].append('Permissions on shadow file are '+ 49 | 'correct (600)') 50 | elif oct(os.stat(shadow_file)[stat.ST_MODE] & 0777) > oct(0600): 51 | check_results['high'].append('Permissions on shadow file are '+ 52 | 'greater than 600') 53 | 54 | # Checks about the passwd file 55 | if os.path.isfile(passwd_file): 56 | # Checking passwd_file permissions 57 | if oct(os.stat(passwd_file)[stat.ST_MODE] & 0777) == oct(0644): 58 | check_results['ok'].append('Permissions on passwd file are '+ 59 | 'correct (644)') 60 | elif oct(os.stat(passwd_file)[stat.ST_MODE] & 0777) > oct(0644): 61 | check_results['high'].append('Permissions on passwd file are '+ 62 | 'greater than 644') 63 | 64 | # Gets the values of each line of the passwd file 65 | passwd_fp = open(passwd_file, 'r') 66 | users = [x.strip('\n').split(':') for x in passwd_fp.readlines()] 67 | 68 | users = [x for i, x in enumerate(users) \ 69 | if not (x[0].startswith('#') or not (x[0] != ''))] 70 | for indexc, line in enumerate(users): 71 | for indexv, col in enumerate(line): 72 | users[indexc][indexv] = col.strip() 73 | 74 | if users: 75 | 76 | # will be true if we find users with UID 0 77 | users_with_uid_zero = False 78 | 79 | # Checking if there's a user (other than root) that has UID 0 80 | for user in users: 81 | if user[0] != 'root' and user[2] == '0': 82 | check_results['high'].append('There is a user (not root) ' + 83 | 'with UID 0') 84 | users_with_uid_zero = True 85 | 86 | # Checking if there's a user (other than root) 87 | # with a valid shell 88 | if user[0] != 'root' and user[-1] not in invalid_shell: 89 | check_results['medium'].append('User "' + user[0] + '" may ' + 90 | 'have a harmful shell (' + user[-1] + ')') 91 | 92 | if not users_with_uid_zero: 93 | check_results['ok'].append("There aren't users (not root) " + 94 | " with UID 0") 95 | 96 | # closing the passwd file 97 | passwd_fp.close() 98 | 99 | # Checking permissions on home directories (including /root) 100 | home_permissions_problems = False 101 | for dir in os.listdir('/home'): 102 | if os.path.isdir('/home/' + dir): 103 | if oct(os.stat('/home/' + dir)[stat.ST_MODE] & 0777) > oct(0100): 104 | check_results['medium'].append('Permissions on /home/' + dir + 105 | ' are greater than 700') 106 | home_permissions_problems = True 107 | 108 | # Checking the permissions of the root home directory 109 | if os.path.exists('/root'): 110 | if oct(os.stat('/root')[stat.ST_MODE] & 0777) > oct(0750): 111 | check_results['medium'].append('Permissions on /root dir are '+ 112 | 'greater than 700') 113 | home_permissions_problems = True 114 | 115 | if not home_permissions_problems: 116 | # if we got here, then we didnt found permissions problems 117 | check_results['ok'].append('Did not found permissions ' + 118 | 'problems on home directories') 119 | 120 | # Checks about the login.defs file 121 | if os.path.isfile(logindefs_file): 122 | lines = HnTool.modules.util.hntool_conf_parser(logindefs_file) 123 | 124 | # Checking when passwords expires 125 | if 'PASS_MAX_DAYS' in lines: 126 | if int(lines['PASS_MAX_DAYS']) > 90: 127 | check_results['medium'].append('By default passwords do not ' + 128 | 'expires on 90 days or less') 129 | else: 130 | check_results['ok'].append('By default passwords expires ' + 131 | 'on 90 days or less') 132 | else: 133 | check_results['high'].append('By default passwords does not expires') 134 | 135 | # Checking the fail delay 136 | if 'FAIL_DELAY' in lines: 137 | if int(lines['FAIL_DELAY']) < 3: 138 | check_results['medium'].append('Delay between failed login prompts is less than 3s') 139 | else: 140 | check_results['ok'].append('Delay between failed login prompts is more than 3s') 141 | else: 142 | check_results['high'].append('Delay between failed login prompts is not defined') 143 | 144 | # Checking pass min days 145 | if 'PASS_MIN_DAYS' in lines: 146 | if int(lines['PASS_MIN_DAYS']) < 5: 147 | check_results['medium'].append('Min. number of days between password changes is less than 5') 148 | else: 149 | check_results['ok'].append('Min. number of days between password changes is more than 5') 150 | else: 151 | check_results['high'].append('Min. number of days between password changes is not defined') 152 | 153 | return check_results -------------------------------------------------------------------------------- /HnTool/modules/ssh.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - ssh 4 | # Copyright (C) 2009-2010 Hugo Doria 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | import os 22 | import HnTool.modules.util 23 | from HnTool.modules.rule import Rule as MasterRule 24 | 25 | class Rule(MasterRule): 26 | def __init__(self, options): 27 | MasterRule.__init__(self, options) 28 | self.short_name="ssh" 29 | self.long_name="Checks security problems on sshd config file" 30 | self.type="config" 31 | self.required_files = ['/etc/ssh/sshd_config'] 32 | 33 | def requires(self): 34 | return self.required_files 35 | 36 | def analyze(self, options): 37 | check_results = self.check_results 38 | ssh_conf_file = self.required_files 39 | 40 | for sshd_conf in ssh_conf_file: 41 | if os.path.isfile(sshd_conf): 42 | # dict with all the lines 43 | lines = HnTool.modules.util.hntool_conf_parser(sshd_conf) 44 | 45 | # Checking if SSH is using the default port 46 | if 'Port' in lines: 47 | if int(lines['Port']) == 22: 48 | check_results['low'].append('SSH is using the default port') 49 | else: 50 | check_results['ok'].append('SSH is not using the default port') 51 | else: 52 | check_results['low'].append('SSH is using the default port') 53 | 54 | # Checking if SSH is using protocol v2 (recommended) 55 | if 'Protocol' in lines: 56 | if int(lines['Protocol']) == 2: 57 | check_results['ok'].append('SSH is using protocol v2') 58 | else: 59 | check_results['high'].append('SSH is not using protocol v2') 60 | else: 61 | check_results['high'].append('SSH is not using protocol v2') 62 | 63 | # Checking if the Root Login is allowed 64 | if 'PermitRootLogin' in lines: 65 | if lines['PermitRootLogin'] == 'yes': 66 | check_results['medium'].append('Root access allowed') 67 | else: 68 | check_results['ok'].append('Root access is not allowed') 69 | else: 70 | check_results['medium'].append('Root access is allowed') 71 | 72 | # Disconnects no successfully login after 30 seconds 73 | if 'LoginGraceTime' in lines: 74 | if int(lines['LoginGraceTime']) > 30: 75 | check_results['low'].append('Keeping connection login attempt to more than 30 seconds') 76 | else: 77 | check_results['ok'].append('Disconnects no successfully login after 30 seconds') 78 | else: 79 | check_results['low'].append('Keeping connection login attempt to more than 30 seconds') 80 | 81 | # Disconnects session after more than 120 seconds of inactivity 82 | if 'ClientAliveInterval' in lines and int(lines['Protocol']) == 2: 83 | if int(lines['ClientAliveInterval']) > 120: 84 | check_results['low'].append('Keeping inactive session') 85 | else: 86 | check_results['ok'].append('Disconnects session after more than 120 seconds of inactivity') 87 | else: 88 | check_results['low'].append('Keeping inactive session') 89 | 90 | # Total number of checkalive message sent by the ssh server 91 | if 'ClientAliveCountMax' in lines and int(lines['Protocol']) == 2: 92 | if int(lines['ClientAliveInterval']) >= 4: 93 | check_results['low'].append('High total number of checkalive message') 94 | else: 95 | check_results['ok'].append('Great value checkalive message') 96 | else: 97 | check_results['low'].append('High total number of checkalive message') 98 | 99 | # Number of tries to login 100 | if 'MaxAuthTries' in lines: 101 | if int(lines['MaxAuthTries']) >= 5: 102 | check_results['low'].append('High value for login attempts') 103 | else: 104 | check_results['ok'].append('Good value for login attempts') 105 | else: 106 | check_results['low'].append('High value for login attempts') 107 | 108 | # Total number of open sessions 109 | if 'MaxSessions' in lines: 110 | if int(lines['MaxSessions']) > 5: 111 | check_results['low'].append('High number of open sessions allowed') 112 | else: 113 | check_results['ok'].append('Good value of open sessions allowed') 114 | else: 115 | check_results['low'].append('High number of open sessions allowed') 116 | 117 | # Checking if empty password are allowed (shouldn't) 118 | if 'PermitEmptyPasswords' in lines: 119 | if lines['PermitEmptyPasswords'] == 'yes': 120 | check_results['high'].append('Empty passwords are allowed') 121 | else: 122 | check_results['ok'].append('Empty passwords are not allowed') 123 | else: 124 | check_results['high'].append('Empty passwords are allowed') 125 | 126 | # Specifies whether rhosts or shosts files should not be used in authentication 127 | if 'IgnoreRhosts' in lines: 128 | if lines['IgnoreRhosts'] == 'no': 129 | check_results['high'].append('Using rhosts file') 130 | else: 131 | check_results['ok'].append('Not using rhosts file') 132 | else: 133 | check_results['high'].append('Using rhosts file') 134 | 135 | # Checking if X11 Forward is allowed (shouldn't) 136 | if 'X11Forwarding' in lines: 137 | if lines['X11Forwarding'] == 'yes': 138 | check_results['low'].append('X11 forward is allowed') 139 | else: 140 | check_results['ok'].append('X11 forward is not allowed') 141 | else: 142 | check_results['ok'].append('X11 forward is not allowed') 143 | 144 | # Checking if SSH allow TCP Forward (shouldn't) 145 | if 'AllowTcpForwarding' in lines: 146 | if lines['AllowTcpForwarding'] == 'yes': 147 | check_results['low'].append('TCP forwarding is allowed') 148 | else: 149 | check_results['ok'].append('TCP forwarding is not allowed') 150 | else: 151 | check_results['low'].append('TCP forwarding is allowed') 152 | 153 | return check_results 154 | -------------------------------------------------------------------------------- /HnTool/output/html.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool - output module - html 4 | # Copyright (C) 2009-2010 Authors 5 | # Authors: 6 | # * Hugo Doria 7 | # * Aurelio A. Heckert 8 | # 9 | # This program is free software; you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation; either version 2 of the License, or 12 | # ( at your option ) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | 23 | import HnTool.modules 24 | import string 25 | 26 | 27 | class Format: 28 | 29 | description = "HTML output for a web browser" 30 | 31 | def __init__(self, options): 32 | pass 33 | 34 | def format_status(self, token): 35 | if token == 'ok': 36 | return 'OK' 37 | elif token == 'low': 38 | return 'LOW' 39 | elif token == 'medium': 40 | return 'MEDIUM' 41 | elif token == 'high': 42 | return 'HIGH' 43 | elif token == 'info': 44 | return 'INFO' 45 | 46 | # Method to show the check results 47 | def msg_status(self, msg, status): 48 | ''' 49 | Method to show the check results 50 | ''' 51 | return '' + \ 52 | self.format_status(status) + \ 53 | '' + msg + '' + \ 54 | '' 55 | 56 | def statistics_graphic(self, statistics): 57 | import matplotlib.pyplot as Matplot 58 | import base64 59 | import os # para remover o grafico gerado 60 | # Matplot.title('types of results') 61 | # Matplot.ylabel('occurrences') 62 | Matplot.grid(True) 63 | Matplot.rcParams.update({'font.size': 18}) 64 | Matplot.rcParams.update({'font.weight': 'bold'}) 65 | 66 | bar_width = 0.6 67 | Matplot.bar(1, statistics['ok'], width=bar_width, facecolor='lightgreen', align='center') 68 | Matplot.bar(2, statistics['high'], width=bar_width, facecolor='red', align='center') 69 | Matplot.bar(3, statistics['medium'], width=bar_width, facecolor='yellow', align='center') 70 | Matplot.bar(4, statistics['low'], width=bar_width, facecolor='lightgray', align='center') 71 | Matplot.bar(5, statistics['info'], width=bar_width, facecolor='lightblue', align='center') 72 | 73 | Matplot.xticks([1, 2, 3, 4, 5], ['OK', 'HIGH', 'MEDIUM', 'LOW', 'INFO']) 74 | graphic_name = 'statistics.png' 75 | Matplot.savefig(graphic_name) 76 | 77 | width = 270 78 | height = 200 79 | image_file = open(graphic_name, 'r') 80 | img_base64 = base64.b64encode(image_file.read()) 81 | image_file.close() 82 | os.remove(graphic_name) 83 | 84 | # imagem redimensionada no html para preservar a qualidade 85 | img_tag = 'statistics graphic'.format(img_base64, width, height) 86 | #img_tag = 'statistics graphic'.format(graphic_name, width, height) 87 | return img_tag 88 | 89 | def output(self, report, conf): 90 | self.conf = conf 91 | # Print all the results, from the 5 types of messages ( ok, low, medium, high and info ). 92 | # First message is the "ok" one ( m['results'][0] ). The second one is 93 | # "low" ( m['results'][1] ). The third ( m['results'][2] ) is for "warnings" 94 | # and the fourth one is "high" ( m['results'][3] ), The last one is for 95 | # info messages. 96 | print '''\n 97 | 98 | HnTool - A hardening tool for *nixes - Report 99 | 100 | 101 | 186 | 187 | 188 | 189 | 190 |
191 | 194 | 195 |
196 | ''' 197 | 198 | statistics = {'ok': 0, 'high': 0, 'medium': 0, 'low': 0, 'info': 0} 199 | for m in report: 200 | print '' 201 | if m['results']['ok'] != []: 202 | for result in m['results']['ok']: 203 | print self.msg_status(result, 'ok') 204 | statistics['ok'] += 1 205 | if m['results']['low'] != []: 206 | for result in m['results']['low']: 207 | print self.msg_status(result, 'low') 208 | statistics['low'] += 1 209 | if m['results']['medium'] != []: 210 | for result in m['results']['medium']: 211 | print self.msg_status(result, 'medium') 212 | statistics['medium'] += 1 213 | if m['results']['high'] != []: 214 | for result in m['results']['high']: 215 | print self.msg_status(result, 'high') 216 | statistics['high'] += 1 217 | if m['results']['info'] != []: 218 | for result in m['results']['info']: 219 | print self.msg_status(result, 'info') 220 | statistics['info'] += 1 221 | 222 | print ''' 223 |

' + m['title'] + '

224 |
225 | 226 | 241 |
242 | 243 | ''' 244 | -------------------------------------------------------------------------------- /HnTool/core.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # HnTool - A hardening tool for Linux/BSD 5 | # Copyright (C) 2009-2010 Authors 6 | # Authors: 7 | # * Hugo Doria 8 | # * Aurelio A. Heckert 9 | # 10 | # This program is free software; you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation; either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program; if not, write to the Free Software 22 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 23 | # 24 | # 25 | 26 | import sys 27 | 28 | import string 29 | from optparse import OptionParser 30 | from optparse import OptionGroup 31 | 32 | import gettext 33 | gettext.install('HnTool') 34 | 35 | import HnTool.modules.util 36 | import HnTool.modules 37 | import HnTool.output 38 | from HnTool import __version__ 39 | 40 | class HnToolCore: 41 | 42 | def __init__(self): 43 | self.rule_modules = {} 44 | self.output_modules = {} 45 | self.report = [] 46 | self.options = None 47 | 48 | self.opt_parser = OptionParser( 49 | usage = _("Usage: %prog [options]"), 50 | version="%prog " + str(__version__)) 51 | 52 | self.output_options = OptionGroup(self.opt_parser, _("Output Options")) 53 | self.rule_options = OptionGroup(self.opt_parser, _("Rule Options")) 54 | 55 | # Method to list all rules available 56 | def list_rules(self, *args): 57 | '''Method to list all rules available''' 58 | 59 | print '-' * 31 + _(' HnTool rule list ') + '-' * 31 60 | print 61 | 62 | for module in sorted(self.rule_modules): 63 | print string.ljust(self.rule_modules[module].short_name, 16) +\ 64 | ': ' + self.rule_modules[module].long_name 65 | 66 | sys.exit(0) 67 | 68 | # Method to list all output formats available 69 | def list_output_formats(self, *args): 70 | '''Method to list all output formats available''' 71 | 72 | print '-' * 30 + _(' HnTool output list ') + '-' * 30 73 | 74 | for module in self.output_modules: 75 | print string.ljust(module, 11) + ': ' \ 76 | + self.output_modules[module].description 77 | 78 | sys.exit(0) 79 | 80 | # Loading all rule modules 81 | def load_modules(self): 82 | '''Method to load all rule modules''' 83 | 84 | for module in sorted(HnTool.modules.__all__): 85 | self.rule_modules[module] = \ 86 | __import__('HnTool.modules.' + module, globals(), \ 87 | locals(), [HnTool]).Rule(self.rule_options) 88 | 89 | # Loading all output modules 90 | def load_output_modules(self): 91 | '''Method to load all output modules''' 92 | 93 | for module in HnTool.output.__formats__: 94 | self.output_modules[module] = \ 95 | __import__('HnTool.output.' + module, globals(), \ 96 | locals(), [HnTool]).Format(self.output_options) 97 | 98 | # Parsing all the configuration options 99 | def config_option_parser(self): 100 | '''Method to parse all the configuration options''' 101 | 102 | # -l/--list option: list all available rules 103 | self.opt_parser.add_option("-l", "--list", 104 | action="callback", 105 | callback=self.list_rules, 106 | help=_("returns a list of available rules")) 107 | 108 | # -t/--output_type: select the way that the report will 109 | # be generate (html or terminal, for now) 110 | self.opt_parser.add_option("-t", "--output_type", 111 | action="store", 112 | dest="output_format", default="terminal", 113 | help=_("select the output format")) 114 | 115 | # --list_output_type: list all available output formats 116 | self.opt_parser.add_option("--list_output_type", 117 | action="callback", 118 | callback=self.list_output_formats, 119 | help=_("list the avaliable output formats")) 120 | 121 | # -m/--modules: run specific modules 122 | self.opt_parser.add_option("-m","--modules", 123 | type="string", 124 | action="store", 125 | dest="modules_list", 126 | help=_("run only the tests specified by MODULES_LIST.")) 127 | 128 | # -e/--exclude: don't run specific modules 129 | self.opt_parser.add_option("-e","--exclude", 130 | type="string", 131 | action="store", 132 | dest="exclude_list", 133 | help=_("don't run the tests specified by MODULES_LIST.")) 134 | 135 | # adding the rules to they respective groups (rules or output) 136 | self.opt_parser.add_option_group(self.output_options) 137 | self.opt_parser.add_option_group(self.rule_options) 138 | 139 | self.options, args = self.opt_parser.parse_args(sys.argv[1:]) 140 | 141 | #TODO: define one error code for each error to allow automatic interactions. 142 | 143 | # Checking if all requirements are met 144 | def initial_check(self): 145 | '''Method to check if all HnTool's requirements are met''' 146 | 147 | # yes, only unix for now 148 | if not HnTool.modules.util.is_unix(): 149 | print >> sys.stderr, \ 150 | _('Error: You must have a Unix(-like) box. (No candy for you)') 151 | 152 | sys.exit(2) 153 | 154 | # Main initialization 155 | def init_core(self): 156 | '''Method to run the initial checks and load all modules, 157 | output modules and configuration options''' 158 | 159 | # status message must not go to stdout to not mess with the output 160 | #format. 161 | self.initial_check() # checking HnTool's requirements 162 | self.load_modules() # loading all the modules 163 | self.load_output_modules() # loading all the output modules 164 | self.config_option_parser() # getting all the options 165 | 166 | # This is where we run all the tests 167 | def run_tests(self): 168 | '''Method to run all tests available on HnTool''' 169 | 170 | self.init_core() # main initialization 171 | 172 | # checking if we are root. we need to be. oh yeah, baby. 173 | if not HnTool.modules.util.is_root(): 174 | print >> sys.stderr, _('Error: You must be root to run HnTool') 175 | print >> sys.stderr, '' 176 | print >> sys.stderr, self.opt_parser.print_help() 177 | 178 | sys.exit(2) 179 | 180 | # starting the checks/output 181 | print >> sys.stderr, _('[ Starting HnTool checks... ]') 182 | 183 | #if we used the -e or --exclude option 184 | if self.options.exclude_list: 185 | # getting a liste with the difference between the list 186 | # with all modules and the list passed by the -e option 187 | modules_list = list(set(HnTool.modules.__all__) - 188 | set(self.options.exclude_list.split(','))) 189 | 190 | elif self.options.modules_list: # if we just want to run specific modules 191 | modules_list = self.options.modules_list.split(',') 192 | 193 | else: # gets all the modules 194 | modules_list = HnTool.modules.__all__ 195 | 196 | # Run all the modules and its checks. 197 | # The results of each module goes to "report" 198 | for m in modules_list: 199 | 200 | if m in HnTool.modules.__all__: 201 | 202 | # if the module requires something 203 | if self.rule_modules[m].requires() != None: 204 | 205 | # if all the requiremets are met 206 | if HnTool.modules.util.requirements_met(self.rule_modules[m].requires()): 207 | self.report.append({'title': self.rule_modules[m].long_name, \ 208 | 'results': self.rule_modules[m].analyze(self.options)}) 209 | 210 | else: # if the requirements aren't met 211 | tmp_check_results = {'ok': [], 'low': [], 'medium': [], 212 | 'high': [], 'info': []} 213 | tmp_check_results['info'].append('One or more files required to run ' + 214 | 'this module could not be found') 215 | self.report.append({'title': self.rule_modules[m].long_name, \ 216 | 'results': tmp_check_results}) 217 | 218 | # if the module does NOT requires something 219 | else: 220 | self.report.append({'title': self.rule_modules[m].long_name, \ 221 | 'results': self.rule_modules[m].analyze(self.options)}) 222 | 223 | else: 224 | print >> sys.stderr, _('Invalid module ') + m 225 | sys.exit(2) 226 | 227 | # Give the report to the user 228 | self.output_modules[self.options.output_format].output( 229 | self.report, 230 | self.options 231 | ) 232 | 233 | def main(): 234 | hn = HnToolCore() 235 | hn.run_tests() 236 | 237 | if __name__ == "__main__": 238 | hn = HnToolCore() 239 | hn.run_tests() 240 | -------------------------------------------------------------------------------- /HnTool/modules/apache.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # HnTool rules - apache 4 | # Copyright (C) 2009-2010 Rafael Gomes 5 | # 2010 Elton Pereira 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to the Free Software 18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # 20 | 21 | # To do : Include code to check when sintax that there isn't in conf 22 | 23 | import os 24 | import commands 25 | import stat 26 | from HnTool.modules.rule import Rule as MasterRule 27 | 28 | class Rule(MasterRule): 29 | def __init__(self, options): 30 | MasterRule.__init__(self, options) 31 | self.short_name="apache" 32 | self.long_name="Checks security problems on Apache config file" 33 | self.type="config" 34 | self.required_files = ['/etc/httpd/conf/httpd.conf', 35 | '/etc/apache2/conf.d/security', 36 | '/etc/apache2/apache2.conf'] 37 | 38 | options.add_option( 39 | '--apache_conf', 40 | action='append', 41 | dest='apache_conf', 42 | help='adds a apache configuration file to the list of files to' + 43 | ' analize' 44 | ) 45 | 46 | def requires(self): 47 | return self.required_files 48 | 49 | def analyze(self, options): 50 | """ Analyze Apache config file searching for harmful settings""" 51 | 52 | check_results = self.check_results 53 | apache_conf_files = self.required_files 54 | 55 | if options.apache_conf: 56 | for f in options.apache_conf: 57 | apache_conf_files.append(f) 58 | 59 | apache_conf_file_found = False 60 | for apache_conf in apache_conf_files: 61 | if os.path.isfile(apache_conf): 62 | apache_conf_file_found = True 63 | fp = None 64 | 65 | try: 66 | fp = open(apache_conf, 'r') 67 | except IOError, (errno, strerror): 68 | check_results['info'].append( 69 | 'Could not open %s: %s' % (apache_conf, strerror) 70 | ) 71 | continue 72 | 73 | lines = [x.strip('\n') for x in fp.readlines()] 74 | fp.close() 75 | 76 | # Checking if ServerTokens is using harmful conf 77 | if not 'ServerTokens Minimal' in lines: 78 | check_results['ok'].append( 79 | 'ServerTokens is not using harmful conf' 80 | ) 81 | else: 82 | check_results['medium'].append( 83 | 'ServerTokens is using harmful conf (set Minimal)' 84 | ) 85 | 86 | # Checking if KeepAlive is set to On 87 | if 'KeepAlive On' in lines: 88 | check_results['ok'].append( 89 | 'KeepAlive is not using harmful conf' 90 | ) 91 | else: 92 | check_results['medium'].append( 93 | 'KeepAlive is using harmful conf (set On)' 94 | ) 95 | 96 | # Checking if ServerSignature is set to On 97 | if 'ServerSignature Off' in lines: 98 | check_results['ok'].append( 99 | 'ServerSignature is not using harmful conf' 100 | ) 101 | else: 102 | check_results['medium'].append( 103 | 'ServerSignature is using harmful conf (set Off)' 104 | ) 105 | 106 | # Checking if LimitRequestBody is bigger than 0 107 | if 'LimitRequestBody' in lines: 108 | for line in lines: 109 | if line.startswith('LimitRequestBody') is True: 110 | piece = line.split(' ') 111 | if int(piece[1]) == 0: 112 | check_results['ok'].append( 113 | 'LimitRequestBody is not using harmful' + 114 | ' value (0)' 115 | ) 116 | else: 117 | check_results['medium'].append( 118 | 'LimitRequestBody is using harmful value' + 119 | ' (0)' 120 | ) 121 | else: 122 | check_results['ok'].append( 123 | 'LimitRequestBody is not using harmful value (0)' 124 | ) 125 | 126 | # Checking if LimitRequestFields is bigger than 0 127 | if 'LimitRequestFields' in lines: 128 | for line in lines: 129 | if line.startswith('LimitRequestFields') is True: 130 | piece = line.split(' ') 131 | if int(piece[1]) == 0: 132 | check_results['ok'].append( 133 | 'LimitRequestFields is not using harmful' + 134 | ' value (0)' 135 | ) 136 | else: 137 | check_results['medium'].append( 138 | 'LimitRequestFields is using harmful' + 139 | ' value (0)' 140 | ) 141 | else: 142 | check_results['ok'].append( 143 | 'LimitRequestFields is not using harmful value (0)' 144 | ) 145 | 146 | # Checking if LimitRequestFieldsize is equal 8190 147 | if 'LimitRequestFieldsize' in lines: 148 | for line in lines: 149 | if line.startswith('LimitRequestFieldsize') is True: 150 | piece = line.split(' ') 151 | if int(piece[1]) == 0: 152 | check_results['ok'].append( 153 | 'LimitRequestFieldsize is using good' + 154 | ' value (8190)' 155 | ) 156 | else: 157 | check_results['low'].append( 158 | 'LimitRequestFieldsize is not using good' + 159 | ' value (8190)' 160 | ) 161 | else: 162 | check_results['ok'].append( 163 | 'LimitRequestFieldsize is using good value (8190)' 164 | ) 165 | 166 | # Checking if LimitRequestLine is equal 8190 167 | if 'LimitRequestLine' in lines: 168 | for line in lines: 169 | if line.startswith('LimitRequestLine') is True: 170 | piece = line.split(' ') 171 | if int(piece[1]) == 0: 172 | check_results['ok'].append( 173 | 'LimitRequestLine is using good value' + 174 | ' (8190)' 175 | ) 176 | else: 177 | check_results['low'].append( 178 | 'LimitRequestLine is not using good' + 179 | ' value (8190)' 180 | ) 181 | else: 182 | check_results['ok'].append( 183 | 'LimitRequestLine is using good value (8190)' 184 | ) 185 | 186 | # Checking Timeout less than 300 187 | tvalue = 300 188 | for line in lines: 189 | if line.startswith('Timeout') is True: 190 | piece = line.split(' ') 191 | if int(piece[1]) <= tvalue: 192 | check_results['ok'].append( 193 | 'Timeout is not using harmful value (>=%s)' 194 | % (tvalue) 195 | ) 196 | else: 197 | check_results['medium'].append( 198 | 'Timeout is using harmful value (>=%s)' 199 | % (tvalue) 200 | ) 201 | 202 | # Checking if access to Apache manual is enabled 203 | for line in lines: 204 | if line.startswith('Alias /manual/') is True: 205 | piece = line.split(' ') 206 | if (piece[1]) == '/manual/': 207 | check_results['medium'].append( 208 | 'Access to Apache manual is enabled' 209 | ) 210 | else: 211 | check_results['ok'].append( 212 | 'Access to Apache manual is disabled' 213 | ) 214 | 215 | # Checking .htpasswd files permission 216 | mode = "550" 217 | mode = int(mode, 8) 218 | locate_status, locate_returns = \ 219 | commands.getstatusoutput('locate .htpasswd') 220 | 221 | if os.path.exists(locate_returns): 222 | if locate_status == 0: 223 | for locate_return in locate_returns.split('\n'): 224 | if stat.S_IMODE(os.stat(locate_return).st_mode) == mode: 225 | check_results['ok'].append( 226 | 'The file %s is not using harmful permission (550)' 227 | % (locate_return) 228 | ) 229 | else: 230 | check_results['medium'].append(\ 231 | 'The file %s is using harmful permission (550)' 232 | % (locate_return) 233 | ) 234 | else: 235 | check_results['info'].append( 236 | 'Could not find a .htpasswd file. Please, run updatedb' 237 | ) 238 | # If there is, closing the apache_config file 239 | if not apache_conf_file_found: 240 | check_results['info'].append( 241 | 'Could not find Apache\'s configuration files' 242 | ) 243 | 244 | return check_results -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------