Merge branch 'post_tests' into s2progress

# Conflicts:
#	modules/generators/structured_content/hackerbot_config/hbauthentication/secgen_metadata.xml
#	modules/generators/structured_content/hackerbot_config/hbauthentication/templates/intro.md.erb
#	modules/generators/structured_content/hackerbot_config/hbauthentication/templates/lab.xml.erb
#	modules/vulnerabilities/unix/access_control_misconfigurations/suid_root_bash/suid_root_bash.pp
#	modules/vulnerabilities/unix/access_control_misconfigurations/suid_root_vi/suid_root_vi.pp
#	modules/vulnerabilities/unix/web_training/dvwa/files/DVWA-master/vulnerabilities/csp/help/help.php
#	modules/vulnerabilities/unix/web_training/dvwa/manifests/apache.pp
#	scenarios/ctf/basic_narrative.xml
#	scenarios/labs/websec_lab_env.xml
#	scenarios/security_audit/team_project.xml
This commit is contained in:
ts
2019-02-15 18:18:30 +00:00
92 changed files with 1597 additions and 574 deletions

4
.gitignore vendored
View File

@@ -12,4 +12,6 @@ modules/generators/challenges/exif/secgen_local/tmp.jpg
modules/generators/challenges/compression/zip/tmp
modules/generators/challenges/image/random_jpg/secgen_local/tmp.jpg
secgen.conf
modules/encoders/compression/huffman/tmp
modules/encoders/compression/huffman/tmp
.rakeTasks
modules/**/Gemfile.lock

View File

@@ -33,6 +33,8 @@ gem 'ruby-graphviz'
gem 'rsa'
gem 'gpgmeh'
gem 'digest-sha3', :git => "http://github.com/izetex/digest-sha3-ruby"
gem 'net-ntp'
gem 'CFPropertyList'
#development only gems go here
group :test, :development do

View File

