Module: PWN::Plugins::Log

Defined in:
lib/pwn/plugins/log.rb

Overview

This plugin is used to instantiate a PWN logger with a custom message format

Constant Summary collapse

TRACE_SKIP_METHODS =
%i[
  authors help to_s inspect class object_id hash eql? == equal? !
  method public_send send __send__ instance_eval class_eval
  public_class_method private_class_method
].freeze
TRACE_SKIP_PREFIXES =
%w[
  PWN::Plugins::Log
  PWN::Plugins::REPL
  PWN::Banner
].freeze
DEFAULT_TRACE_PREFIXES =
%w[
  PWN::AI
  PWN::Memory
  PWN::Sessions
  PWN::Config
  PWN::Cron
  PWN::Plugins
].freeze
SECRET_KEY_RX =
/password|passwd|secret|token|api[_-]?key|authorization|bearer|cookie|session[_-]?id|private[_-]?key|decryptor|credential|ssh[_-]?key|client[_-]?secret|refresh[_-]?token|access[_-]?token|id[_-]?token|vault|csrf/i
SECRET_VALUE_RX =
%r{
  -----BEGIN\ [A-Z ]*PRIVATE\ KEY----- |
  Bearer\s+[A-Za-z0-9\-._~+/]+=* |
  \beyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+ |
  \b(?:sk|rk|pk|xai|xox[baprs]|ghp|gho|ghu|ghs|ghr|glpat|AKIA|ASIA|ya29)[-_][A-Za-z0-9\-_]{16,} |
  \b(?:api[_-]?key|token|secret|password|passwd)\s*[:=]\s*\S+
}ix
DEBUG_VALUE_MAX =
240
DEBUG_ARGS_MAX =
1_800
DEBUG_LOG_MAX =

Open /tmp/pwn-ai-DEBUG-TIMESTAMP.log, tee the same lines to the TUI (opts), and TracePoint PWN modules that process a pwn-ai request.

1_024_000

Class Method Summary collapse

Class Method Details

.append(opts = {}) ⇒ Object

Supported Method Parameters

PWN::Log.create( )



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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
# File 'lib/pwn/plugins/log.rb', line 44

public_class_method def self.append(opts = {})
  level = opts[:level].to_s.downcase.to_sym
  msg = opts[:msg]
  which_self = opts[:which_self].to_s

  driver_name = File.basename($PROGRAM_NAME)

  # Only attempt to exit gracefully if level == :error
  exit_gracefully = false

  # Define Date / Time Format
  datetime_str = '%Y-%m-%d %H:%M:%S.%N%z'

  # Always append to log file
  if level == :learning
    session = SecureRandom.hex
    log_file_path = "/tmp/pwn-ai-#{session}.json" if level == :learning
    log_file = File.open(log_file_path, 'w')
  else
    log_file_path = '/tmp/pwn.log'
    log_file = File.open(log_file_path, 'a')
  end

  # Leave 10 "old" log files where
  # each file is ~ 1,024,000 bytes
  logger = Logger.new(
    log_file,
    10,
    1_024_000
  )
  logger.datetime_format = datetime_str

  case level
  when :debug
    logger.level = Logger::DEBUG
  when :error
    logger.level = Logger::ERROR
    exit_gracefully = true unless driver_name == 'pwn'
    puts "\nERROR: See #{log_file_path} for more details." if driver_name == 'pwn'
  when :fatal
    logger.level = Logger::FATAL
    puts "\n FATAL ERROR: See #{log_file_path} for more details." if driver_name == 'pwn'
  when :info, :learning
    logger.level = Logger::INFO
  when :unknown
    logger.level = Logger::UNKNOWN
  when :warn
    logger.level = Logger::WARN
  else
    level_error = "ERROR: Invalid log level. Valid options are:\n"
    level_error += ":debug\n:error\n:fatal\n:info\n:learning\n:unknown\n:warn\n"
    raise level_error
  end

  if level == :learning
    log_event = msg
    logger.formatter = proc do |_severity, _datetime, _progname, learning_arr|
      JSON.pretty_generate(
        learning_data: learning_arr
      )
    end
  else
    log_event = "driver: #{driver_name}"

    if msg.instance_of?(Interrupt)
      logger.level = Logger::WARN
      if driver_name == 'pwn'
        log_event += ' => CTRL+C Detected.'
      else
        log_event += ' => CTRL+C Detected...Exiting Session.'
        exit_gracefully = true unless driver_name == 'pwn'
      end
    else
      log_event += " => #{msg}"
      if msg.respond_to?('backtrace') && !msg.instance_of?(Errno::ECONNRESET)
        log_event += " => \n\t#{msg.backtrace.join("\n\t")}"
        log_event += "\n\n\n"
      end
    end
  end

  logger.add(logger.level, log_event, which_self)
rescue Interrupt
  puts "\n#{self}.#{__method__} => Goodbye."
rescue StandardError => e
  raise e
end

.authorsObject

Author(s)

0day Inc. support@0dayinc.com



399
400
401
402
403
# File 'lib/pwn/plugins/log.rb', line 399

public_class_method def self.authors
  "AUTHOR(S):
    0day Inc. <support@0dayinc.com>
  "
end

.debug_enabled?(opts = {}) ⇒ Boolean

Returns:

  • (Boolean)


132
133
134
135
136
# File 'lib/pwn/plugins/log.rb', line 132

