dockscan 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +8 -0
  5. data/CODE_OF_CONDUCT.md +49 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +340 -0
  8. data/README.md +52 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/dockscan.gemspec +27 -0
  13. data/docs/dockscan.png +0 -0
  14. data/exe/dockscan +107 -0
  15. data/lib/dockscan/modules/audit/container-filesystem-diff.rb +41 -0
  16. data/lib/dockscan/modules/audit/container-filesystem-shadow.rb +37 -0
  17. data/lib/dockscan/modules/audit/container-number-process.rb +36 -0
  18. data/lib/dockscan/modules/audit/container-sshd-process.rb +33 -0
  19. data/lib/dockscan/modules/audit/docker-experimental-build.rb +27 -0
  20. data/lib/dockscan/modules/audit/docker-insecure-registries.rb +53 -0
  21. data/lib/dockscan/modules/audit/docker-limits.rb +34 -0
  22. data/lib/dockscan/modules/audit/docker-networking-forwarding.rb +27 -0
  23. data/lib/dockscan/modules/audit/docker-registry-mirror.rb +41 -0
  24. data/lib/dockscan/modules/audit/docker-storage-driver-aufs.rb +28 -0
  25. data/lib/dockscan/modules/audit.rb +32 -0
  26. data/lib/dockscan/modules/discover/get-containers.rb +14 -0
  27. data/lib/dockscan/modules/discover/get-docker-info.rb +17 -0
  28. data/lib/dockscan/modules/discover/get-docker-version.rb +14 -0
  29. data/lib/dockscan/modules/discover/get-images.rb +16 -0
  30. data/lib/dockscan/modules/discover/get-run-containers.rb +14 -0
  31. data/lib/dockscan/modules/discover.rb +16 -0
  32. data/lib/dockscan/modules/genmodule.rb +17 -0
  33. data/lib/dockscan/modules/report/html.rb +182 -0
  34. data/lib/dockscan/modules/report/stdout.rb +36 -0
  35. data/lib/dockscan/modules/report/txt.rb +36 -0
  36. data/lib/dockscan/modules/report.rb +75 -0
  37. data/lib/dockscan/scan/issue.rb +18 -0
  38. data/lib/dockscan/scan/manage.rb +172 -0
  39. data/lib/dockscan/scan/plugin.rb +14 -0
  40. data/lib/dockscan/version.rb +3 -0
  41. data/lib/dockscan.rb +7 -0
  42. metadata +141 -0