@@ -19,17 +19,18 @@ GIT
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.3.6)
PriorityQueue (0.1.2)
activesupport (5.2.1)
activesupport (5.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
bases (1.0.2)
bcrypt (3.1.12)
chunky_png (1.3.10)
chunky_png (1.3.11)
cinch (2.3.4)
concurrent-ruby (1.0.5)
concurrent-ruby (1.1.4)
credy (0.2.1)
thor (~> 0.19.1)
digest-simple (1.1.0)
@@ -38,6 +39,7 @@ GEM
digest-whirlpool (1.0.3)
duplicate (1.1.1)
facter (2.5.1)
CFPropertyList (~> 2.2)
faker (1.9.1)
i18n (>= 0.7)
faraday (0.13.1)
@@ -45,7 +47,7 @@ GEM
faraday_middleware (0.12.2)
faraday (>= 0.7.4, < 1.0)
fast_gettext (1.1.2)
ffi (1.9.25)
ffi (1.10.0)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
rake
@@ -60,14 +62,14 @@ GEM
gpgmeh (0.1.6)
activesupport (>= 2.3)
nio4r (~> 2.2)
hiera (3.4.5)
hiera (3.5.0)
hocon (1.2.5)
httpclient (2.8.3)
huffman (0.0.1)
PriorityQueue
activesupport
ruby-graphviz
i18n (1.1.0)
i18n (1.5.3)
concurrent-ruby (~> 1.0)
json (2.1.0)
librarian-puppet (3.0.0)
@@ -80,20 +82,22 @@ GEM
mini_exiftool (2.9.0)
mini_exiftool_vendored (9.2.7.v1)
mini_exiftool (>= 1.6.0)
mini_portile2 (2.3.0)
minitar (0.6.1)
mini_portile2 (2.4.0)
minitar (0.8)
minitest (5.11.3)
multi_json (1.13.1)
multipart-post (2.0.0)
net-ntp (2.1.3)
nio4r (2.3.1)
nokogiri (1.8.4)
mini_portile2 (~> 2.3.0)
nokogiri (1.10.1)
mini_portile2 (~> 2.4.0)
nori (2.6.0)
ovirt-engine-sdk (4.2.4)
ovirt-engine-sdk (4.3.0)
json (>= 1, < 3)
pg (1.1.3)
pg (1.1.4)
process_helper (0.1.2)
puppet (6.0.0)
puppet (6.2.0)
CFPropertyList (~> 2.2)
facter (> 2.0.1, < 4)
fast_gettext (~> 1.1.2)
hiera (>= 3.2.1, < 4)
@@ -102,7 +106,7 @@ GEM
multi_json (~> 1.10)
puppet-resource_api (~> 1.5)
semantic_puppet (~> 1.0)
puppet-resource_api (1.5.0)
puppet-resource_api (1.6.2)
hocon (>= 1.0)
puppet_forge (2.2.9)
faraday (>= 0.9.0, < 0.14.0)
@@ -110,15 +114,15 @@ GEM
gettext-setup (~> 0.11)
minitar
semantic_puppet (~> 1.0)
rake (12.3.1)
rdoc (6.0.4)
rake (12.3.2)
rdoc (6.1.1)
redcarpet (3.4.0)
rmagick (2.16.0)
rqrcode (0.10.1)
chunky_png (~> 1.0)
rsa (0.1.4)
rsync (1.0.9)
ruby-graphviz (1.2.3)
ruby-graphviz (1.2.4)
rubyzip (1.2.2)
scrypt (3.0.6)
ffi-compiler (>= 1.0, < 2.0)
@@ -134,7 +138,7 @@ GEM
thread_safe (~> 0.1)
wordlist (0.1.1)
spidr (~> 0.2)
yard (0.9.16)
yard (0.9.18)
zip-zip (0.3)
rubyzip (>= 1.0.0)
zipruby (0.3.6)
@@ -143,6 +147,7 @@ PLATFORMS
ruby
DEPENDENCIES
CFPropertyList
bases
bcrypt
braille!
@@ -159,6 +164,7 @@ DEPENDENCIES
librarian-puppet
mini_exiftool_vendored
minitest
net-ntp
nokogiri
nori
ovirt-engine-sdk
@@ -182,4 +188,4 @@ DEPENDENCIES
zipruby
BUNDLED WITH
1.16.1
2.0.0.pre.2

View File

@@ -304,8 +304,8 @@ end
# Database interactions
def insert_row(db_conn, prepared_statements, statement_id, secgen_args)
statement = "insert_row_#{statement_id}"
# Add --shutdown and strip trailing whitespace
secgen_args = '--shutdown ' + secgen_args.strip
# Add --shutdown and --no-tests and strip trailing whitespace
secgen_args = '--shutdown --no-tests ' + secgen_args.strip
Print.info "Adding to queue: '#{statement}' '#{secgen_args}' 'todo'"
unless prepared_statements.include? statement
db_conn.prepare(statement, 'insert into queue (secgen_args, status) values ($1, $2) returning id')

View File

@@ -0,0 +1,170 @@
# Post Provision Testing
#
# This file will be copied into each project folder at creation time.
# It will be required from each of the modules/secgen_tests/module_name.rb test scripts
#
# Test classes must: require_relative '../../../../../lib/post_provision_test'
require 'json'
require 'base64'
require 'socket'
require 'timeout'
require 'net/http'
require 'open3'
class PostProvisionTest
attr_accessor :project_path
attr_accessor :system_ip
attr_accessor :module_name
attr_accessor :module_path
attr_accessor :json_inputs
attr_accessor :port
attr_accessor :outputs
attr_accessor :all_tests_passed
def initialize
self.system_ip = get_system_ip
self.json_inputs = get_json_inputs
self.port = get_port
self.outputs = []
self.all_tests_passed = true
end
def run
test_module
puts self.outputs
exit(1) unless all_tests_passed
end
def test_module
# Call super first in overriden methods
self.outputs << "Running tests for #{self.module_name}"
end
#####################
# Testing Functions #
#####################
# Test service is up (tcp)
def test_service_up
if is_port_open? system_ip, self.port
self.outputs << "PASSED: Port #{self.port} is open at #{get_system_ip} (#{get_system_name})!"
else
self.outputs << "FAILED: Port #{self.port} is closed at #{get_system_ip} (#{get_system_name})!"
self.all_tests_passed = false
end
end
# example usage for page: /index.html
def test_html_returned_content(page, match_string, hide_content = false)
begin
source = Net::HTTP.get(get_system_ip, page, self.port)
rescue SocketError, Errno::ECONNREFUSED
# do nothing
end
if source and source.include? match_string
match_string = '<redacted>' if hide_content
self.outputs << "PASSED: Content #{match_string} is contained within #{page} at #{get_system_ip}:#{self.port} (#{get_system_name})!"
else
self.outputs << "FAILED: Content #{match_string} is not contained within #{page} at #{get_system_ip}:#{self.port} (#{get_system_name})!"
self.all_tests_passed = false
end
end
def test_local_command(test_output, local_command, match_string)
Dir.chdir(get_project_path) do
output = run_vagrant_ssh(local_command)
if output[:stdout].include? match_string or output[:stderr].include? match_string
self.outputs << "PASSED: #{test_output} local command (#{local_command}) matches with output (#{match_string}) on #{get_system_name}!"
else
self.outputs << "FAILED: #{test_output} local command (#{local_command}) matches with output (#{match_string}) on #{get_system_name}!"
self.outputs << output[:stderr]
self.all_tests_passed = false
end
end
end
##################
# Misc Functions #
##################
def run_vagrant_ssh(args)
stdout, stderr, status = Open3.capture3("/usr/bin/vagrant ssh #{get_system_name} -c '#{args}'")
{:stdout => stdout, :stderr => stderr, :exit_status => status}
end
def get_system_ip
vagrant_file_path = "#{get_project_path}/Vagrantfile"
vagrantfile = File.read(vagrant_file_path)
ip_line = vagrantfile.split("\n").delete_if {|line| !line.include? "# ip_address_for_#{get_system_name}"}[0]
ip_address = ip_line.split('=')[-1]
if ip_address == "DHCP"
self.outputs << "FAILED: Cannot test against dynamic IPs" # TODO: fix this so that we grab dynamic IP address (maybe from vagrant?)
exit(1)
else
ip_address
end
end
def get_json_inputs
json_inputs_path = "#{File.expand_path('../', self.module_path)}/secgen_functions/files/json_inputs/*"
json_inputs_files = Dir.glob(json_inputs_path)
json_inputs_files.delete_if {|path| !path.include?(self.module_name)}
if json_inputs_files.size > 0
return JSON.parse(Base64.strict_decode64(File.read(json_inputs_files.first)))
end
{}
end
def get_port
if get_json_inputs != {} and get_json_inputs['port'] != nil
get_json_inputs['port'][0].to_i
else
-1
end
end
# Pass __FILE__ in from subclasses
def get_module_path(file_path)
"#{File.expand_path('..', File.dirname(file_path))}"
end
# Note: returns proftpd_testing
def get_system_name
get_system_path.match(/.*?([^\/]*)$/i).captures[0]
end
# Note: returns /home/thomashaw/git/SecGen/projects/SecGen20190202_010552/puppet/proftpd_testing
def get_system_path
"#{File.expand_path('../../', self.module_path)}"
end
# Note: returns /home/thomashaw/git/SecGen/projects/SecGen20190202_010552/
def get_project_path
"#{File.expand_path('../../../../', self.module_path)}"
end
def is_port_open?(ip, port)
retries = 5
while retries > 0
begin
Timeout::timeout(2) do
begin
s = TCPSocket.new(ip, port)
s.close
return true
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
# do nothing
end
end
rescue Timeout::Error
# ignored
end
retries -= 1
end
false
end
end

View File

@@ -172,6 +172,10 @@ class ProjectFilesCreator
abort
end
# Copy the test superclass into the project/lib directory
Print.std "Copying post-provision testing class"
FileUtils.mkdir("#{@out_dir}/lib")
FileUtils.cp("#{ROOT_DIR}/lib/objects/post_provision_test.rb", "#{@out_dir}/lib/post_provision_test.rb")
Print.std "VM(s) can be built using 'vagrant up' in #{@out_dir}"

View File

@@ -136,6 +136,8 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
<% else %>
<%= system.name %>.vm.network :<%= selected_module.attributes['type'].first %>, type: "dhcp", auto_config: false
<% end %>
<% # Below string is used within testing, do not delete. -%>
# ip_address_for_<%= system.name %>=DHCP
<% # Static networking -%>
<% else -%>
<% # Static oVirt networking -%>
@@ -147,9 +149,13 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# use some shell scripting to identify the name of the network interface (eth0/ens3/...), and set the IP address statically
<%= system.name %>.vm.provision 'shell', inline: "echo -e \"auto lo\niface lo inet loopback\n\nauto <%= interface %>\niface <%= interface %> inet static\n\taddress <%= resolve_network(selected_module)%>\" > /etc/network/interfaces"
<%= system.name %>.vm.provision 'shell', inline: "echo '' > /etc/environment"
<% # Static Virtualbox networking -%>
<% # Below string is used within testing, do not delete. -%>
# ip_address_for_<%= system.name %>=<%= resolve_network(selected_module)%>
<% # Static Virtualbox networking -%>
<% else -%>
<%= system.name %>.vm.network :<%= selected_module.attributes['type'].first %>, ip: "<%= resolve_network(selected_module)%>"
<% # Below string is used within testing, do not delete. -%>
# ip_address_for_<%= system.name %>=<%= resolve_network(selected_module)%>
<% end -%>
<% end -%>
<% when 'vulnerability', 'service', 'utility', 'build' -%>
@@ -161,14 +167,14 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
b64_json_inputs = Base64.strict_encode64(json_inputs)
# save the inputs in a randomly named file in the
# project out directory of the secgen_functions module
rand = SecureRandom.hex().to_s
json_inputs_filename = "#{selected_module.module_path_end}_#{SecureRandom.hex(15).to_s}"
dir = "#{@out_dir}/puppet/#{system.name}/modules/secgen_functions/files/json_inputs"
FileUtils.mkdir_p(dir) unless File.exists?(dir)
Print.verbose "Writing #{selected_module.module_path_name} input to: #{dir}/#{rand}"
File.write("#{dir}/#{rand}", b64_json_inputs)
Print.verbose "Writing #{selected_module.module_path_name} input to: #{dir}/#{json_inputs_filename}"
File.write("#{dir}/#{json_inputs_filename}", b64_json_inputs)
-%>
<%= module_name%>.facter = {
"base64_inputs_file" => '<%= rand %>',
"base64_inputs_file" => '<%= json_inputs_filename %>',
}
<% end -%>
<%=module_name%>.module_path = "<%="puppet/#{system.name}/modules"%>"

View File

@@ -20,4 +20,5 @@
<reference>https://atlas.hashicorp.com/puppetlabs</reference>
<software_license>various</software_license>
</base>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0"?>
<base xmlns="http://www.github/cliffe/SecGen/base"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.github/cliffe/SecGen/base">
<name>Ubuntu Xenial 16.04 LTS 64-bit Server by puppetlabs</name>
<author>Thomas Shaw</author>
<module_license>GPLv3</module_license>
<description>TODO </description>
<cpu_word_size>64-bit</cpu_word_size>
<type>server</type>
<type>cli</type>
<platform>linux</platform>
<platform>unix</platform>
<distro>Ubuntu Xenial 16.04 LTS</distro>
<url>https://app.vagrantup.com/puppetlabs/boxes/ubuntu-16.04-64-puppet/versions/1.0.0/providers/virtualbox.box</url>
<ovirt_template>debian_server</ovirt_template>
<reference>https://atlas.hashicorp.com/puppetlabs</reference>
<software_license>various</software_license>
</base>

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class MySQLStretchTest < PostProvisionTest
def initialize
self.module_name = 'mysql_stretch_compatible'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('mysqld process running?', 'ps -ef | grep mysqld', '/usr/sbin/mysqld')
end
end
MySQLStretchTest.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class MySQLWheezyTest < PostProvisionTest
def initialize
self.module_name = 'mysql_wheezy_compatible'
self.module_path = get_module_path(__FILE__)
super
self.port = 3306
end
def test_module
super
test_local_command('mysqld process running?', 'ps -ef | grep mysqld', '/usr/bin/mysqld')
end
end
MySQLWheezyTest.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class Popa3dTest < PostProvisionTest
def initialize
self.module_name = 'popa3d'
self.module_path = get_module_path(__FILE__)
super
self.port = 110
end
def test_module
super
test_service_up
end
end
Popa3dTest.new.run

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class ProftpdTest < PostProvisionTest
def initialize
self.module_name = 'proftpd'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
ProftpdTest.new.run

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class VsftpdTest < PostProvisionTest
def initialize
self.module_name = 'vsftpd'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
VsftpdTest.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class ApacheBashCGITest < PostProvisionTest
def initialize
self.module_name = 'apache_bash_cgi'
self.module_path = get_module_path(__FILE__)
super
self.port = 80
end
def test_module
super
test_service_up
end
end
ApacheBashCGITest.new.run

View File

@@ -34,7 +34,7 @@ class apache (
Boolean $service_manage = true,
$service_ensure = 'running',
$service_restart = undef,
$purge_configs = true,
$purge_configs = false,
$purge_vhost_dir = undef,
$purge_vdir = false,
$serveradmin = 'root@localhost',
@@ -90,7 +90,7 @@ class apache (
$error_log = $::apache::params::error_log,
$scriptalias = $::apache::params::scriptalias,
$access_log_file = $::apache::params::access_log_file,
$overwrite_ports = false, # TODO: Implement this as in wheezy apache
$overwrite_ports = true, # TODO: Implement this as in wheezy apache
) inherits ::apache::params {
$valid_mpms_re = $apache_version ? {
@@ -256,17 +256,19 @@ class apache (
$vhost_load_dir = $vhost_dir
}
concat { $ports_file:
ensure => present,
owner => 'root',
group => $::apache::params::root_group,
mode => $::apache::file_mode,
notify => Class['Apache::Service'],
require => Package['httpd'],
}
concat::fragment { 'Apache ports header':
target => $ports_file,
content => template('apache/ports_header.erb'),
if $overwrite_ports {
concat { $ports_file:
ensure => present,
owner => 'root',
group => $::apache::params::root_group,
mode => $::apache::file_mode,
notify => Class['Apache::Service'],
require => Package['httpd'],
}
concat::fragment { 'Apache ports header':
target => $ports_file,
content => template('apache/ports_header.erb'),
}
}
if $::apache::conf_dir and $::apache::params::conf_file {
@@ -332,7 +334,7 @@ class apache (
ensure => file,
content => template($conf_template),
notify => Class['Apache::Service'],
require => [Package['httpd'], Concat[$ports_file]],
require => [Package['httpd']],
}
# preserve back-wards compatibility to the times when default_mods was

View File

@@ -1,9 +1,25 @@
define apache::listen {
define apache::listen ($port='') {
$listen_addr_port = $name
# Template uses: $listen_addr_port
concat::fragment { "Listen ${listen_addr_port}":
target => $::apache::ports_file,
content => template('apache/listen.erb'),
if defined(Concat[$::apache::ports_file]){
# Template uses: $listen_addr_port
concat::fragment { "Listen ${listen_addr_port}":
target => $::apache::ports_file,
content => template('apache/listen.erb'),
}
} elsif $port != '80' {
# Create a temporary file
# join with cat $tmp_file >> $file
# remove tmp files
$ports_file = $::apache::ports_file
$tmp_file = "$ports_file-tmp_listen"
file { $tmp_file:
ensure => file,
content => template('apache/listen.erb'),
}
exec { "apache::listen: cat $tmp_file with ports.conf":
command => "/bin/cat $tmp_file >> $ports_file;/bin/rm $tmp_file"
}
}
}

View File

@@ -29,20 +29,20 @@ define apache::mpm (
}
}
} else {
if versioncmp($apache_version, '2.4') >= 0 {
file { "${mod_dir}/${mpm}.load":
ensure => file,
path => "${mod_dir}/${mpm}.load",
content => "LoadModule ${_id} ${_path}\n",
require => [
Package['httpd'],
Exec["mkdir ${mod_dir}"],
],
before => File[$mod_dir],
notify => Class['apache::service'],
}
if versioncmp($apache_version, '2.4') >= 0 {
file { "${mod_dir}/${mpm}.load":
ensure => file,
path => "${mod_dir}/${mpm}.load",
content => "LoadModule ${_id} ${_path}\n",
require => [
Package['httpd'],
Exec["mkdir ${mod_dir}"],
],
before => File[$mod_dir],
notify => Class['apache::service'],
}
}
}
case $::osfamily {
'debian': {
@@ -73,22 +73,27 @@ define apache::mpm (
}
}
if $mpm == 'itk' and $::operatingsystem == 'Ubuntu' and $::operatingsystemrelease == '14.04' {
# workaround https://bugs.launchpad.net/ubuntu/+source/mpm-itk/+bug/1286882
exec {
'/usr/sbin/a2dismod mpm_event':
onlyif => '/usr/bin/test -e /etc/apache2/mods-enabled/mpm_event.load',
require => Package['httpd'],
before => Package['apache2-mpm-itk'],
}
}
if $mpm == 'itk' and $::operatingsystem == 'Ubuntu' and $::operatingsystemrelease == '16.04' {
if $mpm == 'itk' and ( ( $::operatingsystem == 'Ubuntu' and $::operatingsystemrelease == '16.04' ) or ( $::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '9.0.0') >= 0 ) ) {
$packagename = 'libapache2-mpm-itk'
} else {
$packagename = "apache2-mpm-${mpm}"
}
$mod_enabled_dir = $::apache::mod_enable_dir
if $mpm == 'prefork' and ( $::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '9.0.0') >= 0 ) {
exec { '/usr/sbin/a2dismod mpm_event':
onlyif => "/usr/bin/test -e ${mod_enabled_dir}/mpm_event.load",
}
}
if $mpm == 'itk' and ( ( $::operatingsystem == 'Ubuntu' and $::operatingsystemrelease == '14.04' ) or ($::operatingsystem == 'Debian' and versioncmp($::operatingsystemrelease, '9.0.0') >= 0 ) ) {
# workaround https://bugs.launchpad.net/ubuntu/+source/mpm-itk/+bug/1286882
exec { '/usr/sbin/a2dismod mpm_event':
onlyif => "/usr/bin/test -e ${mod_enabled_dir}/mpm_event.load",
}
}
if versioncmp($apache_version, '2.4') < 0 or $mpm == 'itk' {
package { $packagename:
ensure => present,

View File

@@ -1,9 +1,31 @@
define apache::namevirtualhost {
define apache::namevirtualhost ($port=''){
$addr_port = $name
# Template uses: $addr_port
concat::fragment { "NameVirtualHost ${addr_port}":
target => $::apache::ports_file,
content => template('apache/namevirtualhost.erb'),
if defined(Concat[$::apache::ports_file]){
# Template uses: $addr_port
concat::fragment { "NameVirtualHost ${addr_port}":
target => $::apache::ports_file,
content => template('apache/namevirtualhost.erb'),
}
} elsif $port != '80' { # if a second vhost is declared off port 80
# Create a temporary file
# join with cat $tmp_file >> $file
# remove tmp files
$ports_file = $::apache::ports_file
$tmp_file = "$ports_file-tmp_nvh"
file { $tmp_file:
ensure => file,
content => template('apache/namevirtualhost.erb'),
}
exec { "apache::listen: cat $tmp_file with ports.conf":
command => "/bin/cat $tmp_file >> $ports_file;/bin/rm $tmp_file",
require => File[$tmp_file]
}
} else { # if a second vhost is declared on port 80
tidy { 'remove apache default site':
path =>'/etc/apache2/sites-enabled/000-default',
}
}
}

View File

@@ -396,12 +396,12 @@ define apache::vhost(
fail("Apache::Vhost[${name}]: Mixing IP and non-IP Listen directives is not possible; check the add_listen parameter of the apache::vhost define to disable this")
}
if $listen_addr_port and $ensure == 'present' {
ensure_resource('apache::listen', $listen_addr_port)
ensure_resource('apache::listen', $listen_addr_port, {'port'=> $port})
}
}
if ! $ip_based {
if $ensure == 'present' and (versioncmp($apache_version, '2.4') < 0) {
ensure_resource('apache::namevirtualhost', $nvh_addr_port)
ensure_resource('::apache::namevirtualhost', $nvh_addr_port, {'port' => $port})
}
}

View File

@@ -5,10 +5,14 @@ class parameterised_website::apache {
class { '::apache':
default_vhost => false,
overwrite_ports => false,
mpm_module => 'prefork',
}
apache::vhost { 'vhost.test.com':
apache::vhost { 'parameterised.website':
port => $port,
docroot => '/var/www/parameterised_website',
notify => Tidy['pws remove default site'],
}
ensure_resource('tidy','pws remove default site', {'path'=>'/etc/apache2/sites-enabled/000-default.conf'})
}

View File

@@ -0,0 +1,75 @@
require_relative '../../../../../lib/post_provision_test'
require 'json'
class ParamWebsiteTest < PostProvisionTest
attr_accessor :organisation
def initialize
self.module_name = 'parameterised_website'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
json_inputs = get_json_inputs
test_service_up
test_html_returned_content("/css/#{json_inputs['theme'][0]}", 'Bootswatch v4.0.0')
test_org_functionality(json_inputs)
test_additional_page(json_inputs)
test_security_audit_remit(json_inputs)
test_acceptable_use_policy(json_inputs)
end
def get_organisation(json_inputs)
JSON.parse(json_inputs['organisation'][0])
end
def test_org_functionality(json_inputs)
if json_inputs['organisation'] and
json_inputs['organisation'][0] and
json_inputs['organisation'][0] != ''
organisation = get_organisation(json_inputs)
employee_1 = organisation['employees'][0]
test_html_returned_content('/index.html', organisation['business_name'])
test_html_returned_content('/contact.html', organisation['business_motto'])
test_html_returned_content('/contact.html', employee_1['name'])
end
end
def test_security_audit_remit(json_inputs)
if json_inputs['security_audit'] and
json_inputs['security_audit'][0] and
json_inputs['security_audit'][0] != ''
test_html_returned_content('/security_audit_remit.html', "Security Audit Remit of #{get_organisation(json_inputs)['business_name']}")
end
end
def test_acceptable_use_policy(json_inputs)
if json_inputs['host_acceptable_use_policy'] and
json_inputs['host_acceptable_use_policy'][0] and
json_inputs['host_acceptable_use_policy'][0] == 'true'
test_html_returned_content('/acceptable_use_policy.html', "Acceptable Use Policy")
test_html_returned_content('/acceptable_use_policy.html', get_organisation(json_inputs)['business_name'])
end
end
def test_additional_page(json_inputs)
if json_inputs['additional_page_filenames'] and
json_inputs['additional_page_filenames'][0] and
json_inputs['additional_page_filenames'][0].include? 'html' and
json_inputs['additional_pages'] and
json_inputs['additional_pages'][0]
page_name = json_inputs['additional_page_filenames'][0]
page_name = "/#{page_name}" if page_name.split[0] != '/'
test_html_returned_content(page_name, json_inputs['additional_pages'][0], true)
end
end
end
ParamWebsiteTest.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class IRC2Test < PostProvisionTest
def initialize
self.module_name = 'irc2'
self.module_path = get_module_path(__FILE__)
super
self.port = 6667
end
def test_module
super
test_service_up
end
end
IRC2Test.new.run

View File

@@ -32,10 +32,6 @@
<name>Kali.*</name>
</conflict>
<conflict>
<name>.*Stretch.*</name>
</conflict>
<requires>
<type>update</type>
</requires>

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class UnrealircTest < PostProvisionTest
def initialize
self.module_name = 'unrealirc'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
UnrealircTest.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class NFSShareTest < PostProvisionTest
def initialize
self.module_name = 'ntp'
self.module_path = get_module_path(__FILE__)
super
self.port = 2049
end
def test_module
super
test_service_up
end
end
NFSShareTest.new.run

View File

@@ -0,0 +1,39 @@
require_relative '../../../../../lib/post_provision_test'
require 'net/ntp'
class NTPTest < PostProvisionTest
def initialize
self.module_name = 'ntp'
self.module_path = get_module_path(__FILE__)
super
self.port = 123
end
def test_module
super
test_ntp_query
end
def test_ntp_query
time_response = ''
retries = 5
while retries > 0
begin
time_response = Net::NTP.get(system_ip, port).time
break
rescue Errno::ECONNREFUSED, Timeout::Error
# do nothing
end
sleep 2
retries = -1
end
if time_response != ''
self.outputs << "PASSED: NTP responded on UDP port #{port} with #{time_response}"
else
self.outputs << "FAILED: unable to connect to #{module_name} on UDP port #{port}"
self.all_tests_passed = false
end
end
end
NTPTest.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class SambaTest < PostProvisionTest
def initialize
self.module_name = 'samba'
self.module_path = get_module_path(__FILE__)
super
self.port = 139
end
def test_module
super
test_service_up
end
end
SambaTest.new.run

View File

@@ -0,0 +1,90 @@
#!/bin/bash
: <<'END'
This software was created by United States Government employees at
The Center for the Information Systems Studies and Research (CISR)
at the Naval Postgraduate School NPS. Please note that within the
United States, copyright protection is not available for any works
created by United States Government employees, pursuant to Title 17
United States Code Section 105. This software is in the public
domain and is not subject to copyright.
END
#
#Install Docker on a Debian system, along with other packages required by Labtainers
#
type sudo >/dev/null 2>&1 || { echo >&2 "Please install sudo. Aborting."; exit 1; }
sudo -v || { echo >&2 "Please make sure user is sudoer. Aborting."; exit 1; }
#needed packages for Docker install
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates curl gnupg2 software-properties-common
#adds Docker<65>s official GPG Key
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
#used to verify matching Key ID (optional)
#sudo apt-key fingerprint 0EBFCD88
#sets up stable repository
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
#installs Docker:Community Edition
sudo apt-get update
# SecGen change: repo is unauthenticated
sudo apt-get -y --allow-unauthenticated install docker-ce
#gives user access to docker commands
sudo groupadd docker
sudo usermod -aG docker $USER
#enables and starts docker
sudo systemctl start docker
sudo systemctl enable docker
#additional packages needed for labtainers
sudo apt-get -y install python-pip
sudo pip install --upgrade pip
sudo pip install netaddr parse python-dateutil
sudo apt-get -y install openssh-server
#---Checking if packages have been installed. If not, the system will not reboot and allow the user to investigate.
declare -a packagelist=("apt-transport-https" "ca-certificates" "curl" "gnupg2" "software-properties-common" "docker-ce" "python-pip" "openssh-server")
packagefail="false"
for i in "${packagelist[@]}"
do
#echo $i
packagecheck=$(dpkg -s $i 2> /dev/null | grep Status)
#echo $packagecheck
if [ "$packagecheck" != "Status: install ok installed" ]; then
if [ $i = docker-ce ];then
echo "ERROR: '$i' package did not install properly. Please check the terminal output above for any errors related to the pacakge installation. Run the install script two more times. If the issue persists, go to docker docs and follow the instructions for installing docker. (Make sure the instructions is CE and is for your Linux distribution,e.g., Ubuntu and Fedora.)"
else
echo "ERROR: '$i' package did not install properly. Please check the terminal output above for any errors related to the pacakge installation. Try installing the '$i' package individually by executing this in the command line: 'sudo apt-get install $i"
fi
packagefail="true"
#echo $packagefail
fi
done
pipcheck=$(pip list 2> /dev/null | grep -F netaddr)
#echo $pipcheck
if [ -z "$pipcheck" ]; then
echo "ERROR: 'netaddr' package did not install properly. Please check the terminal output for any errors related to the pacakge installation. Make sure 'python-pip' is installed and then try running this command: 'sudo -H pip install netaddr' "
packagefail="true"
#echo $packagefail
fi
pipcheck=$(pip list 2> /dev/null | grep -F parse)
#echo $pipcheck
if [ -z "$pipcheck" ]; then
echo "ERROR: 'parse' package did not install properly. Please check the terminal output for any errors related to the package installation. Make sure 'python-pip' is installed and then try running this command: 'sudo -H pip install parse' "
packagefail="true"
#echo $packagefail
fi
if [ $packagefail = "true" ]; then
exit 1
fi
exit 0
#Notes: The <20>-y<> after each install means that the user doesn<73>t need to press <20>y<EFBFBD> in between each package download. The install script is based on this page: https://docs.docker.com/engine/installation/linux/docker-ce/debian/

View File

@@ -0,0 +1,12 @@
class labtainers::config{
require labtainers::install
$secgen_parameters = secgen_functions::get_parameters($::base64_inputs_file)
$lab = $secgen_parameters['lab'][0]
exec { 'start lab':
command => "/opt/labtainers/labtainer-student/labtainer $lab",
provider => shell,
}
}

View File

@@ -0,0 +1,27 @@
class labtainers::install{
# $json_inputs = base64('decode', $::base64_inputs)
# $secgen_parameters = parsejson($json_inputs)
# $server_ip = $secgen_parameters['server_ip'][0]
# $port = $secgen_parameters['port'][0]
# these are also installed by the install script, but good to use puppet where possible
package { ['apt-transport-https', 'ca-certificates', 'curl', 'gnupg2', 'software-properties-common', 'python-pip', 'openssh-server']:
ensure => 'installed',
} ->
file { '/opt/labtainers':
ensure => directory,
recurse => true,
source => 'puppet:///modules/labtainers/labtainer.files',
mode => '0766',
owner => 'root',
group => 'root',
} ->
exec { 'install script':
command => '/opt/labtainers/install-labtainer.sh',
provider => shell,
}
}

View File

@@ -0,0 +1,24 @@
require_relative '../../../../../lib/post_provision_test'
class ParameterisedAccountsTest < PostProvisionTest
def initialize
self.module_name = 'parameterised_accounts'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_accounts_exist
end
def test_accounts_exist
get_json_inputs['accounts'].each do |account|
account = JSON.parse(account)
username = account['username']
test_local_command("#{username} account exists?", 'cat /etc/passwd', username)
end
end
end
ParameterisedAccountsTest.new.run

View File

@@ -1,17 +1,33 @@
class apt_upgrade::apt {
case $operatingsystem {
'Debian': {
exec { 'update':
command => "/usr/bin/apt-get upgrade",
tries => 5,
try_sleep => 30,
notice("Running apt-upgrade module...")
if defined('dirtycow::config') {
notice("vulnerabilities/unix/local/dirtycow included - skipping apt-get upgrade...")
} else {
case $operatingsystem {
'Debian': {
# can't upgrade puppet agent mid-provision or it breaks on oVirt.
exec { 'hold puppet-agent':
command => '/usr/bin/apt-mark hold puppet-agent'
}
exec { 'update':
command => "/usr/bin/apt-get -y upgrade",
tries => 5,
try_sleep => 30,
timeout => 0,
logoutput => true,
require => Exec['hold puppet-agent'],
}
}
}
'Ubuntu': {
exec { 'update':
command => "/usr/bin/apt-get upgrade",
tries => 5,
try_sleep => 30,
'Ubuntu': {
exec { 'update':
command => "/usr/bin/apt-get -y upgrade",
tries => 5,
try_sleep => 30,
timeout => 0,
logoutput => true,
}
}
}
}

View File

@@ -1,6 +1,6 @@
class readable_shadow::config {
file { '/etc/shadow':
ensure => present,
mode => '0622',
mode => '0644',
}
}

View File

@@ -6,7 +6,7 @@
<name>Readable Shadow File</name>
<author>Thomas Shaw</author>
<module_license>MIT</module_license>
<description>Changes permissions on shadow file to 0622, reveals password hashes to local users.
<description>Changes permissions on shadow file to 0611, reveals password hashes to local users.
This is not a common misconfiguration, and not particularly subtle.</description>
<type>access_control_misconfiguration</type>

View File

@@ -0,0 +1,18 @@
require_relative '../../../../../lib/post_provision_test'
class ReadableShadowTest < PostProvisionTest
def initialize
self.module_name = 'readable_shadow'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('Shadow readable?','sudo ls -la /etc/shadow', '-rw-r--r--')
end
end
ReadableShadowTest.new.run

View File

@@ -1,4 +1,4 @@
class uid_bash_root::config {
class suid_root_bash::config {
file { '/bin/bash':
ensure => present,
mode => '4777',

View File

@@ -15,4 +15,8 @@
<hint>Shell permission misconfiguration</hint>
<solution>Bash shell running with root permissions due to suid bit set (try /bin/bash -cp "some_command")</solution>
<conflict>
<module_path>.*shellshock.*</module_path>
</conflict>
</vulnerability>

View File

@@ -0,0 +1,19 @@
require_relative '../../../../../lib/post_provision_test'
class SUIDBashTest < PostProvisionTest
def initialize
self.module_name = 'suid_root_bash'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('bash suid bit set?','sudo ls -la /bin/bash', '-rwsrwxrwx')
test_local_command('bash runs?','/bin/bash --version', 'GNU bash')
end
end
SUIDBashTest.new.run

View File

@@ -1,4 +1,4 @@
class uid_less_root::change_uid_permissions ($file_input = [], $user = 'root') {
class suid_root_less::change_uid_permissions ($file_input = [], $user = 'root') {
$file_input.each |$file, $permission_code| {
file { $file:
mode => $permission_code,

View File

@@ -0,0 +1,19 @@
require_relative '../../../../../lib/post_provision_test'
class SUIDLessTest < PostProvisionTest
def initialize
self.module_name = 'suid_root_less'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('less suid bit set?','sudo ls -la /bin/less', '-rwsrwxrwx')
test_local_command('less runs?','/bin/less --help', 'Commands marked with * may be preceded by a number')
end
end
SUIDLessTest.new.run

View File

@@ -1,4 +1,4 @@
class {'uid_less_root::change_uid_permissions':
class {'suid_root_less::change_uid_permissions':
user => 'root',
file_input => {
'/bin/less' => '4777',

View File

@@ -0,0 +1,19 @@
require_relative '../../../../../lib/post_provision_test'
class SUIDNanoTest < PostProvisionTest
def initialize
self.module_name = 'suid_root_nano'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('nano suid bit set?','sudo ls -la /bin/nano', '-rwsrwxrwx')
test_local_command('nano runs?','/bin/nano --version', 'GNU nano')
end
end
SUIDNanoTest.new.run

View File

@@ -1,4 +1,4 @@
class uid_vi_root::change_uid_permissions ($file_input = [],$user = 'root') {
class suid_root_vi::change_uid_permissions ($file_input = [],$user = 'root') {
$file_input.each |String $file, String $permission_code| {
file { $file:
mode => $permission_code,

View File

@@ -0,0 +1,18 @@
require_relative '../../../../../lib/post_provision_test'
class SUIDviTest < PostProvisionTest
def initialize
self.module_name = 'suid_root_vi'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('vi suid bit set?','sudo ls -la $(readlink -f `whereis vim`)', 'rwsrwxrwx')
end
end
SUIDviTest.new.run

View File

@@ -1,7 +1,8 @@
class {'uid_vi_root::change_uid_permissions':
class {'suid_root_vi::change_uid_permissions':
file_input => {
'/usr/bin/vi' => '4755',
'/etc/alternatives/vi' => '4755',
'/usr/bin/vim.tiny' => '4755',
'/usr/bin/vi' => '4777',
'/etc/alternatives/vi' => '4777',
'/usr/bin/vim.tiny' => '4777',
'/usr/bin/vim.basic' => '4777',
}
}
}

View File

@@ -0,0 +1,18 @@
require_relative '../../../../../lib/post_provision_test'
class WritableGroupsTest < PostProvisionTest
def initialize
self.module_name = 'writable_groups'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('writable groups?','sudo ls -la /etc/group', 'rwxrwxrwx')
end
end
WritableGroupsTest.new.run

View File

@@ -0,0 +1,18 @@
require_relative '../../../../../lib/post_provision_test'
class WritablePasswdTest < PostProvisionTest
def initialize
self.module_name = 'writable_passwd'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('writable /etc/passwd?','sudo ls -la /etc/passwd', 'rwxrwxrwx')
end
end
WritablePasswdTest.new.run

View File

@@ -0,0 +1,18 @@
require_relative '../../../../../lib/post_provision_test'
class WritableShadowTest < PostProvisionTest
def initialize
self.module_name = 'writable_shadow'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('writable /etc/shadow?','sudo ls -la /etc/shadow', 'rwxrwxrwx')
end
end
WritableShadowTest.new.run

View File

@@ -0,0 +1,18 @@
require_relative '../../../../../lib/post_provision_test'
class ShellshockTest < PostProvisionTest
def initialize
self.module_name = 'shellshock'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('correct /bin/bash version?','/bin/bash --version', 'version 4.1')
end
end
ShellshockTest.new.run

View File

@@ -1,39 +0,0 @@
class dc16_amadhj::install {
$secgen_params = secgen_functions::get_parameters($::base64_inputs_file)
$group = $secgen_params['group']
if $secgen_params['account'][0] and $secgen_params['account'][0] != '' {
$account = parsejson($secgen_params['account'][0])
} else {
$account = undef
}
if $secgen_params['storage_directory'] and $secgen_params['storage_directory'][0] {
$storage_dir = $secgen_params['storage_directory'][0]
} else {
$storage_dir = undef
}
if $group {
::secgen_functions::install_setgid_binary { 'defcon16_amadhj_group':
source_module_name => $module_name,
challenge_name => $secgen_params['challenge_name'][0],
group => $group[0],
account => $account,
flag => $secgen_params['flag'][0],
flag_name => 'flag',
storage_dir => $storage_dir,
strings_to_leak => $secgen_params['strings_to_leak'],
}
} else {
::secgen_functions::install_setuid_root_binary { 'defcon16_amadhj':
source_module_name => $module_name,
challenge_name => $secgen_params['challenge_name'][0],
account => $account,
flag => $secgen_params['flag'][0],
flag_name => 'flag',
storage_dir => $storage_dir,
strings_to_leak => $secgen_params['strings_to_leak'],
}
}
}

View File

@@ -1,45 +0,0 @@
class python2_challenge_example::install {
$secgen_params = secgen_functions::get_parameters($::base64_inputs_file)
$group = $secgen_params['group']
$script_data = $secgen_params['script_data']
if $secgen_params['account'][0] and $secgen_params['account'][0] != '' {
$account = parsejson($secgen_params['account'][0])
} else {
$account = undef
}
if $secgen_params['storage_directory'] and $secgen_params['storage_directory'][0] {
$storage_dir = $secgen_params['storage_directory'][0]
} else {
$storage_dir = undef
}
if $group {
::secgen_functions::install_setgid_script { 'python2_challenge_example':
source_module_name => $module_name,
challenge_name => $secgen_params['challenge_name'][0],
script_name => 'test.py',
script_data => $script_data[0],
group => $group[0],
account => $account,
flag => $secgen_params['flag'][0],
flag_name => 'flag',
storage_dir => $storage_dir,
strings_to_leak => $secgen_params['strings_to_leak'],
}
} else {
::secgen_functions::install_setuid_root_script { 'python2_challenge_example':
source_module_name => $module_name,
challenge_name => $secgen_params['challenge_name'][0],
script_name => 'test.py',
script_data => $script_data[0],
account => $account,
flag => $secgen_params['flag'][0],
flag_name => 'flag',
storage_dir => $storage_dir,
strings_to_leak => $secgen_params['strings_to_leak'],
}
}
}

View File

@@ -1 +0,0 @@
include python2_challenge_example::install

View File

@@ -1,63 +0,0 @@
<?xml version="1.0"?>
<vulnerability xmlns="http://www.github/cliffe/SecGen/vulnerability"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.github/cliffe/SecGen/vulnerability">
<name>python2 Challenge Example</name>
<author>Thomas Shaw</author>
<module_license>MIT</module_license>
<description>python2 challenge example</description>
<type>script_challenge</type>
<privilege>none</privilege>
<access>local</access>
<platform>linux</platform>
<challenge_type>misc</challenge_type>
<challenge_subtype>example</challenge_subtype>
<!-- script dropped in account's home directory by default with setuid configuration. -->
<read_fact>challenge_name</read_fact>
<read_fact>script_data</read_fact>
<read_fact>account</read_fact>
<read_fact>flag</read_fact>
<!-- storage_directory: Blank by default. If supplied, store the files here. e.g. NFS or SMB storage location -->
<read_fact>storage_directory</read_fact>
<!-- group: Blank by default. If supplied install script challenge as setgid -->
<read_fact>group</read_fact>
<default_input into="challenge_name">
<value>python2_script_example</value>
</default_input>
<default_input into="script_data">
<generator module_path=".*python_example"/>
</default_input>
<default_input into="account">
<generator type="account">
<input into="username">
<value>challenges</value>
</input>
<input into="password">
<value>password</value>
</input>
</generator>
</default_input>
<default_input into="flag">
<generator type="flag_generator"/>
</default_input>
<default_input into="challenge_name">
<value>python2_script_example</value>
</default_input>
<requires>
<module_path>utilities/unix/system/accounts</module_path>
</requires>
<requires>
<module_path>utilities/unix/system/binary_script_container</module_path>
</requires>
<requires>
<module_path>utilities/unix/languages/python2/python</module_path>
</requires>
</vulnerability>

View File

@@ -1,17 +0,0 @@
class ruby_challenge_example::install {
$secgen_params = secgen_functions::get_parameters($::base64_inputs_file)
$challenge_name = $secgen_params['challenge_name'][0]
::secgen_functions::install_setgid_script { $challenge_name:
source_module_name => $module_name,
challenge_name => $challenge_name,
script_name => 'test.rb',
script_data => $secgen_params['script_data'][0],
group => $secgen_params['group'],
account => $secgen_params['account'],
flag => $secgen_params['flag'],
port => $secgen_params['port'],
storage_directory => $secgen_params['storage_directory'],
strings_to_leak => $secgen_params['strings_to_leak'],
}
}

View File

@@ -1 +0,0 @@
include ruby_challenge_example::install

View File

@@ -1,67 +0,0 @@
<?xml version="1.0"?>
<vulnerability xmlns="http://www.github/cliffe/SecGen/vulnerability"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.github/cliffe/SecGen/vulnerability">
<name>Ruby Challenge Example</name>
<author>Thomas Shaw</author>
<module_license>MIT</module_license>
<description>Ruby challenge example</description>
<type>ctf_challenge</type>
<privilege>none</privilege>
<access>local</access>
<platform>linux</platform>
<challenge_type>misc</challenge_type>
<challenge_subtype>example</challenge_subtype>
<!-- script dropped in account's home directory by default with setuid configuration. -->
<read_fact>challenge_name</read_fact>
<read_fact>script_data</read_fact>
<read_fact>account</read_fact>
<read_fact>flag</read_fact>
<!-- group: Blank by default. Uses challenge name as group name unless explicitly provided. -->
<read_fact>group</read_fact>
<!-- storage_directory: Blank by default. If supplied, store the files here. e.g. NFS or SMB storage location -->
<read_fact>storage_directory</read_fact>
<!-- port: Blank by default. If supplied install script challenge as xinetd program running on given port -->
<read_fact>port</read_fact>
<default_input into="challenge_name">
<value>ruby_challenge_example</value>
</default_input>
<default_input into="script_data">
<generator module_path=".*ruby_example"/>
</default_input>
<default_input into="account">
<generator type="account">
<input into="username">
<value>challenges</value>
</input>
<input into="password">
<value>password</value>
</input>
</generator>
</default_input>
<default_input into="flag">
<generator type="flag_generator"/>
</default_input>
<requires>
<module_path>utilities/unix/system/accounts</module_path>
</requires>
<requires>
<module_path>utilities/unix/system/binary_script_container</module_path>
</requires>
<requires>
<module_path>utilities/unix/languages/ruby</module_path>
</requires>
<requires>
<module_path>utilities/unix/system/xinetd</module_path>
</requires>
</vulnerability>

View File

@@ -30,6 +30,7 @@ class hidden_file::install {
storage_directory => $challenge_directory,
strings_to_leak => $strings_to_leak,
leaked_from => "$challenge_directory-hidden_file",
mode => '0644'
}
}

View File

@@ -36,6 +36,7 @@ class java_decompile::install {
leaked_filenames => $leaked_filenames,
strings_to_leak => $strings_to_leak,
leaked_from => "java_decompile_instructions",
mode => '0644'
}
# Run the template to generate a .java file

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class Proftpd133cBackdoorTest < PostProvisionTest
def initialize
self.module_name = 'proftpd_133c_backdoor'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
Proftpd133cBackdoorTest.new.run

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class Vsftpd234BackdoorTest < PostProvisionTest
def initialize
self.module_name = 'vsftpd_234_backdoor'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
Vsftpd234BackdoorTest.new.run

View File

@@ -84,9 +84,9 @@
<software_name>unrealircd</software_name>
<software_license>MIT</software_license>
<conflict>
<name>.*Stretch.*</name>
</conflict>
<!--<conflict>-->
<!--<name>.*Stretch.*</name>-->
<!--</conflict>-->
<conflict>
<name>.*Kali.*</name>
</conflict>

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class Unrealirc3281BackdoorTest < PostProvisionTest
def initialize
self.module_name = 'unrealirc_3281_backdoor'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
Unrealirc3281BackdoorTest.new.run

View File

@@ -40,5 +40,6 @@ class chkrootkit::install {
leaked_filenames => $leaked_filenames,
strings_to_leak => $strings_to_leak,
leaked_from => "chkrootkit_vuln",
mode => '0600'
}
}

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class ChkrootkitVulnTest < PostProvisionTest
def initialize
self.module_name = 'chkrootkit'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('Chkrootkit binary exists?', 'sudo ls -la /usr/sbin/chkrootkit', 'chkrootkit-0.49')
test_local_command('Chkrootkit runs?', 'sudo /usr/sbin/chkrootkit -V', 'chkrootkit version 0.49')
end
end
ChkrootkitVulnTest.new.run

View File

@@ -0,0 +1 @@
include dirtycow::config

View File

@@ -0,0 +1,3 @@
class dirtycow::config {
notice("dirtycow::config: Do nothing, the apt upgrade just checks if we're defined and blocks apt-get upgrade if so.")
}

View File

@@ -0,0 +1,33 @@
<?xml version="1.0"?>
<vulnerability xmlns="http://www.github/cliffe/SecGen/vulnerability"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.github/cliffe/SecGen/vulnerability">
<name>DirtyCow privilege escalation</name>
<author>Thomas Shaw</author>
<module_license>MIT</module_license>
<description>DirtyCow local privilege escalation. Including this module prevents the default apt-get upgrade from
running which leaves the wheezy bases vulnerable.
</description>
<type>unpatched_kernel</type>
<type>race_condition</type>
<privilege>root_rwx</privilege>
<access>local</access>
<platform>linux</platform>
<difficulty>medium</difficulty>
<conflict>
<name>.*Stretch.*</name>
</conflict>
<conflict>
<name>.*Kali.*</name>
</conflict>
<conflict>
<name>.*Windows.*</name>
</conflict>
<conflict>
<name>.*Ubuntu.*</name>
</conflict>
</vulnerability>

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class DirtyCOWTest < PostProvisionTest
def initialize
self.module_name = 'dirtycow'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('apt-get upgrade not performed?', 'sudo apt-get -u upgrade --assume-no','linux-image-3.')
end
end
DirtyCOWTest.new.run

View File

@@ -14,5 +14,6 @@ class setuid_nmap::init {
leaked_filenames => $leaked_filenames,
strings_to_leak => $strings_to_leak,
leaked_from => "setuid_nmap",
mode => '0600'
}
}

View File

@@ -0,0 +1,19 @@
require_relative '../../../../../lib/post_provision_test'
class SetUIDNmapTest < PostProvisionTest
def initialize
self.module_name = 'setuid_nmap'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_local_command('nmap has setuid flag?', 'sudo ls -la /usr/bin/nmap', '-rwsr-xr-x')
test_local_command('nmap runs?', 'sudo /usr/bin/nmap --version', 'Nmap version')
end
end
SetUIDNmapTest.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class DistCCExecTest < PostProvisionTest
def initialize
self.module_name = 'distcc_exec'
self.module_path = get_module_path(__FILE__)
super
self.port = 3632
end
def test_module
super
test_service_up
end
end
DistCCExecTest.new.run

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class NcBackdoorTest < PostProvisionTest
def initialize
self.module_name = 'nc_backdoor'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
NcBackdoorTest.new.run

View File

@@ -1,52 +1,52 @@
<div class="body_padded">
<h1>Help - Content Security Policy (CSP) Bypass</h1>
<div id="code">
<table width='100%' bgcolor='white' style="border:2px #C0C0C0 solid">
<tr>
<td><div id="code">
<h3>About</h3>
<p>Content Security Policy (CSP) is used to define where scripts and other resources can be loaded or executed from. This module will walk you through ways to bypass the policy based on common mistakes made by developers.</p>
<p>None of the vulnerabilities are actual vulnerabilities in CSP, they are vulnerabilities in the way it has been implemented.</p>
<br /><hr /><br />
<h3>Objective</h3>
<p>Bypass Content Security Policy (CSP) and execute JavaScript in the page.</p>
<br /><hr /><br />
<h3>Low Level</h3>
<p>Examine the policy to find all the sources that can be used to host external script files.</p>
<pre>Spoiler: <span class="spoiler">Scripts can be included from Pastebin, try storing some JavaScript on there and then loading it in.</span></pre>
<br />
<h3>Medium Level</h3>
<p>The CSP policy tries to use a nonce to prevent inline scripts from being added by attackers.</p>
<pre>Spoiler: <span class="spoiler">Examine the nonce and see how it varies (or doesn't).</span></pre>
<br />
<h3>High Level</h3>
<p>The page makes a JSONP call to source/jsonp.php passing the name of the function to callback to, you need to modify the jsonp.php script to change the callback function.</p>
<pre>Spoiler: <span class="spoiler">The JavaScript on the page will execute whatever is returned by the page, changing this to your own code will execute that instead</span></pre>
<br />
<h3>Impossible Level</h3>
<p>
This level is an update of the high level where the JSONP call has its callback function hardcoded and the CSP policy is locked down to only allow external scripts.
</p>
</div></td>
</tr>
</table>
</div>
<br />
<p>Reference: <?php echo dvwaExternalLinkUrlGet( 'https://content-security-policy.com/', "Content Security Policy Reference" ); ?></p>
<p>Reference: <?php echo dvwaExternalLinkUrlGet( 'https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP', "Mozilla Developer Network - CSP: script-src"); ?></p>
<p>Reference: <?php echo dvwaExternalLinkUrlGet( 'https://blog.mozilla.org/security/2014/10/04/csp-for-the-web-we-have/', "Mozilla Security Blog - CSP for the web we have" ); ?></p>
</div>
<div class="body_padded">
<h1>Help - Content Security Policy (CSP) Bypass</h1>
<div id="code">
<table width='100%' bgcolor='white' style="border:2px #C0C0C0 solid">
<tr>
<td><div id="code">
<h3>About</h3>
<p>Content Security Policy (CSP) is used to define where scripts and other resources can be loaded or executed from. This module will walk you through ways to bypass the policy based on common mistakes made by developers.</p>
<p>None of the vulnerabilities are actual vulnerabilities in CSP, they are vulnerabilities in the way it has been implemented.</p>
<br /><hr /><br />
<h3>Objective</h3>
<p>Bypass Content Security Policy (CSP) and execute JavaScript in the page.</p>
<br /><hr /><br />
<h3>Low Level</h3>
<p>Examine the policy to find all the sources that can be used to host external script files.</p>
<pre>Spoiler: <span class="spoiler">Scripts can be included from Pastebin, try storing some JavaScript on there and then loading it in.</span></pre>
<br />
<h3>Medium Level</h3>
<p>The CSP policy tries to use a nonce to prevent inline scripts from being added by attackers.</p>
<pre>Spoiler: <span class="spoiler">Examine the nonce and see how it varies (or doesn't).</span></pre>
<br />
<h3>High Level</h3>
<p>The page makes a JSONP call to source/jsonp.php passing the name of the function to callback to, you need to modify the jsonp.php script to change the callback function.</p>
<pre>Spoiler: <span class="spoiler">The JavaScript on the page will execute whatever is returned by the page, changing this to your own code will execute that instead</span></pre>
<br />
<h3>Impossible Level</h3>
<p>
This level is an update of the high level where the JSONP call has its callback function hardcoded and the CSP policy is locked down to only allow external scripts.
</p>
</div></td>
</tr>
</table>
</div>
<br />
<p>Reference: <?php echo dvwaExternalLinkUrlGet( 'https://content-security-policy.com/', "Content Security Policy Reference" ); ?></p>
<p>Reference: <?php echo dvwaExternalLinkUrlGet( 'https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP', "Mozilla Developer Network - CSP: script-src"); ?></p>
<p>Reference: <?php echo dvwaExternalLinkUrlGet( 'https://blog.mozilla.org/security/2014/10/04/csp-for-the-web-we-have/', "Mozilla Security Blog - CSP for the web we have" ); ?></p>
</div>

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class DVWATest < PostProvisionTest
def initialize
self.module_name = 'dvwa'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
DVWATest.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class Gitlist040Test < PostProvisionTest
def initialize
self.module_name = 'gitlist_040'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
test_html_returned_content('/', '<title>GitList</title>')
end
end
Gitlist040Test.new.run

View File

@@ -0,0 +1,17 @@
require_relative '../../../../../lib/post_provision_test'
class MoinMoin195Test < PostProvisionTest
def initialize
self.module_name = 'moinmoin_195'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
test_html_returned_content('/index.html','<script type="text/javascript" src="/moin_static195/common/js/common.js"></script>')
end
end
MoinMoin195Test.new.run

View File

@@ -12,8 +12,11 @@ class onlinestore::apache {
::apache::vhost { 'onlinestore':
port => $port,
docroot => $docroot,
notify => Tidy['os remove default site'],
}
ensure_resource('tidy','os remove default site', {'path'=>'/etc/apache2/sites-enabled/000-default.conf'})
case $operatingsystemrelease {
/^9.*/: { # do 9.x stretch stuff
exec { 'a2enmod php5.6':

View File

@@ -0,0 +1,18 @@
require_relative '../../../../../lib/post_provision_test'
class OnlineStoreTest < PostProvisionTest
def initialize
self.module_name = 'onlinestore'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
test_html_returned_content('/index.php', '<title>Welcome to furniture!</title>')
test_local_command('Users table populated?',"mysql -u csecvm --password=#{json_inputs['db_password'][0]} -D csecvm -e \"SELECT * FROM users;\"", JSON.parse(json_inputs['accounts'][1])['name'])
end
end
OnlineStoreTest.new.run

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class Wordpress1xTest < PostProvisionTest
def initialize
self.module_name = 'wordpress_1x'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
Wordpress1xTest.new.run

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class Wordpress2xTest < PostProvisionTest
def initialize
self.module_name = 'wordpress_2x'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
Wordpress2xTest.new.run

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class Wordpress3xTest < PostProvisionTest
def initialize
self.module_name = 'wordpress_3x'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
Wordpress3xTest.new.run

View File

@@ -0,0 +1,16 @@
require_relative '../../../../../lib/post_provision_test'
class Wordpress4xTest < PostProvisionTest
def initialize
self.module_name = 'wordpress_4x'
self.module_path = get_module_path(__FILE__)
super
end
def test_module
super
test_service_up
end
end
Wordpress4xTest.new.run

View File

@@ -16,7 +16,7 @@
<system>
<system_name>target_server</system_name>
<base distro="Debian 7.8" platform="linux" type="server"/>
<base distro="Debian 9" platform="linux" type="server"/>
<input into_datastore="IP_addresses">
<value>172.10.0.2</value>
@@ -437,11 +437,9 @@
</input>
</vulnerability>
<utility module_path=".*sqlmap.*"/>
<network type="private_network">
<input into="IP_address">
<datastore access="next">IP_addresses</datastore>
<datastore access="0">IP_addresses</datastore>
</input>
</network>
@@ -455,10 +453,23 @@
<system>
<system_name>attack_vm</system_name>
<base distro="Kali" name="MSF"/>
<utility module_path=".*iceweasel">
<input into="accounts">
<value>{"username":"root","password":"toor","super_user":"","strings_to_leak":[],"leaked_filenames":[]}</value>
</input>
<input into="autostart">
<value>true</value>
</input>
<input into="start_page">
<datastore access="0">IP_addresses</datastore>
</input>
</utility>
<utility module_path=".*kali_top10"/>
<network type="private_network">
<input into="IP_address">
<datastore access="next">IP_addresses</datastore>
<datastore access="1">IP_addresses</datastore>
</input>
</network>
</system>

View File

@@ -6,10 +6,10 @@
<!-- An example access control misconfiguration, setuid on /bin/bash allows programs to run with root privileges -->
<system>
<system_name>access_control_misconfigurations_uid_bash_root</system_name>
<system_name>access_control_misconfigurations_suid_root_bash</system_name>
<base platform="linux" type="server"/>
<vulnerability module_path="vulnerabilities/unix/access_control_misconfigurations/uid_bash_root"/>
<vulnerability module_path="vulnerabilities/unix/access_control_misconfigurations/suid_root_bash"/>
<utility module_path=".*parameterised_accounts">
<input into="accounts">

View File

@@ -9,7 +9,7 @@
<system_name>access_control_misconfigurations_uid_less_root</system_name>
<base platform="linux" type="server"/>
<vulnerability module_path="modules/vulnerabilities/unix/access_control_misconfigurations/uid_less_root"/>
<vulnerability module_path="modules/vulnerabilities/unix/access_control_misconfigurations/suid_root_less"/>
<utility module_path=".*parameterised_accounts">
<input into="accounts">

View File

@@ -9,7 +9,7 @@
<system_name>access_control_misconfigurations_vi_root</system_name>
<base platform="linux" type="server"/>
<vulnerability module_path="vulnerabilities/unix/access_control_misconfigurations/uid_vi_root"/>
<vulnerability module_path="vulnerabilities/unix/access_control_misconfigurations/suid_root_vi"/>
<utility module_path=".*parameterised_accounts">
<input into="accounts">

View File

@@ -9,7 +9,7 @@
<system_name>access_control_misconfigurations_vi_root</system_name>
<base platform="linux" type="server"/>
<vulnerability module_path="vulnerabilities/unix/access_control_misconfigurations/uid_vi_root"/>
<vulnerability module_path="vulnerabilities/unix/access_control_misconfigurations/suid_root_vi"/>
<network type="private_network" range="dhcp"/>
</system>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<scenario xmlns="http://www.github/cliffe/SecGen/scenario"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.github/cliffe/SecGen/scenario">
<system>
<system_name>dirtycow</system_name>
<base platform="linux" distro="Debian 7.8" />
<!-- DirtyCOW vulnerability module leaves the debian 7 bases unpatched by default. -->
<vulnerability module_path=".*dirtycow.*"/>
<input into_datastore="IP_addresses">
<value>172.16.0.12</value>
</input>
<network type="private_network">
<input into="IP_address">
<datastore access="0">IP_addresses</datastore>
</input>
</network>
</system>
</scenario>

View File

@@ -4,11 +4,14 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.github/cliffe/SecGen/scenario">
<system>
<system_name>empty_stretch</system_name>
<base platform="linux" distro="Debian 9" type="server"/>
<system_name>testing</system_name>
<base platform="linux" distro="Debian 7.8" type="server"/>
<!--<vulnerability module_path=".*dirtycow.*"/>-->
<vulnerability module_path=".*suid_root_bash.*"/>
<input into_datastore="IP_addresses">
<value>172.16.0.5</value>
<value>172.16.0.14</value>
</input>
<network type="private_network">

View File

@@ -0,0 +1,34 @@
<?xml version="1.0"?>
<scenario xmlns="http://www.github/cliffe/SecGen/scenario"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.github/cliffe/SecGen/scenario">
<name>Basic Narrative</name>
<author>Thomas Shaw</author>
<description>Single system narrative-based CTF challenge.
</description>
<type>ctf</type>
<type>attack-ctf</type>
<type>web-hints</type>
<difficulty>intermediate</difficulty>
<system>
<system_name>target_server</system_name>
<base distro="Debian 7.8" platform="linux" type="server"/>
<input into_datastore="IP_addresses">
<value>172.12.0.2</value>
</input>
<utility type="upgrade"/>
<network type="private_network">
<input into="IP_address">
<datastore access="0">IP_addresses</datastore>
</input>
</network>
</system>
</scenario>

335
secgen.rb
View File

@@ -1,6 +1,7 @@
require 'getoptlong'
require 'fileutils'
require 'nori'
require 'open3'
require_relative 'lib/helpers/constants.rb'
require_relative 'lib/helpers/print.rb'
@@ -30,6 +31,7 @@ def usage
--help, -h: Shows this usage information
--system, -y [system_name]: Only build this system_name from the scenario
--snapshot: Creates a snapshot of VMs once built
--no-tests: Prevent post-provisioning tests from running.
VIRTUALBOX OPTIONS:
--gui-output, -g: Show the running VM (not headless)
@@ -77,7 +79,7 @@ def build_config(scenario, out_dir, options)
Print.info 'Resolving systems: randomising scenario...'
# update systems with module selections
systems.map! { |system|
systems.map! {|system|
system.module_selections = system.resolve_module_selection(all_available_modules, options)
system
}
@@ -115,9 +117,9 @@ def build_vms(scenario, project_dir, options)
retry_count = OVirtFunctions::provider_ovirt?(options) ? 2 : 0
successful_creation = false
while retry_count and !successful_creation
while retry_count >= 0 and !successful_creation
vagrant_output = GemExec.exe('vagrant', project_dir, "#{command} #{system}")
if vagrant_output[:status] == 0
if vagrant_output[:status] == 0 and post_provision_tests(project_dir, options)
Print.info 'VMs created.'
successful_creation = true
if options[:shutdown] or OVirtFunctions::provider_ovirt?(options)
@@ -141,9 +143,9 @@ def build_vms(scenario, project_dir, options)
elsif match = line.match(/^([-a-zA-Z_0-9]+):[^:]+VM is not created/i)
vm_not_to_destroy = match.captures[0]
Print.err "Not going to destroy #{vm_not_to_destroy}, since it does not exist"
failures_to_destroy.delete_if {|x| x == vm_not_to_destroy }
failures_to_destroy.delete_if {|x| x == vm_not_to_destroy}
# TODO: not sure if there is a need to remove_uncreated_vms() here too? (I don't think so?)
end
end # TODO: Add another elsif here to check if any tests have failed, edit the output of the tests so that it has a unique string that captures the vm name
end
failures_to_destroy = failures_to_destroy.uniq
@@ -168,7 +170,7 @@ def build_vms(scenario, project_dir, options)
end
sleep(10)
end
else # TODO: elsif vagrant_output[:exception].type == ProcessHelper::TimeoutError >destroy individually broken vms as above?
else # TODO: elsif vagrant_output[:exception].type == ProcessHelper::TimeoutError >destroy individually broken vms as above?
Print.err 'Vagrant up timeout, destroying VMs and retrying...'
GemExec.exe('vagrant', project_dir, 'destroy -f')
end
@@ -258,14 +260,14 @@ def make_forensic_image(project_dir, image_output_location, image_type)
system "cd '#{project_dir}' && vagrant halt"
case image_type.downcase
when 'raw', 'dd'
create_dd_image(drive_path, image_output_location)
when 'raw', 'dd'
create_dd_image(drive_path, image_output_location)
when 'ewf', 'e01'
create_ewf_image(drive_path, image_output_location)
when 'ewf', 'e01'
create_ewf_image(drive_path, image_output_location)
else
Print.info "The image type [#{image_type}] is not recognised."
else
Print.info "The image type [#{image_type}] is not recognised."
end
end
@@ -287,14 +289,14 @@ end
def list_scenarios
Print.std "Full paths to scenario files are displayed below"
Dir["#{ROOT_DIR}/scenarios/**/*"].select { |file| !File.directory? file }.each_with_index do |scenario_name, scenario_number|
Dir["#{ROOT_DIR}/scenarios/**/*"].select {|file| !File.directory? file}.each_with_index do |scenario_name, scenario_number|
Print.std "#{scenario_number}) #{scenario_name}"
end
end
def list_projects
Print.std "Full paths to project directories are displayed below"
Dir["#{PROJECTS_DIR}/*"].select { |file| !File.file? file }.each_with_index do |scenario_name, scenario_number|
Dir["#{PROJECTS_DIR}/*"].select {|file| !File.file? file}.each_with_index do |scenario_name, scenario_number|
Print.std "#{scenario_number}) #{scenario_name}"
end
end
@@ -329,13 +331,48 @@ def get_vm_names(scenario)
vm_names
end
def reboot_cycle(project_dir)
Print.info 'Shutting down VMs.'
sleep(30)
GemExec.exe('vagrant', project_dir, 'halt')
sleep 5
GemExec.exe('vagrant', project_dir, 'up --no-provision')
sleep 45
end
def post_provision_tests(project_dir, options)
tests_passed = true
unless options[:notests]
reboot_cycle(project_dir)
Print.info 'Running post-provision tests...'
test_module_outputs = []
test_script_paths = Dir.glob("#{project_dir}/puppet/*/modules/*/secgen_test/*.rb")
test_script_paths.each do |test_file_path|
test_stdout, test_stderr, test_status = Open3.capture3("bundle exec ruby #{test_file_path}")
test_module_outputs << {:stdout => test_stdout.split("\n"), :stderr => test_stderr, :exit_status => test_status}
end
test_module_outputs.each do |test_output|
if test_output[:exit_status].exitstatus != 0
tests_passed = false
Print.err test_output[:stdout].join("\n")
Print.err "Post provision tests contained failures!"
Print.err test_output[:stderr]
else
Print.info test_output[:stdout].join("\n")
end
end
end
tests_passed
end
# end of method declarations
# start of program execution
Print.std '~'*47
Print.std '~' * 47
Print.std 'SecGen - Creates virtualised security scenarios'
Print.std ' Licensed GPLv3 2014-18'
Print.std '~'*47
Print.std '~' * 47
# Add read-options from config file (needs handling before options parsed by GetoptLong)
if ARGV.include? '--read-options'
@@ -377,6 +414,7 @@ opts = GetoptLong.new(
['--ovirt-network', GetoptLong::REQUIRED_ARGUMENT],
['--ovirt-affinity-group', GetoptLong::REQUIRED_ARGUMENT],
['--snapshot', GetoptLong::NO_ARGUMENT],
['--no-tests', GetoptLong::NO_ARGUMENT],
)
scenario = SCENARIO_XML
@@ -387,94 +425,97 @@ options = {}
opts.each do |opt, arg|
case opt
# Main options
when '--help'
usage
when '--scenario'
scenario = arg;
when '--project'
project_dir = arg;
when '--prefix'
options[:prefix] = arg
project_dir = project_dir(arg)
when '--help'
usage
when '--scenario'
scenario = arg;
when '--project'
project_dir = arg;
when '--prefix'
options[:prefix] = arg
project_dir = project_dir(arg)
# Additional options
when '--system'
Print.info "VM control (Vagrant) commands will only apply to system #{arg} (must match a system defined in the scenario)"
options[:system] = arg
when '--reload'
Print.info "Will reload and re-provision the VMs"
options[:reload] = true
when '--gui-output'
Print.info "Gui output set (virtual machines will be spawned)"
options[:gui_output] = true
when '--nopae'
Print.info "no pae"
options[:nopae] = true
when '--hwvirtex'
Print.info "with HW virtualisation"
options[:hwvirtex] = true
when '--vtxvpid'
Print.info "with VT support"
options[:vtxvpid] = true
when '--memory-per-vm'
if options.has_key? :total_memory
Print.info 'Total memory option specified before memory per vm option, defaulting to total memory value'
else
Print.info "Memory per vm set to #{arg}"
options[:memory_per_vm] = arg
end
when '--total-memory'
if options.has_key? :memory_per_vm
Print.info 'Memory per vm option specified before total memory option, defaulting to memory per vm value'
else
Print.info "Total memory to be used set to #{arg}"
options[:total_memory] = arg
end
when '--cpu-cores'
Print.info "Number of cpus to be used set to #{arg}"
options[:cpu_cores] = arg
when '--max-cpu-usage'
Print.info "Max CPU usage set to #{arg}"
options[:max_cpu_usage] = arg
when '--shutdown'
Print.info 'Shutdown VMs after provisioning'
options[:shutdown] = true
when '--network-ranges'
Print.info 'Overriding Network Ranges'
options[:ip_ranges] = arg.split(',')
when '--forensic-image-type'
Print.info "Image output type set to #{arg}"
options[:forensic_image_type] = arg
when '--ovirtuser'
Print.info "Ovirt Username : #{arg}"
options[:ovirtuser] = arg
when '--ovirtpass'
Print.info "Ovirt Password : ********"
options[:ovirtpass] = arg
when '--ovirt-url'
Print.info "Ovirt API url : #{arg}"
options[:ovirturl] = arg
when '--ovirtauthz'
Print.info "Ovirt Authz: #{arg}"
options[:ovirtauthz] = arg
when '--ovirt-cluster'
Print.info "Ovirt Cluster : #{arg}"
options[:ovirtcluster] = arg
when '--ovirt-network'
Print.info "Ovirt Network Name : #{arg}"
options[:ovirtnetwork] = arg
when '--ovirt-affinity-group'
Print.info "Ovirt Affinity Group : #{arg}"
options[:ovirtaffinitygroup] = arg
when '--snapshot'
Print.info "Taking snapshots when VMs are created"
options[:snapshot] = true
when '--system'
Print.info "VM control (Vagrant) commands will only apply to system #{arg} (must match a system defined in the scenario)"
options[:system] = arg
when '--reload'
Print.info "Will reload and re-provision the VMs"
options[:reload] = true
when '--gui-output'
Print.info "Gui output set (virtual machines will be spawned)"
options[:gui_output] = true
when '--nopae'
Print.info "no pae"
options[:nopae] = true
when '--hwvirtex'
Print.info "with HW virtualisation"
options[:hwvirtex] = true
when '--vtxvpid'
Print.info "with VT support"
options[:vtxvpid] = true
when '--memory-per-vm'
if options.has_key? :total_memory
Print.info 'Total memory option specified before memory per vm option, defaulting to total memory value'
else
Print.err "Argument not valid: #{arg}"
usage
exit 1
Print.info "Memory per vm set to #{arg}"
options[:memory_per_vm] = arg
end
when '--total-memory'
if options.has_key? :memory_per_vm
Print.info 'Memory per vm option specified before total memory option, defaulting to memory per vm value'
else
Print.info "Total memory to be used set to #{arg}"
options[:total_memory] = arg
end
when '--cpu-cores'
Print.info "Number of cpus to be used set to #{arg}"
options[:cpu_cores] = arg
when '--max-cpu-usage'
Print.info "Max CPU usage set to #{arg}"
options[:max_cpu_usage] = arg
when '--shutdown'
Print.info 'Shutdown VMs after provisioning'
options[:shutdown] = true
when '--network-ranges'
Print.info 'Overriding Network Ranges'
options[:ip_ranges] = arg.split(',')
when '--forensic-image-type'
Print.info "Image output type set to #{arg}"
options[:forensic_image_type] = arg
when '--no-tests'
Print.info "Not running post-provision tests"
options[:notests] = true
when '--ovirtuser'
Print.info "Ovirt Username : #{arg}"
options[:ovirtuser] = arg
when '--ovirtpass'
Print.info "Ovirt Password : ********"
options[:ovirtpass] = arg
when '--ovirt-url'
Print.info "Ovirt API url : #{arg}"
options[:ovirturl] = arg
when '--ovirtauthz'
Print.info "Ovirt Authz: #{arg}"
options[:ovirtauthz] = arg
when '--ovirt-cluster'
Print.info "Ovirt Cluster : #{arg}"
options[:ovirtcluster] = arg
when '--ovirt-network'
Print.info "Ovirt Network Name : #{arg}"
options[:ovirtnetwork] = arg
when '--ovirt-affinity-group'
Print.info "Ovirt Affinity Group : #{arg}"
options[:ovirtaffinitygroup] = arg
when '--snapshot'
Print.info "Taking snapshots when VMs are created"
options[:snapshot] = true
else
Print.err "Argument not valid: #{arg}"
usage
exit 1
end
end
@@ -487,53 +528,53 @@ end
# process command
case ARGV[0]
when 'run', 'r'
project_dir = default_project_dir unless project_dir
run(scenario, project_dir, options)
when 'build-project', 'p'
project_dir = default_project_dir unless project_dir
build_config(scenario, project_dir, options)
when 'build-vms', 'v'
if project_dir
build_vms(scenario, project_dir, options)
else
Print.err 'Please specify project directory to read'
usage
exit 1
end
when 'create-forensic-image'
image_type = options.has_key?(:forensic_image_type) ? options[:forensic_image_type] : 'raw';
if project_dir
build_vms(scenario, project_dir, options)
make_forensic_image(project_dir, nil, image_type)
else
project_dir = default_project_dir unless project_dir
build_config(scenario, project_dir, options)
build_vms(scenario, project_dir, options)
make_forensic_image(project_dir, nil, image_type)
end
when 'ovirt-post-build'
ovirt_post_build(options, scenario, project_dir)
exit 0
when 'list-scenarios'
list_scenarios
exit 0
when 'list-projects'
list_projects
exit 0
when 'delete-all-projects'
delete_all_projects
Print.std 'All projects deleted'
exit 0
when 'run', 'r'
project_dir = default_project_dir unless project_dir
run(scenario, project_dir, options)
when 'build-project', 'p'
project_dir = default_project_dir unless project_dir
build_config(scenario, project_dir, options)
when 'build-vms', 'v'
if project_dir
build_vms(scenario, project_dir, options)
else
Print.err "Command not valid: #{ARGV[0]}"
Print.err 'Please specify project directory to read'
usage
exit 1
end
when 'create-forensic-image'
image_type = options.has_key?(:forensic_image_type) ? options[:forensic_image_type] : 'raw';
if project_dir
build_vms(scenario, project_dir, options)
make_forensic_image(project_dir, nil, image_type)
else
project_dir = default_project_dir unless project_dir
build_config(scenario, project_dir, options)
build_vms(scenario, project_dir, options)
make_forensic_image(project_dir, nil, image_type)
end
when 'ovirt-post-build'
ovirt_post_build(options, scenario, project_dir)
exit 0
when 'list-scenarios'
list_scenarios
exit 0
when 'list-projects'
list_projects
exit 0
when 'delete-all-projects'
delete_all_projects
Print.std 'All projects deleted'
exit 0
else
Print.err "Command not valid: #{ARGV[0]}"
usage
exit 1
end