public_class_method def self.debug_enabled?(opts = {})
  return @debug_enabled == true if opts.is_a?(Hash)

  @debug_enabled == true
end

.debug_log_path(opts = {}) ⇒ Object



138
139
140
141
142
# File 'lib/pwn/plugins/log.rb', line 138

public_class_method def self.debug_log_path(opts = {})
  return @debug_path if opts.is_a?(Hash)

  @debug_path
end

.helpObject

Display Usage for this Module



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/pwn/plugins/log.rb', line 407

public_class_method def self.help
  puts "USAGE:
    logger = #{self}.append(
      level: 'required - log verbosity :debug|:error|:fatal|:info|:learning|:unknown|:warn',
      msg: 'required - message to log',
      which_self: 'required - pass in self object from module calling #{self}'
    )

    path = #{self}.start_debug(
      tee: $stdout,
      path: 'optional - defaults to /tmp/pwn-ai-DEBUG-TIMESTAMP.log'
    )
    #{self}.progress(msg: 'stage', which_self: self)
    #{self}.stop_debug
  "
end

.loud_tui!(opts = {}) ⇒ Object



222
223
224
225
226
# File 'lib/pwn/plugins/log.rb', line 222

public_class_method def self.loud_tui!(opts = {})
  return if opts[:skip]

  @debug_tui_quiet = false
end

.progress(opts = {}) ⇒ Object

One progress line to the debug file and the TUI tee (same payload).



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/pwn/plugins/log.rb', line 229

public_class_method def self.progress(opts = {})
  return false unless debug_enabled?
  return false if Thread.current[:pwn_log_progress]

  Thread.current[:pwn_log_progress] = true
  msg = sanitize_debug_text(text: opts[:msg].to_s)
  which = opts[:which_self] || self
  line = format_progress(msg: msg, which_self: which)
  begin
    @debug_file&.puts(line)
    @debug_file&.flush
    roll_debug_log!
  rescue StandardError
    nil
  end
  tee = opts.key?(:tee) ? opts[:tee] : @debug_tee
  if !@debug_tui_quiet && tee.respond_to?(:puts)
    begin
      tee.puts(color_progress(line: line))
    rescue StandardError
      nil
    end
  end
  true
ensure
  Thread.current[:pwn_log_progress] = false
end

.quiet_tui!(opts = {}) ⇒ Object



216
217
218
219
220
# File 'lib/pwn/plugins/log.rb', line 216

public_class_method def self.quiet_tui!(opts = {})
  return if opts[:skip]

  @debug_tui_quiet = true
end

.start_debug(opts = {}) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/pwn/plugins/log.rb', line 148

public_class_method def self.start_debug(opts = {})
  return @debug_path if debug_enabled? && opts.is_a?(Hash)

  ts = Time.now.strftime('%Y%m%d-%H%M%S')
  path = opts[:path].to_s
  path = "/tmp/pwn-ai-DEBUG-#{ts}.1.log" if path.empty?
  stem, idx = debug_path_parts(path: path)
  path = "#{stem}.#{idx}.log"
  FileUtils.mkdir_p(File.dirname(path))
  io = File.open(path, 'a')
  io.sync = true
  @debug_file = io
  @debug_path = path
  @debug_stem = stem
  @debug_index = idx
  @debug_tee = opts[:tee]
  @debug_tui_quiet = false
  @debug_enabled = true
  start_trace!(prefixes: opts[:prefixes]) unless opts[:trace] == false
  progress(msg: "debug session start path=#{path}", which_self: self)
  path
end

.start_trace!(opts = {}) ⇒ Object



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/pwn/plugins/log.rb', line 370

public_class_method def self.start_trace!(opts = {})
  return @debug_tp if @debug_tp && opts.is_a?(Hash)

  prefixes = Array(opts[:prefixes] || DEFAULT_TRACE_PREFIXES).map(&:to_s)
  prefixes = DEFAULT_TRACE_PREFIXES if prefixes.empty?
  @debug_tp = TracePoint.new(:call) do |tp|
    next unless debug_enabled?
    next unless traceable?(tp: tp, prefixes: prefixes)

    progress(
      msg: format_trace_call(tp: tp),
      which_self: ''
    )
  rescue StandardError
    nil
  end
  @debug_tp.enable
  @debug_tp
end

.stop_debug(opts = {}) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/pwn/plugins/log.rb', line 171

public_class_method def self.stop_debug(opts = {})
  reason = opts[:reason].to_s if opts.is_a?(Hash)
  if debug_enabled?
    tail = reason.to_s.empty? ? 'debug session stop' : "debug session stop reason=#{reason}"
    progress(msg: tail, which_self: self)
  end
  stop_trace!
  begin
    @debug_file&.close unless @debug_file.nil? || @debug_file.closed?
  rescue StandardError
    nil
  end
  path = @debug_path
  @debug_file = nil
  @debug_path = nil
  @debug_stem = nil
  @debug_index = nil
  @debug_tee = nil
  @debug_tui_quiet = false
  @debug_enabled = false
  path
end

.stop_trace!(opts = {}) ⇒ Object



390
391
392
393
394
395
# File 'lib/pwn/plugins/log.rb', line 390

public_class_method def self.stop_trace!(opts = {})
  return if opts[:skip]

  @debug_tp&.disable
  @debug_tp = nil
end