@@ -0,0 +1,41 @@
1
+ class ContainerFilesystemDiff < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks for filesystem differences'
5
+ end
6
+
7
+ def check(dockercheck)
8
+
9
+ limit=5
10
+ sp=Dockscan::Scan::Plugin.new
11
+ si=Dockscan::Scan::Issue.new
12
+ si.title="Container have higher number of changed files"
13
+ si.description="Container have high number of changed files which is not recommended practice.\nThis is not recommended for production as data can be lost. It can also mean successful break in attempt."
14
+ si.solution="It is recommended to have minimal number of changed files inside container and do not store data inside container. It is recommended to use volumes."
15
+ si.severity=4 # Low
16
+ si.risk = { "cvss" => 3.2 }
17
+ sp.vuln=si
18
+ sp.output=""
19
+ if scandata.key?("GetContainers") and not scandata["GetContainers"].obj.empty?
20
+ sp.state="run"
21
+ scandata["GetContainers"].obj.each do |container|
22
+ begin
23
+ ps=container.changes
24
+ if ps.count > limit then
25
+ sp.state="vulnerable"
26
+ allch = ''
27
+ ps.each do |change|
28
+ allch << change["Path"] << "\n"
29
+ end
30
+ sp.output << idcontainer(container) << " has more than #{limit} file changes: #{ps.count}\n"
31
+ sp.output << allch
32
+ sp.output << "\n"
33
+ end
34
+ rescue
35
+ end
36
+ end
37
+ end
38
+ return sp
39
+ end
40
+ end
41
+
@@ -0,0 +1,37 @@
1
+ class ContainerFileSystemShadow < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks /etc/shadow for problems'
5
+ end
6
+
7
+ def check(dockercheck)
8
+ sp=Dockscan::Scan::Plugin.new
9
+ si=Dockscan::Scan::Issue.new
10
+ si.title="Container have passwordless users in shadow"
11
+ si.description="Container have vulnerable entries in /etc/shadow.\nIt allows attacker to login or switch to user without password."
12
+ si.solution="It is recommended to set password for user or to lock user account."
13
+ si.severity=6 # High
14
+ si.risk = { "cvss" => 7.5 }
15
+ sp.vuln=si
16
+ sp.output=""
17
+ if scandata.key?("GetContainers") and not scandata["GetContainers"].obj.empty?
18
+ sp.state="run"
19
+ scandata["GetContainers"].obj.each do |container|
20
+ content=''
21
+ container.copy('/etc/shadow') { |chunk| content=content+chunk }
22
+ shcontent=''
23
+ Gem::Package::TarReader.new(StringIO.new(content)) { |t| shcontent=t.first.read }
24
+ # shcontent.split("\n").each do |line|
25
+ shcontent.lines.map(&:chomp).each do |line|
26
+ shfield=line.split(":")
27
+ if shfield[1].to_s=='' then
28
+ sp.state="vulnerable"
29
+ sp.output << idcontainer(container) << " does not have password set for user: #{shfield[0]}\n"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ return sp
35
+ end
36
+ end
37
+
@@ -0,0 +1,36 @@
1
+ class ContainerNumberProcess < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks number of container processes'
5
+ end
6
+
7
+ def check(dockercheck)
8
+
9
+ limit=1
10
+ sp=Dockscan::Scan::Plugin.new
11
+ si=Dockscan::Scan::Issue.new
12
+ si.title="Container have higher number of processess"
13
+ si.description="Container have more than allowable number of processes.\nThis is not recommended for production as it does not provide intended isolation."
14
+ si.solution="It is recommended to have single process inside container. If you have more than one process, it is recommended to split them in separate containers."
15
+ si.severity=4 # Low
16
+ si.risk = { "cvss" => 3.2 }
17
+ sp.vuln=si
18
+ sp.output=""
19
+ if scandata.key?("GetContainersRunning") and not scandata["GetContainersRunning"].obj.empty?
20
+ sp.state="run"
21
+ scandata["GetContainersRunning"].obj.each do |container|
22
+ ps=container.top
23
+ if ps.count > limit then
24
+ sp.state="vulnerable"
25
+ sp.output << idcontainer(container) << " has more than #{limit} process(es): #{ps.count}\n"
26
+ ps.each do |process|
27
+ sp.output << process["CMD"] << "\n"
28
+ end
29
+ sp.output << "\n"
30
+ end
31
+ end
32
+ end
33
+ return sp
34
+ end
35
+ end
36
+
@@ -0,0 +1,33 @@
1
+ class ContainerSSHProcess < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks if SSH is running inside container'
5
+ end
6
+
7
+ def check(dockercheck)
8
+ sp=Dockscan::Scan::Plugin.new
9
+ si=Dockscan::Scan::Issue.new
10
+ si.title="Container have SSH server process"
11
+ si.description="Docker daemon reports it is running SSH daemon inside container.\nThis is not recommended practice as it provides yet another attack surface for attackers and wastes computer resources."
12
+ si.solution="It is recommended to remove SSH daemon/client from container. It is recommended to use docker exec command to execute commands inside container."
13
+ si.severity=4 # Low
14
+ si.risk = { "cvss" => 3.2 }
15
+ sp.vuln=si
16
+ sp.output=""
17
+ if scandata.key?("GetContainersRunning") and not scandata["GetContainersRunning"].obj.empty?
18
+ sp.state="run"
19
+ scandata["GetContainersRunning"].obj.each do |container|
20
+ ps=container.top
21
+ ps.each do |process|
22
+ if process["CMD"].include?("ssh") then
23
+ sp.output << idcontainer(container) << " has SSH process running: " << process["CMD"] << "\n"
24
+ sp.state="vulnerable"
25
+ break
26
+ end
27
+ end
28
+ end
29
+ end
30
+ return sp
31
+ end
32
+ end
33
+
@@ -0,0 +1,27 @@
1
+ class DockerExperimentalBuild < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks if docker is running Experimental Build'
5
+ end
6
+
7
+ def check(dockercheck)
8
+ sp=Dockscan::Scan::Plugin.new
9
+ si=Dockscan::Scan::Issue.new
10
+ si.title="Running Experimental version of Docker."
11
+ si.description="Docker daemon reports it is running ExperimentalBuild.\nThis is not recommended for production as it might have problems and security issues."
12
+ si.solution="It is recommended to replace Docker version with stable and production ready one."
13
+ si.severity=6 # High
14
+ si.risk = { "cvss" => 7.0 }
15
+ si.reflinks = {"Docker's Experimental Binary" => "https://blog.docker.com/2015/06/experimental-binary/"}
16
+ sp.vuln=si
17
+ if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("ExperimentalBuild")
18
+ sp.state="run"
19
+ if scandata["GetDockerInfo"].obj["ExperimentalBuild"] == true then
20
+ sp.output = "Docker daemon reports it is running ExperimentalBuild."
21
+ sp.state="vulnerable"
22
+ end
23
+ end
24
+ return sp
25
+ end
26
+ end
27
+
@@ -0,0 +1,53 @@
1
+ class DockerInsecureRegistries < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks if insecure registries in use'
5
+ end
6
+
7
+ def check(dockercheck)
8
+ sp=Dockscan::Scan::Plugin.new
9
+ si=Dockscan::Scan::Issue.new
10
+ si.title="Insecure registries in use"
11
+ si.description="Docker daemon reports it is running configuration with insecure registries.\nThis is not recommended as attacker is able to deploy malicious images to registries."
12
+ si.solution="It is recommended to use secure registries and configuration without insecure registries."
13
+ si.severity=4 # Low
14
+ si.risk = { "cvss" => 3.2 }
15
+ si.references = {"CIS" => "2.5 Do not use insecure registries" }
16
+ sp.vuln=si
17
+ if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("RegistryConfig")
18
+ sp.state="run"
19
+ vulnerable = false
20
+ outputregs = ""
21
+ outputindexs = ""
22
+ # ["RegistryConfig"]["InsecureRegistryCIDRs"].each do |item| puts item end
23
+ scandata["GetDockerInfo"].obj["RegistryConfig"]["InsecureRegistryCIDRs"].each do |item|
24
+ if item != "127.0.0.0/8" then
25
+ vulnerable=true
26
+ outputregs = item << "\n"
27
+ end
28
+ end
29
+ # Docker.info["RegistryConfig"]["IndexConfigs"].each do |item,value| puts item,value,value["Secure"] end
30
+ scandata["GetDockerInfo"].obj["RegistryConfig"]["IndexConfigs"].each do |item, value|
31
+ if value["Secure"] != true
32
+ vulnerable=true
33
+ outputindexs = item value["Name"] << "\n"
34
+ end
35
+ end
36
+
37
+ if vulnerable then
38
+ sp.state="vulnerable"
39
+ sp.output = "Docker daemon reports it is using insecure registries. Offending issues below.\n "
40
+ if outputregs != "" then
41
+ sp.output << "Insecure CIDRs offending configuration:\n"
42
+ sp.output << outputregs << "\n"
43
+ end
44
+ if outputindexs != "" then
45
+ sp.output << "Offending registry indexes:\n"
46
+ sp.output << outputindexs << "\n"
47
+ end
48
+ end
49
+ end
50
+ return sp
51
+ end
52
+ end
53
+
@@ -0,0 +1,34 @@
1
+ class DockerLimits < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks if docker is running with defined limits'
5
+ end
6
+
7
+ def check(dockercheck)
8
+ sp=Dockscan::Scan::Plugin.new
9
+ si=Dockscan::Scan::Issue.new
10
+ si.title="Docker running without defined limits"
11
+ si.description="Docker daemon reports it is running daemon without defined limits.\nThis is not recommended as offending containers could use up all resources."
12
+ si.solution="It is recommended to define docker limits."
13
+ si.severity=5 # Medium
14
+ si.risk = { "cvss" => 4.4 }
15
+ si.references = {"CIS" => "2.10 Set default ulimit as appropriate" }
16
+ sp.output=""
17
+ sp.vuln=si
18
+ if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("MemoryLimit")
19
+ sp.state="run"
20
+ if scandata["GetDockerInfo"].obj["MemoryLimit"] == false then
21
+ sp.output << "Docker daemon reports it is running without memory limit.\n"
22
+ sp.state="vulnerable"
23
+ end
24
+ end
25
+ if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("SwapLimit")
26
+ if scandata["GetDockerInfo"].obj["SwapLimit"] == false then
27
+ sp.output << "Docker daemon reports it is running without swap limit.\n"
28
+ sp.state="vulnerable"
29
+ end
30
+ end
31
+ return sp
32
+ end
33
+ end
34
+
@@ -0,0 +1,27 @@
1
+ class DockerIPV4Forwarding < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks if docker is running with ipv4 forwarding enabled'
5
+ end
6
+
7
+ def check(dockercheck)
8
+ sp=Dockscan::Scan::Plugin.new
9
+ si=Dockscan::Scan::Issue.new
10
+ si.title="Docker running with IPv4 forwarding enabled"
11
+ si.description="Docker daemon reports it is running daemon with IPv4 forwarding enabled.\nThis is not recommended for production as it forwards network packets without rules."
12
+ si.solution="It is recommended to disable IPv4 forwarding by default."
13
+ si.severity=5 # Medium
14
+ si.risk = { "cvss" => 5.0 }
15
+ si.reflinks = {"ip_forward to expose containers to the public internet" => "https://github.com/docker/docker/issues/11508"}
16
+ sp.vuln=si
17
+ if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("IPv4Forwarding")
18
+ sp.state="run"
19
+ if scandata["GetDockerInfo"].obj["IPv4Forwarding"] == true then
20
+ sp.output = "Docker daemon reports it is running with automatic IPv4 forwarding."
21
+ sp.state="vulnerable"
22
+ end
23
+ end
24
+ return sp
25
+ end
26
+ end
27
+
@@ -0,0 +1,41 @@
1
+ class DockerRegistryMirror < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks if mirror registries are in use'
5
+ end
6
+
7
+ def check(dockercheck)
8
+ sp=Dockscan::Scan::Plugin.new
9
+ si=Dockscan::Scan::Issue.new
10
+ si.title="Docker registries are not mirrored"
11
+ si.description="Docker daemon reports it is running configuration without registry mirrors.\nIf you set up local mirror, your docker host does not have to go directly to internet if not needed."
12
+ si.solution="It is recommended to setup mirror registry."
13
+ si.severity=4 # Low
14
+ si.risk = { "cvss" => 3.0 }
15
+ si.references = {"CIS" => "2.6 Setup a local registry mirror" }
16
+ sp.vuln=si
17
+ if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("RegistryConfig")
18
+ sp.state="run"
19
+ vulnerable=true
20
+ outputindexs = ""
21
+ scandata["GetDockerInfo"].obj["RegistryConfig"]["IndexConfigs"].each do |item, value|
22
+ if value["Mirrors"] != nil
23
+ vulnerable = false
24
+ else
25
+ outputindexs << value["Name"] << "\n"
26
+ end
27
+ end
28
+
29
+ if vulnerable then
30
+ sp.state="vulnerable"
31
+ sp.output = "Docker daemon reports it does not have mirror registries.\n"
32
+ if outputindexs != "" then
33
+ sp.output << "Offending registry indexes:\n"
34
+ sp.output << outputindexs << "\n"
35
+ end
36
+ end
37
+ end
38
+ return sp
39
+ end
40
+ end
41
+
@@ -0,0 +1,28 @@
1
+ class DockerStorageDriverAufs < Dockscan::Modules::AuditModule
2
+
3
+ def info
4
+ return 'This plugin checks if storage driver is aufs'
5
+ end
6
+
7
+ def check(dockercheck)
8
+ sp=Dockscan::Scan::Plugin.new
9
+ si=Dockscan::Scan::Issue.new
10
+ si.title="Running aufs as storage driver"
11
+ si.description="Docker daemon reports it is running aufs as storage driver.\nThis is not recommended for production as it might have problems and security issues."
12
+ si.solution="It is recommended to use devicemapper instead of aufs storage driver. Actually, you should use the storage driver that is best supported by your vendor."
13
+ si.severity=4 # Low
14
+ si.risk = { "cvss" => 3.2 }
15
+ si.reflinks = {"Switching Docker from aufs to devicemapper" => "http://muehe.org/posts/switching-docker-from-aufs-to-devicemapper/"}
16
+ si.references = {"CIS" => "2.7 Do not use the aufs storage driver" }
17
+ sp.vuln=si
18
+ if scandata.key?("GetDockerInfo") and scandata["GetDockerInfo"].obj.key?("Driver")
19
+ sp.state="run"
20
+ if scandata["GetDockerInfo"].obj["Driver"] == true then
21
+ sp.output = "Docker daemon reports it is running aufs as storage driver."
22
+ sp.state="vulnerable"
23
+ end
24
+ end
25
+ return sp
26
+ end
27
+ end
28
+
@@ -0,0 +1,32 @@
1
+ module Dockscan
2
+ module Modules
3
+
4
+ class AuditModule < GenericModule
5
+ attr_accessor :scandata
6
+
7
+ def info
8
+ raise "#{self.class.name} doesn't implement `handle_command`!"
9
+ end
10
+
11
+ def idcontainer(container)
12
+ str=''
13
+ str << container.id
14
+ str << " ("
15
+ names = ''
16
+ container.info["Names"].each do |name|
17
+ names << name << " "
18
+ end
19
+ str << names
20
+ str << ")"
21
+ str << " with IP: "
22
+ str << container.json["NetworkSettings"]["IPAddress"]
23
+ return str
24
+ end
25
+
26
+ def check(dockercheck)
27
+ raise "#{self.class.name} doesn't implement `handle_command`!"
28
+ end
29
+ end # class
30
+
31
+ end # Module
32
+ end # Dockscan
@@ -0,0 +1,14 @@
1
+ class GetContainers < Dockscan::Modules::DiscoverModule
2
+
3
+ def info
4
+ return 'Container discovery module'
5
+ end
6
+
7
+ def run
8
+ sp=Dockscan::Scan::Plugin.new
9
+ sp.obj = Docker::Container.all(:all => true)
10
+ sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n")
11
+ return sp
12
+ end
13
+ end
14
+
@@ -0,0 +1,17 @@
1
+ require 'docker'
2
+
3
+ class GetDockerInfo < Dockscan::Modules::DiscoverModule
4
+
5
+ def info
6
+ return 'Info discovery module'
7
+ end
8
+
9
+ def run
10
+ sp=Dockscan::Scan::Plugin.new
11
+ sp.obj = Docker.info
12
+ sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n")
13
+ sp.state = "run"
14
+ return sp
15
+ end
16
+ end
17
+
@@ -0,0 +1,14 @@
1
+ class GetDockerVersion < Dockscan::Modules::DiscoverModule
2
+
3
+ def info
4
+ return 'Info discovery module'
5
+ end
6
+
7
+ def run
8
+ sp=Dockscan::Scan::Plugin.new
9
+ sp.obj = Docker.version
10
+ sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n")
11
+ return sp
12
+ end
13
+ end
14
+
@@ -0,0 +1,16 @@
1
+ require 'docker'
2
+
3
+ class GetImages < Dockscan::Modules::DiscoverModule
4
+
5
+ def info
6
+ return 'Image discovery module'
7
+ end
8
+
9
+ def run
10
+ sp=Dockscan::Scan::Plugin.new
11
+ sp.obj = Docker::Image.all
12
+ sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n")
13
+ return sp
14
+ end
15
+ end
16
+
@@ -0,0 +1,14 @@
1
+ class GetContainersRunning < Dockscan::Modules::DiscoverModule
2
+
3
+ def info
4
+ return 'Running Container discovery module'
5
+ end
6
+
7
+ def run
8
+ sp=Dockscan::Scan::Plugin.new
9
+ sp.obj = Docker::Container.all
10
+ sp.output = sp.obj.map{|k,v| "#{k}=#{v}"}.join("\n")
11
+ return sp
12
+ end
13
+ end
14
+
@@ -0,0 +1,16 @@
1
+ module Dockscan
2
+ module Modules
3
+
4
+ class DiscoverModule < GenericModule
5
+ attr_accessor :scandata
6
+ def info
7
+ raise "#{self.class.name} doesn't implement `handle_command`!"
8
+ end
9
+
10
+ def run
11
+ raise "#{self.class.name} doesn't implement `handle_command`!"
12
+ end
13
+ end # class
14
+
15
+ end # Module
16
+ end # Dockscan
@@ -0,0 +1,17 @@
1
+ module Dockscan
2
+ module Modules
3
+
4
+ class GenericModule
5
+ def self.modules
6
+ @modules ||= []
7
+ end
8
+
9
+ def self.inherited(klass)
10
+ @modules ||= []
11
+
12
+ @modules << klass
13
+ end
14
+ end # Class
15
+
16
+ end # Module
17
+ end # Dockscan