Module: NewRelic::Security::Agent::Utils

Extended by:
Utils
Included in:
Utils
Defined in:
lib/newrelic_security/agent/utils/agent_utils.rb

Constant Summary collapse

ENABLED =
'enabled'
VULNERABLE =
'VULNERABLE'
AES_256_CBC =
'AES-256-CBC'
H_ASTERIK =
'H*'
ASTERISK =
'*'

Instance Method Summary collapse

Instance Method Details

#app_port(env) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 149

def app_port(env)
  listen_port = nil
  if env.key?('puma.socket')
    NewRelic::Security::Agent.config.app_server = :puma
    if env['puma.socket'].is_a?(TCPSocket)
      listen_port = env['puma.socket'].addr[1]
      NewRelic::Security::Agent.logger.debug "Detected port from puma.socket TCPSocket : #{listen_port}"
    elsif env['puma.socket'].is_a?(Puma::MiniSSL::Socket)
        listen_port = env['puma.socket'].instance_variable_get(:@socket).addr[1]
        NewRelic::Security::Agent.logger.debug "Detected port from puma.socket Puma::MiniSSL::Socket TCPSocket : #{listen_port}"
    end
  end
  if env.key?('unicorn.socket') && env['unicorn.socket'].is_a?(::Unicorn::TCPClient)
    NewRelic::Security::Agent.config.app_server = :unicorn
    listen_port, _ = ::Socket.unpack_sockaddr_in(env['unicorn.socket'].getsockname)
    NewRelic::Security::Agent.logger.debug "Detected port from unicorn.socket Unicorn::TCPClient : #{listen_port}"
  end
  ObjectSpace.each_object(::Falcon::Server) { |z|
    NewRelic::Security::Agent.config.app_server = :falcon
    listen_port = z.endpoint.instance_variable_get(:@specification)[1]
    NewRelic::Security::Agent.logger.debug "Detected port from Falcon::Server : #{listen_port}"
  } if defined?(::Falcon::Server)
  ObjectSpace.each_object(::Thin::Backends::TcpServer) { |z|
    NewRelic::Security::Agent.config.app_server = :thin
    listen_port = z.instance_variable_get(:@port)
    NewRelic::Security::Agent.logger.debug "Detected port from Thin::Backends::TcpServer : #{listen_port}"
  } if defined?(::Thin::Backends::TcpServer)
  ObjectSpace.each_object(::WEBrick::GenericServer) { |z|
    NewRelic::Security::Agent.config.app_server = :webrick
    listen_port = z.instance_variable_get(:@config)[:Port]
    NewRelic::Security::Agent.logger.debug "Detected port from WEBrick::GenericServer : #{listen_port}"
  } if defined?(::WEBrick::GenericServer)
  if NewRelic::Security::Agent.config[:'security.application_info.port'] != 0
    listen_port = NewRelic::Security::Agent.config[:'security.application_info.port']
    NewRelic::Security::Agent.logger.info "Using application listen port from newrelic.yml security.application_info.port : #{listen_port}"
  end
  if listen_port
    NewRelic::Security::Agent.logger.info "Detected application listen_port : #{listen_port}"
  else
    NewRelic::Security::Agent.logger.warn "Unable to detect application listen port, IAST can not run without application listen port. Please provide application listen port in security.application_info.port in newrelic.yml"
  end
  disable_object_space_in_jruby if NewRelic::Security::Agent.config[:jruby_objectspace_enabled]
  listen_port
rescue Exception => exception
  NewRelic::Security::Agent.logger.error "Exception in port detection : #{exception.inspect} #{exception.backtrace}"
end

#app_rootObject



196
197
198
199
200
201
202
203
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 196

def app_root
  #so far assuming it as Rails
  #TBD, determing the frame work then use appropriate APIs
  #val = Rails.root
  root = nil
  root = ::Rack::Directory.new(EMPTY_STRING).root.to_s if defined? ::Rack::Directory
  root
end

#create_exit_event(event) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 80

