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.
@@ -0,0 +1 @@
1
+ require 'aruba/cucumber'
@@ -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
@@ -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,10 @@
1
+ module Codesake
2
+ module Engine
3
+ module Core
4
+ def analyse
5
+ []
6
+ end
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ module Codesake
2
+ module Engine
3
+ class Generic
4
+ include Codesake::Utils::Files
5
+
6
+ def initialize(filename)
7
+ @filename = filename
8
+ end
9
+
10
+ end
11
+ end
12
+ 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