codesake 0.0.1 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/History.md +40 -0
- data/README.md +25 -3
- data/Rakefile +22 -2
- data/bin/codesake +36 -0
- data/codesake.gemspec +3 -0
- data/features/codesake_complains_if_missing_target.feature +8 -0
- data/features/codesake_process_jsp_file.feature +88 -0
- data/features/codesake_process_text_file.feature +23 -0
- data/features/step_definition/codesake_steps.rb +164 -0
- data/features/support/env.rb +1 -0
- data/lib/codesake.rb +8 -3
- data/lib/codesake/cli.rb +90 -0
- data/lib/codesake/engine/core.rb +10 -0
- data/lib/codesake/engine/generic.rb +12 -0
- data/lib/codesake/engine/jsp.rb +165 -0
- data/lib/codesake/engine/text.rb +36 -0
- data/lib/codesake/kernel.rb +39 -0
- data/lib/codesake/utils/files.rb +25 -0
- data/lib/codesake/utils/secrets.rb +45 -0
- data/lib/codesake/version.rb +2 -1
- data/spec/cli_spec.rb +65 -0
- data/spec/engine_core_spec.rb +45 -0
- data/spec/file_utils_spec.rb +59 -0
- data/spec/jsp_engine_spec.rb +114 -0
- data/spec/kernel_spec.rb +63 -0
- data/spec/secrets_utils_spec.rb +79 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/text_engine_spec.rb +72 -0
- metadata +92 -3
@@ -0,0 +1 @@
|
|
1
|
+
require 'aruba/cucumber'
|
data/lib/codesake.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
require "codesake/version"
|
2
|
+
require "codesake/cli"
|
3
|
+
require "codesake/kernel"
|
4
|
+
require "codesake/utils/files"
|
5
|
+
require "codesake/utils/secrets"
|
6
|
+
require "codesake/engine/text"
|
7
|
+
require "codesake/engine/generic"
|
8
|
+
require "codesake/engine/core"
|
9
|
+
require "codesake/engine/jsp"
|
2
10
|
|
3
|
-
module Codesake
|
4
|
-
# Your code goes here...
|
5
|
-
end
|
data/lib/codesake/cli.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'trimmy'
|
3
|
+
|
4
|
+
module Codesake
|
5
|
+
class Cli
|
6
|
+
attr_reader :options
|
7
|
+
attr_reader :targets
|
8
|
+
|
9
|
+
def parse(command_line)
|
10
|
+
@options = {}
|
11
|
+
|
12
|
+
return {:vulnerabilities=>:all} if (command_line.nil?) or (command_line.send(:empty?))
|
13
|
+
|
14
|
+
begin
|
15
|
+
option_parser =OptionParser.new do |opts|
|
16
|
+
executable_name = File.basename($PROGRAM_NAME)
|
17
|
+
opts.banner =
|
18
|
+
"codesake v#{Codesake::VERSION} - (C) 2012 - paolo@armoredcode.com\nReviews one or more source file for security issues.\n\nUsage #{executable_name} [options] sources\n"
|
19
|
+
# opts.on("-h", "--help") do
|
20
|
+
# @options[:help] = true
|
21
|
+
# end
|
22
|
+
|
23
|
+
opts.on("-v", "--version", "Show codesake version") do
|
24
|
+
@options[:version] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-V", "--verbose", "Be verbose") do
|
28
|
+
@options[:verbose] = true
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-k KEYWORDS", "--add-keys", "Add the command separated list of strings as reserved keywords") do |val|
|
32
|
+
@options[:keywords] = val.trim.split(",")
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-o TARGET", "--output", "Write output to file, to json string or to db usin SQLite3") do |val|
|
36
|
+
@options[:output]=:screen
|
37
|
+
val=val.trim
|
38
|
+
@options[:output]=val.to_sym if (val.to_sym == :file) or (val.to_sym == :json) or (val.to_sym == :db)
|
39
|
+
end
|
40
|
+
opts.on("-C", "--confirmed-vulnerabilities", "Show only confirmed vulnerabilities") do
|
41
|
+
@options[:vulnerabilities] = :confirmed
|
42
|
+
end
|
43
|
+
opts.on("-A", "--all-vulnerabilities", "Show all vulnerabilities found [default]") do
|
44
|
+
@options[:vulnerabilities] = :all
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
rest = option_parser.parse(command_line)
|
50
|
+
|
51
|
+
@targets = []
|
52
|
+
@targets = build_target_list(rest[0].split(" ")) if expect_targets? and (! rest.empty?) and (! rest[0].nil?)
|
53
|
+
@options[:vulnerabilities] = :all if @options[:vulnerabilities].nil?
|
54
|
+
rescue OptionParser::InvalidOption => e
|
55
|
+
@options={:error=>true, :message=>e.message}
|
56
|
+
end
|
57
|
+
@options
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def is_good_target?(target)
|
62
|
+
(!Dir.glob(target).empty?) or File.exists?(target) or File.directory?(target)
|
63
|
+
end
|
64
|
+
|
65
|
+
def has_errors?
|
66
|
+
(@options[:error])
|
67
|
+
end
|
68
|
+
|
69
|
+
def error_message
|
70
|
+
@options[:message] if has_errors?
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
private
|
75
|
+
def expect_targets?
|
76
|
+
(! @options[:help] ) and ( ! @options[:version])
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
def build_target_list(target_list)
|
81
|
+
ret = []
|
82
|
+
target_list.each do |target|
|
83
|
+
ret << {:target=>target, :valid=>is_good_target?(target)}
|
84
|
+
end
|
85
|
+
|
86
|
+
ret
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'codesake/engine/core'
|
2
|
+
|
3
|
+
module Codesake
|
4
|
+
module Engine
|
5
|
+
class Jsp
|
6
|
+
include Codesake::Utils::Files
|
7
|
+
include Codesake::Utils::Secrets
|
8
|
+
include Codesake::Engine::Core
|
9
|
+
|
10
|
+
FALSE_POSITIVES = ["request.getContextPath()", "request.getLocalName()", "request.getLocalPort()"]
|
11
|
+
|
12
|
+
|
13
|
+
attr_reader :imports
|
14
|
+
attr_reader :attack_entrypoints
|
15
|
+
attr_reader :reflected_xss
|
16
|
+
attr_reader :cookies
|
17
|
+
|
18
|
+
def initialize(filename, options)
|
19
|
+
@filename = filename
|
20
|
+
@options = options
|
21
|
+
|
22
|
+
read_file
|
23
|
+
load_secrets
|
24
|
+
end
|
25
|
+
|
26
|
+
def analyse
|
27
|
+
ret = []
|
28
|
+
@reserved_keywords = find_reserved_keywords
|
29
|
+
@imports = find_imports
|
30
|
+
@attack_entrypoints = find_attack_entrypoints
|
31
|
+
@reflected_xss = find_reflected_xss
|
32
|
+
@cookies = find_cookies
|
33
|
+
|
34
|
+
@reserved_keywords.each do |secret|
|
35
|
+
ret << "reserved keyword found: \"#{secret[:matcher]}\" (#{@filename}@#{secret[:line]})"
|
36
|
+
end
|
37
|
+
@imports.each do |import|
|
38
|
+
ret << "imported package found: \"#{import[:package]}\""
|
39
|
+
end
|
40
|
+
|
41
|
+
@attack_entrypoints.each do |entry|
|
42
|
+
ret << "attack entrypoint found: parameter \"#{entry[:param]}\" stored in \"#{entry[:var]}\" (#{@filename}@#{entry[:line]})"
|
43
|
+
end
|
44
|
+
@reflected_xss.each do |entry|
|
45
|
+
ret << "suspicious reflected xss found: \"#{entry[:var]}\" (#{@filename}@#{entry[:line]})\"" if entry[:false_positive] and @options[:vulnerabilities] == :all
|
46
|
+
ret << "reflected xss found: \"#{entry[:var]}\" (#{@filename}@#{entry[:line]})\"" if ! entry[:false_positive]
|
47
|
+
end
|
48
|
+
|
49
|
+
@cookies.each do |c|
|
50
|
+
ret << "cookie \"#{c[:name]}\" found with value: \"#{c[:value]}\" (#{@filename}@#{c[:line]})"
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
ret
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
private
|
60
|
+
# redefined
|
61
|
+
def find_reserved_keywords
|
62
|
+
|
63
|
+
ret = []
|
64
|
+
|
65
|
+
@file_content.each_with_index do |l, i|
|
66
|
+
l = l.unpack("C*").pack("U*")
|
67
|
+
@secrets.each do |s|
|
68
|
+
ret << {:line=> i+1, :matcher=>s } if l.trim.include?(s)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
ret
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_cookies
|
76
|
+
ret = []
|
77
|
+
|
78
|
+
@file_content.each_with_index do |l, i|
|
79
|
+
l = l.unpack("C*").pack("U*")
|
80
|
+
m = /Cookie (.*?) = new Cookie \("(.*?)",(.*?)\)/.match(l);
|
81
|
+
ret << {:line => i+1, :var => m[1].trim, :name => m[2].trim.gsub("\"", ""), :value => m[3].trim.gsub("\"", "")} unless m.nil?
|
82
|
+
|
83
|
+
m = /Cookie (.*?) = new Cookie\("(.*?)",(.*?)\)/.match(l);
|
84
|
+
ret << {:line => i+1, :var => m[1].trim, :name => m[2].trim.gsub("\"", ""), :value => m[3].trim.gsub("\"", "")} unless m.nil?
|
85
|
+
|
86
|
+
|
87
|
+
m = /(.*?) = new Cookie \("(.*?)",(.*?)\)/.match(l);
|
88
|
+
ret << {:line => i+1, :var => m[1].trim, :name => m[2].trim.gsub("\"", ""), :value => m[3].trim.gsub("\"", "")} unless m.nil?
|
89
|
+
|
90
|
+
|
91
|
+
end
|
92
|
+
ret
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
def find_reflected_xss
|
98
|
+
ret = []
|
99
|
+
@file_content.each_with_index do |l, i|
|
100
|
+
# <%=avar%> #=> /<%=(\w+)%>/.match(a)[1] = avar
|
101
|
+
l = l.unpack("C*").pack("U*")
|
102
|
+
m = /<%=(.*?)%>/.match(l)
|
103
|
+
ret << {:line => i+1, :var=> m[1].trim, :false_positive=>Codesake::Engine::Jsp.is_false_positive?(m[1].trim)} unless m.nil?
|
104
|
+
|
105
|
+
m = /out\.println\((.*?)\)/.match(l)
|
106
|
+
ret << {:line => i+1, :var=> m[1].trim, :false_positive=>Codesake::Engine::Jsp.is_false_positive?(m[1].trim)} unless m.nil?
|
107
|
+
|
108
|
+
m = /out\.print\((.*?)\)/.match(l)
|
109
|
+
ret << {:line => i+1, :var=> m[1].trim, :false_positive=>Codesake::Engine::Jsp.is_false_positive?(m[1].trim)} unless m.nil?
|
110
|
+
|
111
|
+
m = /out\.write\((.*?)\)/.match(l)
|
112
|
+
ret << {:line => i+1, :var=> m[1].trim, :false_positive=>Codesake::Engine::Jsp.is_false_positive?(m[1].trim)} unless m.nil?
|
113
|
+
|
114
|
+
m = /out\.writeln\((.*?)\)/.match(l)
|
115
|
+
ret << {:line => i+1, :var=> m[1].trim, :false_positive=>Codesake::Engine::Jsp.is_false_positive?(m[1].trim)} unless m.nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
ret
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.is_false_positive?(var)
|
122
|
+
FALSE_POSITIVES.include?(var)
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def find_attack_entrypoints
|
127
|
+
ret = []
|
128
|
+
@file_content.each_with_index do |l, i|
|
129
|
+
l = l.unpack("C*").pack("U*")
|
130
|
+
m = /request.getParameter\((.*?)\)/.match(l)
|
131
|
+
ret << {:line => i+1, :param => m[1].trim.gsub("\"", ""), :var => variable_from_line(l) } unless m.nil?
|
132
|
+
|
133
|
+
m = /request.getParameterValues\((.*?)\)/.match(l)
|
134
|
+
ret << {:line => i+1, :param => m[1].trim.gsub("\"", ""), :var => variable_from_line(l) } unless m.nil?
|
135
|
+
|
136
|
+
m = /request.getAttribute\((.*?)\)/.match(l)
|
137
|
+
ret << {:line => i+1, :param => m[1].trim.gsub("\"", ""), :var => variable_from_line(l) } unless m.nil?
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
ret
|
142
|
+
end
|
143
|
+
def variable_from_line(line)
|
144
|
+
ret = ""
|
145
|
+
left_operand = line.split('=')[0]
|
146
|
+
l = left_operand.split
|
147
|
+
l[l.size - 1]
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
def find_imports
|
153
|
+
ret = []
|
154
|
+
@file_content.each_with_index do |l, i|
|
155
|
+
l = l.unpack("C*").pack("U*")
|
156
|
+
m = /<%@page import="(.*?)"%>/.match(l)
|
157
|
+
ret << {:line => i+1, :package=>m[1].trim} unless m.nil?
|
158
|
+
end
|
159
|
+
|
160
|
+
ret
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'codesake/engine/core'
|
2
|
+
|
3
|
+
module Codesake
|
4
|
+
module Engine
|
5
|
+
class Text
|
6
|
+
include Codesake::Utils::Files
|
7
|
+
include Codesake::Utils::Secrets
|
8
|
+
include Codesake::Engine::Core
|
9
|
+
|
10
|
+
attr_reader :reserved_keywords
|
11
|
+
|
12
|
+
def initialize(filename)
|
13
|
+
@filename = filename
|
14
|
+
@raw_results = nil
|
15
|
+
|
16
|
+
read_file
|
17
|
+
load_secrets
|
18
|
+
end
|
19
|
+
|
20
|
+
def analyse
|
21
|
+
ret = []
|
22
|
+
@reserved_keywords = find_reserved_keywords
|
23
|
+
@reserved_keywords.each do |secret|
|
24
|
+
ret << "reserved keyword found: \"#{secret[:matcher]}\" (#{@filename}@#{secret[:line]})"
|
25
|
+
end
|
26
|
+
|
27
|
+
ret
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.is_txt?(filename)
|
31
|
+
(File.extname(filename).empty? or File.extname(filename) == ".txt" or File.extname(filename) == ".conf" or File.extname(filename) == ".rc" or File.extname(filename) == ".bak" or File.extname(filename) == ".old" )
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Codesake
|
4
|
+
class Kernel
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
attr_reader :engine
|
8
|
+
|
9
|
+
NONE = 0
|
10
|
+
TEXT = 1
|
11
|
+
JSP = 2
|
12
|
+
UNKNOWN = -1
|
13
|
+
|
14
|
+
def choose_engine(filename, options)
|
15
|
+
|
16
|
+
|
17
|
+
engine = nil
|
18
|
+
|
19
|
+
case detect(filename)
|
20
|
+
when TEXT
|
21
|
+
engine = Codesake::Engine::Text.new(filename)
|
22
|
+
when NONE
|
23
|
+
engine = Codesake::Engine::Generic.new(filename)
|
24
|
+
when JSP
|
25
|
+
engine = Codesake::Engine::Jsp.new(filename, options)
|
26
|
+
end
|
27
|
+
engine
|
28
|
+
end
|
29
|
+
|
30
|
+
def detect(filename)
|
31
|
+
return NONE if filename.nil? or filename.empty?
|
32
|
+
return TEXT if Codesake::Engine::Text.is_txt?(filename)
|
33
|
+
return JSP if (File.extname(filename) == ".jsp")
|
34
|
+
|
35
|
+
|
36
|
+
return UNKNOWN
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Codesake
|
2
|
+
module Utils
|
3
|
+
module Files
|
4
|
+
|
5
|
+
attr_accessor :filename
|
6
|
+
attr_reader :file_content
|
7
|
+
|
8
|
+
def read_file
|
9
|
+
@file_content = []
|
10
|
+
@file_content = File.readlines(@filename) if File.exists?(@filename)
|
11
|
+
end
|
12
|
+
|
13
|
+
def lines
|
14
|
+
@file_content.count
|
15
|
+
end
|
16
|
+
|
17
|
+
def lines_of_comment
|
18
|
+
0
|
19
|
+
end
|
20
|
+
def loc
|
21
|
+
0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Codesake
|
2
|
+
module Utils
|
3
|
+
module Secrets
|
4
|
+
DEFAULT_SECRETS = ["secret", "password", "username", "login", "xxx", "fixme", "fix", "todo", "passwd"]
|
5
|
+
attr_accessor :secrets
|
6
|
+
attr_accessor :reserved_keywords
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
load_secrets
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_secrets
|
13
|
+
@secrets = DEFAULT_SECRETS
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(word)
|
17
|
+
@secrets << word
|
18
|
+
@secrets
|
19
|
+
end
|
20
|
+
|
21
|
+
def reserved?(word)
|
22
|
+
@secrets.include?(word)
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_reserved_keywords
|
26
|
+
|
27
|
+
ret = []
|
28
|
+
|
29
|
+
@file_content.each_with_index do |l, i|
|
30
|
+
l = l.unpack("C*").pack("U*")
|
31
|
+
l.split.each do |tok|
|
32
|
+
# ret << {:line=> i+1, :matcher=>tok, :source_line=>l} if @secrets.include?(tok.downcase)
|
33
|
+
ret << {:line=> i+1, :matcher=>tok } if @secrets.include?(tok.downcase)
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ret
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|