def create_exit_event(event)
  return unless event
  return unless is_IAST?
  return unless is_IAST_request?(event.httpRequest[:headers])
  if event.httpRequest[:headers][NR_CSEC_FUZZ_REQUEST_ID].include?(event.apiId) && event.httpRequest[:headers][NR_CSEC_FUZZ_REQUEST_ID].include?(VULNERABLE)
    exit_event = NewRelic::Security::Agent::Control::ExitEvent.new
    exit_event.executionId = event.id
    exit_event.caseType = event.caseType
    exit_event.k2RequestIdentifier = event.httpRequest[:headers][NR_CSEC_FUZZ_REQUEST_ID]
    NewRelic::Security::Agent.agent.event_processor.send_exit_event(exit_event)
  end
rescue Exception => exception
  NewRelic::Security::Agent.logger.error "Exception in create_exit_event: #{exception.inspect} #{exception.backtrace}"
  NewRelic::Security::Agent.agent.exit_event_stats.error_count.increment
end

#decrypt_data(name, sha) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 54

def decrypt_data(name, sha)
  cipher = ::OpenSSL::Cipher.new AES_256_CBC
  cipher.decrypt
  cipher.key = NewRelic::Security::Agent.config[:extraction_key]
  decrypted = cipher.update [name].pack(H_ASTERIK)
  decrypted << cipher.final
  fname = decrypted[16..-1]
  return fname if ::Digest::SHA256.hexdigest(fname) == sha
  nil
rescue Exception => exception
  NewRelic::Security::Agent.logger.error "Exception in decrypt_data: #{exception.inspect} #{exception.backtrace}"
end

#delete_created_files(ctxt) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 67

def delete_created_files(ctxt)
  return unless ctxt
  headers = ctxt.headers
  if is_IAST? && is_IAST_request?(headers)
    ctxt.fuzz_files.each do |file|
      begin
        ::File.delete(file)
      rescue
      end
    end
  end
end

#disable_object_space_in_jrubyObject



212
213
214
215
216
217
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 212

def disable_object_space_in_jruby
  if RUBY_ENGINE == 'jruby' && JRuby.respond_to?(:objectspace) && JRuby.objectspace
    JRuby.objectspace = false
    NewRelic::Security::Agent.config.jruby_objectspace_enabled = false
  end
end

#enable_object_space_in_jrubyObject



205
206
207
208
209
210
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 205

def enable_object_space_in_jruby
  if RUBY_ENGINE == 'jruby' && JRuby.respond_to?(:objectspace) && !JRuby.objectspace
    JRuby.objectspace = true
    NewRelic::Security::Agent.config.jruby_objectspace_enabled = true
  end
end

#filtered_log(log) ⇒ Object



223
224
225
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 223

def filtered_log(log)
  log.gsub(license_key, ASTERISK * license_key.size)
end

#get_app_routes(framework, router = nil) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 96

