diff --git a/.gitignore b/.gitignore index ef9219d6..f9c03ecb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ cert.pem key.pem tmp/* templates/* +log/*.log attachments/* config.json plugins/* diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..efdd94b5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM ruby:2.3.5 +MAINTAINER Serpico + +ENV SRP_ROOT /Serpico +WORKDIR $SRP_ROOT +COPY . $SRP_ROOT + +RUN bundle install + +# Allow DB to be on a shared volume +VOLUME ["$SRP_ROOT/db"] +EXPOSE 8443 + +CMD ["bash", "scripts/docker.sh"] diff --git a/Gemfile b/Gemfile index efe04eea..5f9ffd9f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby "2.3.3" +ruby "2.3.5" gem 'sinatra' gem 'haml' diff --git a/config.json.defaults b/config.json.defaults index 7b47da11..46ca0783 100644 --- a/config.json.defaults +++ b/config.json.defaults @@ -29,6 +29,11 @@ "Logging and Auditing", "Imported" ], + "finding_states": [ + "Draft", + "Under Review", + "Completed" + ], "logo": "/img/logo_1.svg", "auto_import": false, "chart": true, @@ -47,4 +52,4 @@ "AES128-GCM-SHA256","AES256-GCM-SHA384","AES128-SHA256", "AES256-SHA256","AES128-SHA","AES256-SHA"], "languages":["English"] -} \ No newline at end of file +} diff --git a/config.rb b/config.rb new file mode 100644 index 00000000..8eb40334 --- /dev/null +++ b/config.rb @@ -0,0 +1,37 @@ +require 'json' + +class Config + private_class_method :new # Don't allow instantiation. + @config = nil + def self._get_config() + @defaults = JSON.parse(File.read('./config.json.defaults')) + @config = JSON.parse(File.read('./config.json')) + end + + def self._get(key) + Config._get_config() unless @config + return @config[key] if @config.key?(key) + return @defaults[key] # Fallback to default values + end + + def self.[](key) return Config._get(key) end + + def self.[]=(key, val) + Config._get_config() unless @config + @config[key] = val + end + + def self.key?(key) + Config._get_config() unless @config + return @config.key?(key) + end + + def self.each(&block) + @config.each(&block) + end + + def self.to_json(*a) + return @config.to_json + end +end + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..c30b7769 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3' +services: + serpico: + environment: + - SRP_ADMIN=administrator + - SRP_FINDINGS=yes + build: + dockerfile: docker/dev.dockerfile + context: . + ports: + - "8443:8443" + volumes: + - ./:/Serpico diff --git a/docker/dev.dockerfile b/docker/dev.dockerfile new file mode 100644 index 00000000..4d89d128 --- /dev/null +++ b/docker/dev.dockerfile @@ -0,0 +1,9 @@ +FROM ruby:2.3.5 +MAINTAINER Serpico +ENV SRP_ROOT /Serpico +WORKDIR $SRP_ROOT +# No volume: It will be mounted by docker-compose. +COPY Gemfile $SRP_ROOT/ +RUN bundle install +EXPOSE 8443 +CMD ["bash", "docker/docker.sh"] diff --git a/docker/docker.sh b/docker/docker.sh new file mode 100755 index 00000000..786f8313 --- /dev/null +++ b/docker/docker.sh @@ -0,0 +1,12 @@ +#!/usr/bin/bash +# This script is used as the entry point for the docker image. +# It will initialize the database if it isn't already present. + +if [ ! -f "$SRP_ROOT/db/master.db" ]; then + echo "First run detected. Initializing database..." + ruby "$SRP_ROOT/scripts/first_time.rb" +fi + +# CMD ["ruby", "serpico.rb"] +ruby serpico.rb + diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 00000000..bf9664f2 --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,65 @@ +# Running Inside Docker + +The included `Dockerfile` allows to run Serpico inside docker from any system +that supports containers. + +By default, Serpico listens on 8443, you can expose it as `443` if you would +like by using `docker run -p 443:8443 ...` + +The image needs to first be built. + +1. Build the image +2. Map the database location in docker-compose or at `docker run` time. +3. If the database doesn't exist, it will be created with defaults + +## Creating the image + +This will create a container with the current state of your repository. +The database is not created at this point, and this image is safe for +distribution. + +``` +docker build -t serpico . +``` + +The Dockerfile exposes a `VOLUME` at `/Serpico/db` to allow mounting an +external database through `docker-compose` or `docker run -v`. + + +## Running with `docker run` + +``` +# Create a new container called "serpico" and run it in the foreground +docker run --name serpico -p 8443:8443 -v"$(pwd)":/Serpico/db -it serpico + +# Stop the container when you no longer need it +docker stop serpico + +# Start it again when you need it. It will keep its state. +docker start serpico +``` + +This will store the database locally at `$PWD/master.db` Please note that the +path to the database on the host [must be absolute][1]. + +[1]: https://docs.docker.com/engine/reference/run/#volume-shared-filesystems + +## docker-compose + +The `docker-compose.yml` in the repository is aimed at development use. It will +provision the ruby environment inside the container and mount the repository as +the docker application, allowing for reloading the source code by simply +restarting the container. The dockerfile `docker/dev.dockerfile` is used by +compose. + +## Caveats + +This is a work in progress, so a few things are currently not supported. + +- Running a new container with an existing `master.db` will not work because + `first_time.rb` will not run, and there won't be any certificates for SSL. +- `config.json` is not exposed to the host so customization requires rebuilding + the image or accessing it with `docker exec bash`. +- `docker-compose up` will not automatically reload the backend when `.rb` + files are changed. This is a possible improvement. + diff --git a/model/master.rb b/model/master.rb index be875a31..2ce3174b 100644 --- a/model/master.rb +++ b/model/master.rb @@ -193,6 +193,7 @@ class Findings property :nist_rating, String, :required => false property :language, String, required: false + property :state, Integer, required: false end class TemplateReports diff --git a/routes/admin.rb b/routes/admin.rb index a2366458..44fa6e96 100644 --- a/routes/admin.rb +++ b/routes/admin.rb @@ -1,11 +1,10 @@ require 'sinatra' require 'zip' - -config_options = JSON.parse(File.read('./config.json')) +require './config' # set the report_assessment_types for <1.2 versions of Serpico -unless config_options['report_assessment_types'] - config_options['report_assessment_types'] = ['Network Internal', 'External', 'Web application', 'Physical', 'Social engineering', 'Configuration audit'] +unless Config['report_assessment_types'] + Config['report_assessment_types'] = ['Network Internal', 'External', 'Web application', 'Physical', 'Social engineering', 'Configuration audit'] end ###### @@ -183,16 +182,16 @@ get '/admin/config' do redirect to('/no_access') unless is_administrator? - @config = config_options - @scoring = if config_options['cvss'] + @config = Config + @scoring = if Config['cvss'] 'cvss' - elsif config_options['cvssv3'] + elsif Config['cvssv3'] 'cvssv3' - elsif config_options['dread'] + elsif Config['dread'] 'dread' - elsif config_options['riskmatrix'] + elsif Config['riskmatrix'] 'riskmatrix' - elsif config_options["nist800"] + elsif Config["nist800"] 'nist800' else 'default' @@ -209,68 +208,68 @@ rat = params['report_assessment_types'].split(',') lang = params['languages'].delete(' ').split(',') - config_options['effort'] = params['effort'].split(',') if params['effort'] - - config_options['finding_types'] = ft - config_options['user_defined_variables'] = udv - config_options['port'] = params['port'] - config_options['report_assessment_types'] = rat - config_options['languages'] = lang - config_options['use_ssl'] = params['use_ssl'] ? true : false - config_options['bind_address'] = params['bind_address'] - config_options['ldap'] = params['ldap'] ? true : false - config_options['ldap_domain'] = params['ldap_domain'] - config_options['ldap_dc'] = params['ldap_dc'] - config_options['burpmap'] = params['burpmap'] ? true : false - config_options['nessusmap'] = params['nessusmap'] ? true : false - config_options['vulnmap'] = params['vulnmap'] ? true : false - config_options['logo'] = params['logo'] - config_options['auto_import'] = params['auto_import'] ? true : false - config_options['chart'] = params['chart'] ? true : false - config_options['threshold'] = params['threshold'] - config_options['show_exceptions'] = params['show_exceptions'] ? true : false - config_options['cvssv2_scoring_override'] = params['cvssv2_scoring_override'] ? true : false + Config['effort'] = params['effort'].split(',') if params['effort'] + + Config['finding_types'] = ft + Config['user_defined_variables'] = udv + Config['port'] = params['port'] + Config['report_assessment_types'] = rat + Config['languages'] = lang + Config['use_ssl'] = params['use_ssl'] ? true : false + Config['bind_address'] = params['bind_address'] + Config['ldap'] = params['ldap'] ? true : false + Config['ldap_domain'] = params['ldap_domain'] + Config['ldap_dc'] = params['ldap_dc'] + Config['burpmap'] = params['burpmap'] ? true : false + Config['nessusmap'] = params['nessusmap'] ? true : false + Config['vulnmap'] = params['vulnmap'] ? true : false + Config['logo'] = params['logo'] + Config['auto_import'] = params['auto_import'] ? true : false + Config['chart'] = params['chart'] ? true : false + Config['threshold'] = params['threshold'] + Config['show_exceptions'] = params['show_exceptions'] ? true : false + Config['cvssv2_scoring_override'] = params['cvssv2_scoring_override'] ? true : false if params['risk_scoring'] == 'CVSSv2' - config_options['dread'] = false - config_options['cvss'] = true - config_options['cvssv3'] = false - config_options['riskmatrix'] = false - config_options['nist800'] = false + Config['dread'] = false + Config['cvss'] = true + Config['cvssv3'] = false + Config['riskmatrix'] = false + Config['nist800'] = false elsif params['risk_scoring'] == 'CVSSv3' - config_options['dread'] = false - config_options['cvss'] = false - config_options['cvssv3'] = true - config_options['riskmatrix'] = false - config_options['nist800'] = false + Config['dread'] = false + Config['cvss'] = false + Config['cvssv3'] = true + Config['riskmatrix'] = false + Config['nist800'] = false elsif params['risk_scoring'] == 'DREAD' - config_options['dread'] = true - config_options['cvss'] = false - config_options['cvssv3'] = false - config_options['riskmatrix'] = false - config_options['nist800'] = false + Config['dread'] = true + Config['cvss'] = false + Config['cvssv3'] = false + Config['riskmatrix'] = false + Config['nist800'] = false elsif params['risk_scoring'] == 'RISKMATRIX' - config_options['dread'] = false - config_options['cvss'] = false - config_options['cvssv3'] = false - config_options['riskmatrix'] = true - config_options['nist800'] = false + Config['dread'] = false + Config['cvss'] = false + Config['cvssv3'] = false + Config['riskmatrix'] = true + Config['nist800'] = false elsif params['risk_scoring'] == 'NIST800-30' - config_options['dread'] = false - config_options['cvss'] = false - config_options['cvssv3'] = false - config_options['riskmatrix'] = false - config_options['nist800'] = true + Config['dread'] = false + Config['cvss'] = false + Config['cvssv3'] = false + Config['riskmatrix'] = false + Config['nist800'] = true else - config_options['dread'] = false - config_options['cvss'] = false - config_options['cvssv3'] = false - config_options['riskmatrix'] = false - config_options['nist800'] = false + Config['dread'] = false + Config['cvss'] = false + Config['cvssv3'] = false + Config['riskmatrix'] = false + Config['nist800'] = false end File.open('./config.json', 'w') do |f| - f.write(JSON.pretty_generate(config_options)) + f.write(JSON.pretty_generate(Config)) serpico_log("Configuration file modified") end redirect to('/admin/config') diff --git a/routes/api.rb b/routes/api.rb index 1d87678a..a521592d 100644 --- a/routes/api.rb +++ b/routes/api.rb @@ -1,8 +1,8 @@ require 'sinatra' +require './config' ##### Simple API Components - Read-Only for now -config_options = JSON.parse(File.read('./config.json')) # returns an API session key post '/v1/session' do diff --git a/routes/basic.rb b/routes/basic.rb index fe535f73..71b89d6a 100644 --- a/routes/basic.rb +++ b/routes/basic.rb @@ -1,8 +1,7 @@ require 'sinatra' +require './config' ### Basic Routes -config_options = JSON.parse(File.read('./config.json')) - # Used for 404 responses not_found do "Sorry, I don't know this page." @@ -136,14 +135,14 @@ end elsif user - if config_options['ldap'].to_s == 'true' + if Config['ldap'].to_s == 'true' # try AD authentication usern = params[:username] data = url_escape_hash(request.POST) redirect to('/') if (usern == '') || (params[:password] == '') - user = "#{config_options['ldap_domain']}\\#{data['username']}" - ldap = Net::LDAP.new host: (config_options['ldap_dc']).to_s, port: 636, encryption: :simple_tls, auth: { method: :simple, username: user, password: params[:password] } + user = "#{Config['ldap_domain']}\\#{data['username']}" + ldap = Net::LDAP.new host: (Config['ldap_dc']).to_s, port: 636, encryption: :simple_tls, auth: { method: :simple, username: user, password: params[:password] } if ldap.bind # replace the session in the session table diff --git a/routes/mapping.rb b/routes/mapping.rb index bb6a9521..200acbec 100644 --- a/routes/mapping.rb +++ b/routes/mapping.rb @@ -1,6 +1,5 @@ require 'sinatra' - -config_options = JSON.parse(File.read('./config.json')) +require './config' # Delete a mapping from finding get '/mapping/:id/nessus/:mappingid/delete' do diff --git a/routes/master.rb b/routes/master.rb index bb72f965..c1f501c3 100644 --- a/routes/master.rb +++ b/routes/master.rb @@ -1,36 +1,36 @@ require 'zip' require 'sinatra' +require './config' ###### # Template Document Routes ###### -config_options = JSON.parse(File.read('./config.json')) # These are the master routes, they control the findings database # List Available Templated Findings get '/master/findings' do @findings = TemplateFindings.all(order: [:title.asc]) @master = true - @dread = config_options['dread'] - @cvss = config_options['cvss'] - @cvssv3 = config_options['cvssv3'] - @riskmatrix = config_options['riskmatrix'] - @nist800 = config_options['nist800'] + @dread = Config['dread'] + @cvss = Config['cvss'] + @cvssv3 = Config['cvssv3'] + @riskmatrix = Config['riskmatrix'] + @nist800 = Config['nist800'] haml :findings_list end # Create a new templated finding get '/master/findings/new' do @master = true - @languages = config_options['languages'] - @dread = config_options['dread'] - @cvss = config_options['cvss'] - @cvssv3 = config_options['cvssv3'] - @nist800 = config_options['nist800'] - @riskmatrix = config_options['riskmatrix'] - @nessusmap = config_options['nessusmap'] - @vulnmap = config_options['vulnmap'] + @languages = Config['languages'] + @dread = Config['dread'] + @cvss = Config['cvss'] + @cvssv3 = Config['cvssv3'] + @nist800 = Config['nist800'] + @riskmatrix = Config['riskmatrix'] + @nessusmap = Config['nessusmap'] + @vulnmap = Config['vulnmap'] haml :create_finding end @@ -39,11 +39,11 @@ post '/master/findings/new' do data = url_escape_hash(request.POST) - if config_options['dread'] + if Config['dread'] data['dread_total'] = data['damage'].to_i + data['reproducability'].to_i + data['exploitability'].to_i + data['affected_users'].to_i + data['discoverability'].to_i end - if config_options['riskmatrix'] + if Config['riskmatrix'] if data['severity'] == 'Low' severity_val = 0 elsif data['severity'] == 'Medium' @@ -64,7 +64,7 @@ end # Create NIST800 finding in the main database - if(config_options["nist800"]) + if(Config["nist800"]) # call nist800 helper function data = nist800(data) end @@ -86,21 +86,21 @@ @newfinding = TemplateFindings.first(title: data['title'], order: [:id.desc], limit: 1) # save mapping data - if config_options['nessusmap'] && nessusdata['pluginid'] + if Config['nessusmap'] && nessusdata['pluginid'] nessusdata['templatefindings_id'] = @finding.id @nessus = NessusMapping.new(nessusdata) @nessus.save end - if config_options['vulnmap'] && vulnmapdata['msf_ref'] + if Config['vulnmap'] && vulnmapdata['msf_ref'] vulnmapdata['templatefindings_id'] = @finding.id @vulnmappings = VulnMappings.new(vulnmapdata) @vulnmappings.save end - if config_options['cvss'] + if Config['cvss'] data = cvss(data, false) - elsif config_options['cvssv3'] + elsif Config['cvssv3'] data = cvss(data, true) end @@ -111,15 +111,15 @@ get '/master/findings/:id/edit' do @master = true - @languages = config_options['languages'] - @dread = config_options['dread'] - @cvss = config_options['cvss'] - @cvssv3 = config_options['cvssv3'] - @riskmatrix = config_options['riskmatrix'] - @nist800 = config_options['nist800'] - @nessusmap = config_options['nessusmap'] - @burpmap = config_options['burpmap'] - @vulnmap = config_options['vulnmap'] + @languages = Config['languages'] + @dread = Config['dread'] + @cvss = Config['cvss'] + @cvssv3 = Config['cvssv3'] + @riskmatrix = Config['riskmatrix'] + @nist800 = Config['nist800'] + @nessusmap = Config['nessusmap'] + @burpmap = Config['burpmap'] + @vulnmap = Config['vulnmap'] # Check for kosher name in report name id = params[:id] @@ -155,15 +155,15 @@ data['title'] = data['title'] - if config_options['dread'] + if Config['dread'] data['dread_total'] = data['damage'].to_i + data['reproducability'].to_i + data['exploitability'].to_i + data['affected_users'].to_i + data['discoverability'].to_i - elsif config_options['cvss'] + elsif Config['cvss'] data = cvss(data, false) - elsif config_options['cvssv3'] + elsif Config['cvssv3'] data = cvss(data, true) end - if config_options['riskmatrix'] + if Config['riskmatrix'] if data['severity'] == 'Low' severity_val = 0 elsif data['severity'] == 'Medium' @@ -184,7 +184,7 @@ end # Edit NIST800 finding in the main database - if(config_options["nist800"]) + if(Config["nist800"]) # call nist800 helper function data = nist800(data) end @@ -211,19 +211,19 @@ @finding.update(data) # save nessus mapping data to db - if config_options['nessusmap'] + if Config['nessusmap'] @nessus = NessusMapping.new(nessusdata) @nessus.save end # save burp mapping data to db - if config_options['burpmap'] + if Config['burpmap'] @burp = BurpMapping.new(burpdata) @burp.save end # save vuln mapping data to db - if config_options['vulnmap'] + if Config['vulnmap'] @vulnmappings = VulnMappings.new(vulnmappingdata) @vulnmappings.save end diff --git a/routes/report.rb b/routes/report.rb index 383faf23..30a398c6 100644 --- a/routes/report.rb +++ b/routes/report.rb @@ -1,15 +1,15 @@ require 'sinatra' require 'odle' +require './config' ##### # Reporting Routes ##### -config_options = JSON.parse(File.read('./config.json')) # set the report_assessment_types for <1.2 versions of Serpico -unless config_options['report_assessment_types'] - config_options['report_assessment_types'] = ['Network Internal', 'External', 'Web application', 'Physical', 'Social engineering', 'Configuration audit'] +unless Config['report_assessment_types'] + Config['report_assessment_types'] = ['Network Internal', 'External', 'Web application', 'Physical', 'Social engineering', 'Configuration audit'] end # List current reports @@ -19,7 +19,7 @@ @admin = true if is_administrator? # allow the user to set their logo in the configuration options - @logo = config_options['logo'] + @logo = Config['logo'] haml :reports_list end @@ -27,8 +27,8 @@ # Create a report get '/report/new' do @templates = Xslt.all - @assessment_types = config_options['report_assessment_types'] - @languages = config_options['languages'] + @assessment_types = Config['report_assessment_types'] + @languages = Config['languages'] haml :new_report end @@ -40,7 +40,7 @@ data['date'] = DateTime.now.strftime '%m/%d/%Y' @report = Reports.new(data) - @report.scoring = set_scoring(config_options) + @report.scoring = set_scoring(Config) @report.save # compensate for datamappers oddness @@ -139,8 +139,13 @@ return 'No Such Report' if @report.nil? +<<<<<<< HEAD + @auto_import = Config['auto_import'] + haml :import_scan_data, encode_html: true +======= @auto_import = config_options['auto_import'] haml :import_scan_data +>>>>>>> 21ba51183fd326f6e434bfebb716806fc285982c end post '/report/:id/import_scan_data' do @@ -155,13 +160,13 @@ return 'No Such Report' if @report.nil? if type == 'nessus' - data = JSON.parse(Nessus.new.parse(scan_xml, config_options['threshold'])) + data = JSON.parse(Nessus.new.parse(scan_xml, Config['threshold'])) elsif type == 'burpv1' - data = JSON.parse(Burp.new.parse(scan_xml, config_options['threshold'])) + data = JSON.parse(Burp.new.parse(scan_xml, Config['threshold'])) elsif type == 'metasploit' - data = JSON.parse(Metasploit.new.parse(scan_xml, config_options['threshold'])) + data = JSON.parse(Metasploit.new.parse(scan_xml, Config['threshold'])) elsif type == 'nmap' - data = JSON.parse(Nmap.new.parse(scan_xml, config_options['threshold'])) + data = JSON.parse(Nmap.new.parse(scan_xml, Config['threshold'])) else "Unknown type: #{type}" end @@ -176,11 +181,11 @@ # go directly to findings list menu @total_added = vulns.size - @chart = config_options['chart'] + @chart = Config['chart'] @plugin_side_menu = get_plugin_list('user') @findings, @dread, @cvss, @cvssv3, @risk, @riskmatrix,@nist800 = get_scoring_findings(@report) - @cvssv2_scoring_override = if config_options.key?('cvssv2_scoring_override') - config_options['cvssv2_scoring_override'] + @cvssv2_scoring_override = if Config.key?('cvssv2_scoring_override') + Config['cvssv2_scoring_override'] else false end @@ -192,7 +197,7 @@ get '/report/:id/import_nessus' do id = params[:id] - @nessusmap = config_options['nessusmap'] + @nessusmap = Config['nessusmap'] # Query for the first report matching the id @report = get_report(id) @@ -207,7 +212,7 @@ xml = params[:file][:tempfile].read if xml =~ /^/ && type == 'nessus' import_nessus = true - vulns = parse_nessus_xml(xml, config_options['threshold']) + vulns = parse_nessus_xml(xml, Config['threshold']) elsif xml =~ /^ username) + exists = User.first(:username => username) - if exists - puts "That username already exists. Please use reset_pw.rb to reset a password" - else - user = User.new - user.username = username - user.password = password - user.type = "Administrator" - user.auth_type = "Local" - user.save + if exists + puts "That username already exists. Please use reset_pw.rb to reset a password" + else + user = User.new + user.username = username + user.password = password + user.type = "Administrator" + user.auth_type = "Local" + user.save - puts "Please use the following login credentials" - puts "\t \t \t **** #{username} : #{password} ****" + puts "Please use the following login credentials" + puts "\t \t \t **** #{username} : #{password} ****" - end + end else - puts "Skipping username creation (users exist), please use the create_user.rb script to add a user." + puts "Skipping username creation (users exist), please use the create_user.rb script to add a user." end -puts "Would you like to initialize the database with templated findings? (Y/n)" -find_i = gets.chomp -if (find_i == "" or find_i.downcase == "y" or find_i.downcase == "yes") +find_i = true +do_findings = ENV['SRP_FINDINGS'] +if (do_findings) + # Disable Findings via environment variable if set to "n" + if (do_findings == "n" or do_findings == "no") + find_i = false + end +else + # Otherwise prompt for user input. + puts "Would you like to initialize the database with templated findings? (Y/n)" + find_i = gets.chomp + if (find_i == "" or find_i.downcase == "y" or find_i.downcase == "yes") + find_i = true + end +end + +if (find_i) puts "Importing Templated Findings template_findings.json..." file = File.new('./templates/template_findings.json',"rb") @@ -67,172 +84,172 @@ templates = Xslt.first if !templates - # Add default risk scoring report template -------------------- - puts "Adding the Default Generic Risk Scoring Report Template" - xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" - docx = "./templates/Serpico - GenericRiskScoring.docx" - - xslt = generate_xslt(docx) - if xslt =~ /Error file DNE/ - return "ERROR!!!!!!" - end - - # open up a file handle and write the attachment - File.open(xslt_file, 'wb') {|f| f.write(xslt) } - - # delete the file data from the attachment - datax = Hash.new - datax["docx_location"] = "#{docx}" - datax["xslt_location"] = "#{xslt_file}" - datax["description"] = "Generic Risk Scoring Report" - datax["report_type"] = "Default Template - Generic Risk Scoring" - report = Xslt.new(datax) - report.save - # -------------------------------------------------------------- - - # Add default DREAD report template ---------------------------- - puts "Adding the Default DREAD Report Template" - xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" - docx = "./templates/Serpico - Report.docx" - - xslt = generate_xslt(docx) - if xslt =~ /Error file DNE/ - return "ERROR!!!!!!" - end - - # open up a file handle and write the attachment - File.open(xslt_file, 'wb') {|f| f.write(xslt) } - - # delete the file data from the attachment - datax = Hash.new - datax["docx_location"] = "#{docx}" - datax["xslt_location"] = "#{xslt_file}" - datax["description"] = "Default Serpico Report - DREAD Scoring" - datax["report_type"] = "Default Template - DREAD Scoring" - report = Xslt.new(datax) - report.save - # -------------------------------------------------------------- - - # Add default CVSS report template ----------------------------- - puts "Adding the Default CVSS Report Template" - xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" - docx = "./templates/CVSS_Template.docx" - - xslt = generate_xslt(docx) - if xslt =~ /Error file DNE/ - return "ERROR!!!!!!" - end - - # open up a file handle and write the attachment - File.open(xslt_file, 'wb') {|f| f.write(xslt) } - - # delete the file data from the attachment - datax = Hash.new - datax["docx_location"] = "#{docx}" - datax["xslt_location"] = "#{xslt_file}" - datax["description"] = "Default CVSS Report" - datax["report_type"] = "Default CVSS Report" - report = Xslt.new(datax) - report.save - # -------------------------------------------------------------- - - # Add default CVSS3 report template ---------------------------- - puts "Adding the Default CVSSv3 Report Template" - xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" - docx = "./templates/Default CVSS 3 Report.docx" - - xslt = generate_xslt(docx) - if xslt =~ /Error file DNE/ - return "ERROR!!!!!!" - end - - # open up a file handle and write the attachment - File.open(xslt_file, 'wb') {|f| f.write(xslt) } - - # delete the file data from the attachment - datax = Hash.new - datax["docx_location"] = "#{docx}" - datax["xslt_location"] = "#{xslt_file}" - datax["description"] = "Default CVSSv3 Report" - datax["report_type"] = "Default CVSSv3 Report" - report = Xslt.new(datax) - report.save - # -------------------------------------------------------------- - - # Add default NIST800 report template -------------------------- - puts "Adding the Serpico Default NIST800 Template" - #xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" - xslt_file = "./templates/nist800.xslt" - docx = "./templates/Default NIST800 Report.docx" - - xslt = generate_xslt(docx) - if xslt =~ /Error file DNE/ - return "ERROR!!!!!!" - end - - # open up a file handle and write the attachment - File.open(xslt_file, 'wb') {|f| f.write(xslt) } - - # delete the file data from the attachment - datax = Hash.new - datax["docx_location"] = "#{docx}" - datax["xslt_location"] = "#{xslt_file}" - datax["description"] = "Default NIST800 Report" - datax["report_type"] = "Default NIST800 Report" - report = Xslt.new(datax) - report.save - # -------------------------------------------------------------- - - # Add default Finding template --------------------------------- - puts "Adding the Serpico Default Finding Template" - - xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" - docx = "./templates/Serpico - Risk Finding.docx" - - xslt = generate_xslt(docx) - if xslt =~ /Error file DNE/ - return "ERROR!!!!!!" - end - - # open up a file handle and write the attachment - File.open(xslt_file, 'wb') {|f| f.write(xslt) } - - - datax = Hash.new - datax["docx_location"] = "#{docx}" - datax["xslt_location"] = "#{xslt_file}" - datax["description"] = "Default Serpico Finding" - datax["report_type"] = "Default Finding" - report = Xslt.new(datax) - report.save - # -------------------------------------------------------------- - - puts "Adding the Serpico Default Status Template" - - xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" - docx = "./templates/Serpico - Finding.docx" - - xslt = generate_xslt(docx) - if xslt =~ /Error file DNE/ - return "ERROR!!!!!!" - end - - # open up a file handle and write the attachment - File.open(xslt_file, 'wb') {|f| f.write(xslt) } - - # delete the file data from the attachment - datax = Hash.new - datax["docx_location"] = "#{docx}" - datax["xslt_location"] = "#{xslt_file}" - datax["description"] = "Default Serpico Status" - datax["report_type"] = "Default Status" - datax["status_template"] = true - report = Xslt.new(datax) - report.save - # -------------------------------------------------------------- + # Add default risk scoring report template -------------------- + puts "Adding the Default Generic Risk Scoring Report Template" + xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" + docx = "./templates/Serpico - GenericRiskScoring.docx" + + xslt = generate_xslt(docx) + if xslt =~ /Error file DNE/ + return "ERROR!!!!!!" + end + + # open up a file handle and write the attachment + File.open(xslt_file, 'wb') {|f| f.write(xslt) } + + # delete the file data from the attachment + datax = Hash.new + datax["docx_location"] = "#{docx}" + datax["xslt_location"] = "#{xslt_file}" + datax["description"] = "Generic Risk Scoring Report" + datax["report_type"] = "Default Template - Generic Risk Scoring" + report = Xslt.new(datax) + report.save + # -------------------------------------------------------------- + + # Add default DREAD report template ---------------------------- + puts "Adding the Default DREAD Report Template" + xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" + docx = "./templates/Serpico - Report.docx" + + xslt = generate_xslt(docx) + if xslt =~ /Error file DNE/ + return "ERROR!!!!!!" + end + + # open up a file handle and write the attachment + File.open(xslt_file, 'wb') {|f| f.write(xslt) } + + # delete the file data from the attachment + datax = Hash.new + datax["docx_location"] = "#{docx}" + datax["xslt_location"] = "#{xslt_file}" + datax["description"] = "Default Serpico Report - DREAD Scoring" + datax["report_type"] = "Default Template - DREAD Scoring" + report = Xslt.new(datax) + report.save + # -------------------------------------------------------------- + + # Add default CVSS report template ----------------------------- + puts "Adding the Default CVSS Report Template" + xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" + docx = "./templates/CVSS_Template.docx" + + xslt = generate_xslt(docx) + if xslt =~ /Error file DNE/ + return "ERROR!!!!!!" + end + + # open up a file handle and write the attachment + File.open(xslt_file, 'wb') {|f| f.write(xslt) } + + # delete the file data from the attachment + datax = Hash.new + datax["docx_location"] = "#{docx}" + datax["xslt_location"] = "#{xslt_file}" + datax["description"] = "Default CVSS Report" + datax["report_type"] = "Default CVSS Report" + report = Xslt.new(datax) + report.save + # -------------------------------------------------------------- + + # Add default CVSS3 report template ---------------------------- + puts "Adding the Default CVSSv3 Report Template" + xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" + docx = "./templates/Default CVSS 3 Report.docx" + + xslt = generate_xslt(docx) + if xslt =~ /Error file DNE/ + return "ERROR!!!!!!" + end + + # open up a file handle and write the attachment + File.open(xslt_file, 'wb') {|f| f.write(xslt) } + + # delete the file data from the attachment + datax = Hash.new + datax["docx_location"] = "#{docx}" + datax["xslt_location"] = "#{xslt_file}" + datax["description"] = "Default CVSSv3 Report" + datax["report_type"] = "Default CVSSv3 Report" + report = Xslt.new(datax) + report.save + # -------------------------------------------------------------- + + # Add default NIST800 report template -------------------------- + puts "Adding the Serpico Default NIST800 Template" + #xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" + xslt_file = "./templates/nist800.xslt" + docx = "./templates/Default NIST800 Report.docx" + + xslt = generate_xslt(docx) + if xslt =~ /Error file DNE/ + return "ERROR!!!!!!" + end + + # open up a file handle and write the attachment + File.open(xslt_file, 'wb') {|f| f.write(xslt) } + + # delete the file data from the attachment + datax = Hash.new + datax["docx_location"] = "#{docx}" + datax["xslt_location"] = "#{xslt_file}" + datax["description"] = "Default NIST800 Report" + datax["report_type"] = "Default NIST800 Report" + report = Xslt.new(datax) + report.save + # -------------------------------------------------------------- + + # Add default Finding template --------------------------------- + puts "Adding the Serpico Default Finding Template" + + xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" + docx = "./templates/Serpico - Risk Finding.docx" + + xslt = generate_xslt(docx) + if xslt =~ /Error file DNE/ + return "ERROR!!!!!!" + end + + # open up a file handle and write the attachment + File.open(xslt_file, 'wb') {|f| f.write(xslt) } + + + datax = Hash.new + datax["docx_location"] = "#{docx}" + datax["xslt_location"] = "#{xslt_file}" + datax["description"] = "Default Serpico Finding" + datax["report_type"] = "Default Finding" + report = Xslt.new(datax) + report.save + # -------------------------------------------------------------- + + puts "Adding the Serpico Default Status Template" + + xslt_file = "./templates/#{rand(36**36).to_s(36)}.xslt" + docx = "./templates/Serpico - Finding.docx" + + xslt = generate_xslt(docx) + if xslt =~ /Error file DNE/ + return "ERROR!!!!!!" + end + + # open up a file handle and write the attachment + File.open(xslt_file, 'wb') {|f| f.write(xslt) } + + # delete the file data from the attachment + datax = Hash.new + datax["docx_location"] = "#{docx}" + datax["xslt_location"] = "#{xslt_file}" + datax["description"] = "Default Serpico Status" + datax["report_type"] = "Default Status" + datax["status_template"] = true + report = Xslt.new(datax) + report.save + # -------------------------------------------------------------- else - puts "Skipping XSLT creation, templates exist." + puts "Skipping XSLT creation, templates exist." end # create the SSL cert @@ -255,24 +272,24 @@ ef.subject_certificate = crt ef.issuer_certificate = crt crt.extensions = [ -ef.create_extension("basicConstraints","CA:TRUE", true), -ef.create_extension("subjectKeyIdentifier", "hash"), + ef.create_extension("basicConstraints","CA:TRUE", true), + ef.create_extension("subjectKeyIdentifier", "hash"), ] crt.add_extension ef.create_extension("authorityKeyIdentifier", -"keyid:always,issuer:always") + "keyid:always,issuer:always") crt.sign key, OpenSSL::Digest::SHA1.new File.open("./cert.pem", "w") do |f| - f.write crt.to_pem + f.write crt.to_pem end File.open("./key.pem", "w") do |f| - f.write key.to_pem + f.write key.to_pem end # Copying the default configurations over puts "Copying configuration settings over." File.open("./config.json", "w") do |f| - f.write File.open("./config.json.defaults", "rb").read + f.write File.open("./config.json.defaults", "rb").read end diff --git a/serpico.rb b/serpico.rb index 25a976ed..48e9020a 100644 --- a/serpico.rb +++ b/serpico.rb @@ -3,21 +3,21 @@ require 'openssl' require 'json' require './server.rb' -config_options = JSON.parse(File.read('./config.json')) +require './config' ## SSL Settings -ssl_certificate = config_options['ssl_certificate'] -ssl_key = config_options['ssl_key'] -use_ssl = config_options['use_ssl'] -port = config_options['port'] -bind_address = config_options['bind_address'] +ssl_certificate = Config['ssl_certificate'] +ssl_key = Config['ssl_key'] +use_ssl = Config['use_ssl'] +port = Config['port'] +bind_address = Config['bind_address'] server_options = { Port: port, Host: bind_address } -if config_options['show_exceptions'].to_s.casecmp('false').zero? || !(config_options['show_exceptions']) +if Config['show_exceptions'].to_s.casecmp('false').zero? || !(Config['show_exceptions']) puts "|+| [#{DateTime.now.strftime('%d/%m/%Y %H:%M')}] Sending Webrick logging to /dev/null.." server_options[:Logger] = WEBrick::Log.new(File.open(File::NULL, 'w')) server_options[:AccessLog] = [] @@ -38,8 +38,8 @@ server_options[:SSLOptions] = ssl_options server_options[:SSLVersion] = :TLSv1_2 - if(config_options.key?('ssl_ciphers')) - cz = config_options['ssl_ciphers'] + if(Config.key?('ssl_ciphers')) + cz = Config['ssl_ciphers'] else # SSL Ciphers cz = ['ECDHE-RSA-AES128-GCM-SHA256','ECDHE-RSA-AES256-GCM-SHA384', diff --git a/server.rb b/server.rb index 968284b2..14692add 100644 --- a/server.rb +++ b/server.rb @@ -6,25 +6,26 @@ require './helpers/helper.rb' require 'zip' require 'net/ldap' +require './config' class Server < Sinatra::Application # import config options - config_options = JSON.parse(File.read('./config.json')) - set :config_options, config_options + set :config_options, Config ## Global variables - set :finding_types, config_options['finding_types'] + set :finding_types, Config['finding_types'] + set :finding_states, Config['finding_states'] set :assessment_types, ['External', 'Internal', 'Internal/External', 'Wireless', 'Web Application', 'DoS'] set :status, ['EXPLOITED'] - set :show_exceptions, config_options['show_exceptions'] + set :show_exceptions, Config['show_exceptions'] - if config_options['effort'] - set :effort, config_options['effort'] + if Config['effort'] + set :effort, Config['effort'] else set :effort, %w[Quick Planned Involved] end - if config_options['show_exceptions'].to_s.casecmp('false').zero? || !(config_options['show_exceptions']) + if Config['show_exceptions'].to_s.casecmp('false').zero? || !(Config['show_exceptions']) configure do disable :logging set :set_logging, nil @@ -37,17 +38,17 @@ class Server < Sinatra::Application end # Set Logging - if config_options['log_file'] != '' - log = File.new(config_options['log_file'], 'a+') + if Config['log_file'] != '' + log = File.new(Config['log_file'], 'a+') set :logger_out, log - server_log("Logging set to #{config_options['log_file']}") + server_log("Logging set to #{Config['log_file']}") end # Set Alignment - if config_options['image_align'] == '' + if Config['image_align'] == '' set :alignment, 'center' else - set :alignment, config_options['image_align'] + set :alignment, Config['image_align'] end # CVSS @@ -92,13 +93,13 @@ class Server < Sinatra::Application # Risk Matrix set :severity, %w[Low Medium High] set :likelihood, %w[Low Medium High] - + # NIST800 set :nist_likelihood, ['Low','Moderate','High'] set :nist_impact, ['Informational','Low','Moderate','High','Critical'] - - if config_options['cvssv2_scoring_override'] - if config_options['cvssv2_scoring_override'] == 'true' + + if Config['cvssv2_scoring_override'] + if Config['cvssv2_scoring_override'] == 'true' set :cvssv2_scoring_override, true end else @@ -106,13 +107,13 @@ class Server < Sinatra::Application end ## LDAP Settings - if config_options['ldap'] == 'true' + if Config['ldap'] == 'true' set :ldap, true else set :ldap, false end - set :domain, config_options['ldap_domain'] - set :dc, config_options['ldap_dc'] + set :domain, Config['ldap_domain'] + set :dc, Config['ldap_dc'] enable :sessions set :session_secret, rand(36**12).to_s(36) diff --git a/views/findings_edit.haml b/views/findings_edit.haml index 7318005e..3f1d0f0b 100644 --- a/views/findings_edit.haml +++ b/views/findings_edit.haml @@ -12,6 +12,16 @@ .controls %input{ :type => "text", :name => "title", :value => "#{CGI.unescapeHTML(@finding.title)}" } -if !@master + - if @states + .control-group + %label.control-label{ :for => "state" } State + .controls + %select{ :name => "state" } + - @states.each do |state| + - if @finding and @finding.state and state == @states[@finding.state] + %option{ :selected => "selected" } #{state} + - else + %option #{state} .control-group %label.control-label{ :for => "assessment_type" } Assessment Type .controls diff --git a/views/findings_list.haml b/views/findings_list.haml index 684a14b1..7d442840 100644 --- a/views/findings_list.haml +++ b/views/findings_list.haml @@ -16,8 +16,11 @@ %thead %th{ :style => "width: 2%" } %input.checkbox#checkall{ :type => "checkbox" } - %th{ :style => "width: 50%" } + %th{ :style => "width: 43%" } Title + - if settings.finding_states + %th{ :style => "text-align:center; width: 7%" } + State %th{ :style => "text-align:center; width: 7%" } Risk %th{ :style => "text-align:center; width: 10%" } @@ -270,11 +273,18 @@ #{finding.notes.gsub("","
").gsub("
","").gsub("","•").gsub("","")} - else None. + -# Show finding state if the list of states is configured. #- + - if settings.finding_states != nil + %td{ :style => "text-align:center" } + - if finding.state + #{settings.finding_states[finding.state] || settings.finding_states[0]} + - else + -# Default to the first state #- + #{settings.finding_states[0]} - if @dread %td{ :style => "text-align:center" } #{finding.dread_total} - -#-------------------- display risk score NIST800 in current findings section --------------------#- - elsif @nist800 %td.searchable{ :style => "text-align:center", :"data-index" => "#{finding.title.downcase.gsub(' ','')}" }