diff --git a/.gitignore b/.gitignore index c440fc7..54c0124 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.pyc nmaper/static/results/ .DS_Store +.env diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e04cd2..039af3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ #CHANGELOG +* [10/06/2019] + * Compatible with Python 3 (Tested on Python 3.7.4) + * Move out the configuration from source code to environment file (use the [python-dotenv](https://github.com/theskumar/python-dotenv) library) + * Add more variables for application + * Add the setup.sh for simplifying the setup [GH#3] * [09/29/2016] Fixes bug with nmaper-cronjob.py not finding Nmap. [GH#8] * [09/17/2016] Added checkboxes to enable/disable DNS resolution and host discovery. [GH#2] + diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index babe91c..0000000 --- a/INSTALL.md +++ /dev/null @@ -1,34 +0,0 @@ -#INSTALLATION -#Prerequisites -- Django (Tested on Django 1.9) -If you do not have Django installed use: `pip install Django` -- lxml -Install it with `pip install lxml` - -##Linux/OSX -- Download the code from this repository. -``` -git clone https://github.com/cldrn/rainmap-lite -``` -- Update BASE_URL, SMTP_USER, SMTP_PASS, SMTP_SERVER and SMTP_PORT in nmaper-cronjob.py with your base URL and SMTP credentials to receive email alerts. -- Create the database schema -``` -python manage.py migrate -``` -- Load the default scanning profiles data -``` -python manage.py loaddata nmapprofiles -``` -- Add a cron task to execute nmaper-cronjob.py periodically. For example: -``` -*/5 * * * * cd && /usr/bin/python nmaper-cronjob.py >> /var/log/nmaper.log 2>&1 -``` -- Run the app (Or install it): -``` -python manage.py runserver 0.0.0.0:8080 -``` -##Adding the first admin user -For security RainmapLite does not have any default administrative user out of box. You need to create one by running the following command: -``` -python manage.py createsuperuser -``` diff --git a/README.md b/README.md index 442748d..473c788 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Unlike it's predecessor [1], Rainmap-lite does not require special services (Rab * Dozens of scanning profiles to choose from. * Easy to install/set up. * Share results with your team. +* Compatible with Python 3 (Tested on Python 3.7.4) This project is still in beta version. Any feedback, bug reports and PRs are greatly appreciated! @@ -23,7 +24,38 @@ https://youtu.be/3oNegHPBd3o ## Documentation You can find all the documentation related to this project on the [Wiki](https://github.com/cldrn/rainmap-lite/wiki/ "Rainmap Lite Documentation") +## Installation + +1. Make sure the **python3**, **pip3**, **nmap**, **nmap-scripts** and **cron** installed on system +2. git clone this project +3. Rename/Copy the .env.sample to .env +4. Prepare the environment file (.env) +5. sh setup.sh + +## Environment variables (.env.sample file) + +### Configuration for setup.sh + +- APP_ROOT_PATH="/opt/rainmap-lite/" +- HTTP_PORT="8000" +- LOG_PATH="/var/log/nmaper.log" +- ADMIN_USER="admin" +- ADMIN_PASS="admin" +- ADMIN_EMAIL="user@domain.org" + +### Config for namper-cronjob.py + +#### Please follow the format that described in [python-dotenv](https://github.com/theskumar/python-dotenv) + +- BASE_URL="[http://127.0.0.1:${HTTP_PORT}](http://127.0.0.1:${HTTP_PORT}/)" +- SMTP_USER="SMTP_USER" +- SMTP_PASS="SMTP_PASS" +- SMTP_SERVER="SMTP_SERVER" +- SMTP_PORT="SMTP_PORT" +- SMTP_DOMAIN_NAME="SMTP_DOMAIN_NAME" + ## Screenshots + * Responsive interface * Customizable diff --git a/rainmap-lite/.env.sample b/rainmap-lite/.env.sample new file mode 100644 index 0000000..5a30534 --- /dev/null +++ b/rainmap-lite/.env.sample @@ -0,0 +1,15 @@ +# Config for setup.sh +APP_ROOT_PATH="/opt/rainmap-lite/" +HTTP_PORT="8000" +LOG_PATH="/var/log/nmaper.log" +ADMIN_USER="admin" +ADMIN_PASS="admin" +ADMIN_EMAIL="freebooters@gmail.com" + +# Config for namper-cronjob.py +BASE_URL="http://127.0.0.1:${HTTP_PORT}" +SMTP_USER="SMTP_USER" +SMTP_PASS="SMTP_PASS" +SMTP_SERVER="SMTP_SERVER" +SMTP_PORT="SMTP_PORT" +SMTP_DOMAIN_NAME="SMTP_DOMAIN_NAME" diff --git a/rainmap-lite/NmapOptions.py b/rainmap-lite/NmapOptions.py index 73d484f..7a711b1 100644 --- a/rainmap-lite/NmapOptions.py +++ b/rainmap-lite/NmapOptions.py @@ -546,7 +546,7 @@ def setdefault(self, key, default): return self.d.setdefault(self.canonicalize_name(key), default) def handle_result(self, result): - if isinstance(result, basestring): + if isinstance(result, str): # A positional argument. self.target_specs.append(result) return @@ -768,7 +768,7 @@ def render(self): opt_list.append("-T%s" % str(self["-T"])) if self["-O"] is not None: - if isinstance(self["-O"], basestring): + if isinstance(self["-O"], str): opt_list.append("-O%s" % self["-O"]) elif self["-O"]: opt_list.append("-O") @@ -818,7 +818,7 @@ def render(self): if self[ping_option] is not None: opt_list.append(ping_option + self[ping_option]) if self["-PB"] is not None: - if isinstance(self["-PB"], basestring): + if isinstance(self["-PB"], str): opt_list.append("-PB" + self["-PB"]) elif self["-PB"]: opt_list.append("-PB") diff --git a/rainmap-lite/manage.py b/rainmap-lite/manage.py index 4f3e9af..0033a80 100755 --- a/rainmap-lite/manage.py +++ b/rainmap-lite/manage.py @@ -2,6 +2,9 @@ import os import sys +from dotenv import load_dotenv +load_dotenv() + if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "scandere.settings") diff --git a/rainmap-lite/nmaper-cronjob.py b/rainmap-lite/nmaper-cronjob.py index b8facd6..c23f656 100644 --- a/rainmap-lite/nmaper-cronjob.py +++ b/rainmap-lite/nmaper-cronjob.py @@ -1,5 +1,5 @@ -#!/usr/bin/python -#Cronjob script that executes Nmap scans on the background +#!/usr/bin/python3 +# Cronjob script that executes Nmap scans on the background import sqlite3 import os import subprocess @@ -9,37 +9,61 @@ import lxml.etree as ET import distutils.spawn -from email.MIMEMultipart import MIMEMultipart -from email.MIMEText import MIMEText +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart from NmapOptions import NmapOptions -# -BASE_URL = "http://127.0.0.1:8000" -SMTP_USER = "youremail@gmail.com" -SMTP_PASS = "yourpassword" -SMTP_SERVER = "smtp.gmail.com" -SMTP_PORT = 587 -# +from dotenv import load_dotenv +load_dotenv() + +BASE_URL = os.getenv('BASE_URL') +SMTP_USER = os.getenv('SMTP_USER') +SMTP_PASS = os.getenv('SMTP_PASS') +SMTP_SERVER = os.getenv('SMTP_SERVER') +SMTP_PORT = os.getenv('SMTP_PORT') +SMTP_DOMAIN_NAME = os.getenv('SMTP_DOMAIN_NAME') + +OUTPUT_PATH = os.path.normpath( + "%s/nmaper/static/results" % os.getcwd() +).replace("\\", "/") -OUTPUT_PATH = os.path.normpath("%s/nmaper/static/results" % os.getcwd()).replace("\\", "/") def find_nmap(): if os.name == "nt": - nmap_path = distutils.spawn.find_executable("nmap.exe", os.environ["PROGRAMFILES(X86)"]+"\Nmap") + nmap_path = distutils.spawn.find_executable( + "nmap.exe", f"{os.environ['PROGRAMFILES(X86)']}\\Nmap" + ) + if not(nmap_path): - nmap_path = distutils.spawn.find_executable("nmap.exe", os.environ["PROGRAMFILES"]+"\Nmap") + nmap_path = distutils.spawn.find_executable( + "nmap.exe", f"{os.environ['PROGRAMFILES']}\\Nmap" + ) else: - nmap_path = distutils.spawn.find_executable("nmap","/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin") + nmap_path = distutils.spawn.find_executable( + "nmap", "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + ) return nmap_path + def notify(id_, email, cmd): - print('[%s] Sending report %s to %s' % (datetime.datetime.now(), id_, email)) + print( + '[%s] Sending report %s to %s' % (datetime.datetime.now(), id_, email) + ) + msg = MIMEMultipart() - msg['From'] = SMTP_USER + email_from_address = f'{SMTP_USER}@{SMTP_DOMAIN_NAME}' + msg['From'] = email_from_address msg['To'] = email msg['Subject'] = "Your scan results are ready" - body = "{2}\n\nView online:\n{0}/static/results/{1}.html\n\nDownload:\n{0}/static/results/{1}.nmap\n{0}/static/results/{1}.xml\n{0}/static/results/{1}.gnmap".format(BASE_URL, id_, cmd) + body = ( + "{2}\n\n" + "View online:\n{0}/static/results/{1}.html\n\n" + "Download:\n" + "{0}/static/results/{1}.nmap\n" + "{0}/static/results/{1}.xml\n" + "{0}/static/results/{1}.gnmap" + ).format(BASE_URL, id_, cmd) msg.attach(MIMEText(body, 'plain')) server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) server.ehlo() @@ -47,51 +71,84 @@ def notify(id_, email, cmd): server.ehlo() server.login(SMTP_USER, SMTP_PASS) text = msg.as_string() - server.sendmail(SMTP_USER, email, text) + server.sendmail(email_from_address, email, text) + def update_status(id_, status, cursor, db): - cursor.execute('''UPDATE nmaper_nmapscan SET status_text = ? WHERE id = ? ''', (status, id_)) + cursor.execute( + '''UPDATE nmaper_nmapscan SET status_text = ? WHERE id = ? ''', + (status, id_) + ) db.commit() - print("[%s] Job #%s status changed to '%s'" % (datetime.datetime.now(), id_, status)) + print( + "[%s] Job #%s status changed to '%s'" % ( + datetime.datetime.now(), id_, status + ) + ) + def set_random_id(id_, cursor, db): rid = uuid.uuid4() - cursor.execute('''UPDATE nmaper_nmapscan SET uuid = ? WHERE id = ? ''', (rid.hex, id_)) + cursor.execute( + '''UPDATE nmaper_nmapscan SET uuid = ? WHERE id = ? ''', + (rid.hex, id_) + ) db.commit() return rid.hex + def set_endtime(id_, cursor, db): - cursor.execute('''UPDATE nmaper_nmapscan SET end_date = ? WHERE id = ? ''', (datetime.datetime.now(), id_)) + cursor.execute( + '''UPDATE nmaper_nmapscan SET end_date = ? WHERE id = ? ''', + (datetime.datetime.now(), id_) + ) db.commit() + def execute(path, cmd, uuid): - filename = "%s/%s" % (OUTPUT_PATH, uuid) + filename = "%s/%s" % (OUTPUT_PATH, uuid) nmap_cmd = '%s %s -oA %s' % (path, cmd, filename) ops = NmapOptions() ops.parse_string(nmap_cmd) proc = subprocess.Popen(ops.render(), shell=False) proc.wait() - print('\n[%s] Finished execution of command "%s"' % (datetime.datetime.now(), cmd)) + print( + '\n[%s] Finished execution of command "%s"' % ( + datetime.datetime.now(), cmd + ) + ) dom = ET.parse("%s.xml" % filename) - xsl_filename = dom.getroot().getprevious().getprevious().parseXSL() # need to add error checking + # need to add error checking + xsl_filename = dom.getroot().getprevious().getprevious().parseXSL() transform = ET.XSLT(xsl_filename) html = transform(dom) - html_file = open('%s.html' % filename, 'w') + html_file = open('%s.html' % filename, 'wb') html.write(html_file) - print('[%s] HTML report generated (%s.html)' % (datetime.datetime.now(), filename)) + print( + '[%s] HTML report generated (%s.html)' % ( + datetime.datetime.now(), filename + ) + ) + def main(): path = find_nmap() + if not path: - print("[%s] Could not find path for nmap. Quitting!" % datetime.datetime.now()) + print( + "[%s] Could not find path for nmap. Quitting!" % + datetime.datetime.now() + ) exit() db = sqlite3.connect('scandere.sqlite3') cursor = db.cursor() - cursor.execute('''SELECT * FROM nmaper_nmapscan WHERE status_text="waiting"''') + cursor.execute( + r'SELECT * FROM nmaper_nmapscan WHERE status_text="waiting"' + ) all_rows = cursor.fetchall() print('[%s] Listing pending nmap scans...' % datetime.datetime.now()) @@ -105,8 +162,15 @@ def main(): execute(path, cmd, rid) update_status(jid, "finished", cursor, db) set_endtime(jid, cursor, db) - print("[%s] Job #%d finished. Notifying '%s'" % (datetime.datetime.now(), jid, email)) + print( + "[%s] Job #%d finished. Notifying '%s'" % ( + datetime.datetime.now(), + jid, + email + ) + ) notify(rid, email, cmd) + if __name__ == "__main__": main() diff --git a/rainmap-lite/requirement.txt b/rainmap-lite/requirement.txt new file mode 100644 index 0000000..bb515f6 --- /dev/null +++ b/rainmap-lite/requirement.txt @@ -0,0 +1,4 @@ +Django==1.11.25 +lxml==4.4.1 +python-dotenv==0.10.3 +pytz==2019.2 diff --git a/rainmap-lite/scandere/settings.py b/rainmap-lite/scandere/settings.py index 6d7abfa..c9df08f 100644 --- a/rainmap-lite/scandere/settings.py +++ b/rainmap-lite/scandere/settings.py @@ -25,7 +25,10 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True LOGIN_REDIRECT_URL = '/' -ALLOWED_HOSTS = [] + +ALLOWED_HOSTS = [ + os.getenv('BASE_URL').split(':')[1].replace('/', '') +] # Application definition diff --git a/rainmap-lite/setup.sh b/rainmap-lite/setup.sh new file mode 100755 index 0000000..7a3deca --- /dev/null +++ b/rainmap-lite/setup.sh @@ -0,0 +1,33 @@ +APP_ROOT_PATH=$(grep 'APP_ROOT_PATH' .env | cut -d '=' -f2 | tr -d '"') +LOG_PATH=$(grep 'LOG_PATH' .env | cut -d '=' -f2 | tr -d '"') +ADMIN_USER=$(grep 'ADMIN_USER' .env | cut -d '=' -f2 | tr -d '"') +ADMIN_PASS=$(grep 'ADMIN_PASS' .env | cut -d '=' -f2 | tr -d '"') +ADMIN_EMAIL=$(grep 'ADMIN_EMAIL' .env | cut -d '=' -f2 | tr -d '"') +HTTP_PORT=$(grep 'HTTP_PORT=' .env | cut -d '=' -f2 | tr -d '"') +IS_SETUP=$(grep 'IS_SETUP' .env | cut -d '=' -f2 | tr -d '"') + +# Setup the environment on first run +if [ -z "$IS_SETUP" ] +then + # Install the required Python libs + pip install -r requirement.txt + + # Setup the application + python3 manage.py migrate + python3 manage.py loaddata nmapprofiles + + # Setup the crontab job + crontab -l > cronjob_list.txt + echo "*/5 * * * * cd $APP_ROOT_PATH && python3 nmaper-cronjob.py >> $LOG_PATH 2>&1" >> cronjob_list.txt + crontab cronjob_list.txt + rm cronjob_list.txt + + # Add the admin user + echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('$ADMIN_USER', '$ADMIN_EMAIL', '$ADMIN_PASS')" | python3 manage.py shell +fi + +# Write the config file for setup flag +echo "IS_SETUP=True" >> .env + +# Start up the HTTP server +python3 manage.py runserver 0.0.0.0:$HTTP_PORT