def get_app_routes(framework, router = nil)
  enable_object_space_in_jruby
  case framework
  when :rails
    ::Rails.application.routes.routes.each do |route|
      if route.verb.is_a?(::Regexp)
        method = route.verb.inspect.match(/[a-zA-Z]+/)
        NewRelic::Security::Agent.agent.route_map << "#{method}@#{route.path.spec.to_s.gsub(/\(\.:format\)/, "")}" if method
      else
        route.verb.split("|").each { |m|
          NewRelic::Security::Agent.agent.route_map << "#{m}@#{route.path.spec.to_s.gsub(/\(\.:format\)/, "")}"
        }
      end
    end
  when :sinatra
    ::Sinatra::Application.routes.each do |method, routes|
      routes.map { |r| r.first.to_s }.map do |route|
        NewRelic::Security::Agent.agent.route_map << "#{method}@#{route}"
      end
    end
  when :grape
    if defined?(::Grape::API)
      ObjectSpace.each_object(Class).select { |klass| klass < ::Grape::API }.each do |api_class|
        api_class.routes.each do |route|
          http_method = route.request_method || route.options[:method]
          NewRelic::Security::Agent.agent.route_map << "#{http_method}@#{route.pattern.origin}"
        end
      end
    end
  when :padrino
    if router.instance_of?(::Padrino::PathRouter::Router)
      router.instance_variable_get(:@routes).each do |route|
        NewRelic::Security::Agent.agent.route_map << "#{route.instance_variable_get(:@verb)}@#{route.matcher.instance_variable_get(:@path)}"
      end
    end
  when :roda
    NewRelic::Security::Agent.logger.debug "TODO: Roda is a routing tree web toolkit, which generates route dynamically, hence route extraction is not possible."
  when :grpc
    router.owner.superclass.public_instance_methods(false).each do |m|
      NewRelic::Security::Agent.agent.route_map << "*@/#{router.owner}/#{m}"
    end
  when :rack
    # TODO: API enpointes(routes) extraction for rack
  else
    NewRelic::Security::Agent.logger.error "Unable to get app routes as Framework not detected"
  end
  disable_object_space_in_jruby if NewRelic::Security::Agent.config[:jruby_objectspace_enabled]
  NewRelic::Security::Agent.logger.debug "ALL ROUTES : #{NewRelic::Security::Agent.agent.route_map}"
  NewRelic::Security::Agent.agent.event_processor&.send_application_url_mappings unless NewRelic::Security::Agent.agent.route_map.empty?
rescue Exception => exception
  NewRelic::Security::Agent.logger.error "Error in get app routes : #{exception.inspect} #{exception.backtrace}"
end

#is_IAST?Boolean

Returns:

  • (Boolean)


17
18
19
20
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 17

def is_IAST?
  return true if NewRelic::Security::Agent.config[:mode] == IAST
  false
end

#is_IAST_request?(headers) ⇒ Boolean

Returns:

  • (Boolean)


22
23
24
25
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 22

def is_IAST_request?(headers)
  return true if headers&.key?(NR_CSEC_FUZZ_REQUEST_ID)
  false
end

#license_keyObject



219
220
221
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 219

def license_key
  NewRelic::Security::Agent.config[:license_key]
end

#parse_fuzz_header(ctxt) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/newrelic_security/agent/utils/agent_utils.rb', line 27

def parse_fuzz_header(ctxt)
  headers = ctxt.headers if ctxt
  if is_IAST? && is_IAST_request?(headers)
    fuzz_request = headers[NR_CSEC_FUZZ_REQUEST_ID].split(COLON_IAST_COLON)
    if fuzz_request.length() >= 7
      decrypted_data = decrypt_data(fuzz_request[6], fuzz_request[7]) if fuzz_request[6] && fuzz_request[7] && !fuzz_request[6].empty? && !fuzz_request[7].empty?
      if decrypted_data
        NewRelic::Security::Agent.logger.debug "Encrypted data: #{fuzz_request[6]},  decrypted data: #{decrypted_data}, Sha256: #{fuzz_request[7]}"
        decrypted_data.split(COMMA).each do |filename|
          begin
            filename.gsub!(NR_CSEC_VALIDATOR_HOME_TMP, NewRelic::Security::Agent.config[:fuzz_dir_path])
            filename.gsub!(NR_CSEC_VALIDATOR_HOME_TMP_URL_ENCODED, NewRelic::Security::Agent.config[:fuzz_dir_path])
            filename.gsub!(NR_CSEC_VALIDATOR_FILE_SEPARATOR, ::File::SEPARATOR)
            dirname = ::File.dirname(filename)
            ::FileUtils.mkdir_p(dirname, :mode => 0770) unless ::File.directory?(dirname)
            ctxt&.fuzz_files&.<< filename
            ::File.open(filename, ::File::WRONLY | ::File::CREAT | ::File::EXCL) do |fd|
                # puts "Ownership acquired by : #{Process.pid}"
            end unless ::File.exist?(filename)
          rescue
          end
        end
      end
    end
  end
end