Module: WPScan::Target::Platform::PHP

Included in:
Model::WpItem, WordPress
Defined in:
lib/wpscan/target/platform/php.rb

Overview

Some PHP specific implementation

Constant Summary collapse

DEBUG_LOG_PATTERN =
/(?:\[\d{2}-[a-zA-Z]{3}-\d{4}\s\d{2}:\d{2}:\d{2}\s[A-Z]{3}\]|
PHP\s(?:Fatal|Warning|Strict|Error|Notice):)/x
FPD_PATTERN =
/Fatal error:.+? in (.+?) on/
ERROR_LOG_PATTERN =
/PHP Fatal error/i

Instance Method Summary collapse

Instance Method Details

#debug_log?(path, params = {}) ⇒ Boolean

Returns true if url(path) is a debug log, false otherwise.

Parameters:

  • path (String)
  • params (Hash) (defaults to: {})

    The request params

Returns:

  • (Boolean)

    true if url(path) is a debug log, false otherwise



92
93
94
# File 'lib/wpscan/target/platform/php.rb', line 92

def debug_log?(path, params = {})
  log_file?(path, DEBUG_LOG_PATTERN, params)
end

#error_log?(path, params = {}) ⇒ Boolean

Returns Wether or not url(path) is an error log file.

Parameters:

  • path (String)
  • params (Hash) (defaults to: {})

    The request params

Returns:

  • (Boolean)

    Wether or not url(path) is an error log file



100
101
102
# File 'lib/wpscan/target/platform/php.rb', line 100

def error_log?(path, params = {})
  log_file?(path, ERROR_LOG_PATTERN, params)
end

#full_path_disclosure?(path = nil, params = {}) ⇒ Boolean

Returns true if url(path) contains a FPD, false otherwise.

Parameters:

  • path (String) (defaults to: nil)
  • params (Hash) (defaults to: {})

    The request params

Returns:

  • (Boolean)

    true if url(path) contains a FPD, false otherwise



108
109
110
# File 'lib/wpscan/target/platform/php.rb', line 108

def full_path_disclosure?(path = nil, params = {})
  !full_path_disclosure_entries(path, params).empty?
end

#full_path_disclosure_entries(path = nil, params = {}) ⇒ Array<String>

Returns The FPD found, or an empty array if none.

Parameters:

  • path (String) (defaults to: nil)
  • params (Hash) (defaults to: {})

    The request params

Returns:

  • (Array<String>)

    The FPD found, or an empty array if none



116
117
118
119
120
# File 'lib/wpscan/target/platform/php.rb', line 116

def full_path_disclosure_entries(path = nil, params = {})
  res = WPScan::Browser.get(url(path), params)

  res.body.scan(FPD_PATTERN).flatten
end

#install_body_cap(req, body, max_size) ⇒ Object

Installs an on_body callback on ‘req` that appends chunks to `body`. If `max_size` is set, the transfer is aborted once the buffer exceeds it. Returns a callable that reports whether the transfer was aborted.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/wpscan/target/platform/php.rb', line 70

def install_body_cap(req, body, max_size)
  aborted = false

  if max_size
    req.on_body do |chunk|
      body << chunk
      next if body.bytesize <= max_size

      aborted = true
      :abort
    end
  else
    req.on_body { |chunk| body << chunk }
  end

  -> { aborted }
end

#log_file?(path, pattern, params = {}) ⇒ Boolean

Parameters:

  • path (String)
  • pattern (Regexp)
  • params (Hash) (defaults to: {})

    The request params

Returns:

  • (Boolean)


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/wpscan/target/platform/php.rb', line 18

def log_file?(path, pattern, params = {})
  max_mib  = WPScan::ParsedCli.max_log_file_size.to_i
  max_size = max_mib.positive? ? max_mib * 1024 * 1024 : nil

  head_res = WPScan::Browser.forge_request(url(path), head_or_get_params).run
  return false unless head_res.code == 200

  if max_size
    content_length = head_res.headers&.[]('Content-Length').to_i
    return false if content_length > max_size
  end

  body = stream_capped_body(path, params, max_size)
  return false if body.nil?

  body.match?(pattern) || false
end

#stream_capped_body(path, params, max_size) ⇒ String?

Performs a streaming GET of ‘path` and returns the body as a String, capped at `max_size` bytes. Returns nil if the transfer was aborted because it exceeded the cap.

Only the first 700 bytes of the file are needed to match log-file patterns. Servers may ignore the Range header, and libcurl’s maxfilesize is a no-op when the response has no Content-Length (chunked transfer encoding), so an on_body callback is used to abort the transfer once the accumulated body exceeds the configured limit. Streaming via on_body also keeps Typhoeus::Response#body empty, so the response cache cannot Marshal-dump a huge payload.

Parameters:

  • path (String)
  • params (Hash)

    The base request params

  • max_size (Integer, nil)

    Size cap in bytes, or nil to disable

Returns:

  • (String, nil)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/wpscan/target/platform/php.rb', line 51

def stream_capped_body(path, params, max_size)
  get_params = params.merge(
    headers: { 'Range' => 'bytes=0-700' },
    method: :get,
    cache_ttl: 0
  )
  get_params[:maxfilesize] = max_size if max_size

  req  = WPScan::Browser.forge_request(url(path), get_params)
  body = +''
  aborted = install_body_cap(req, body, max_size)
  req.run

  aborted.call ? nil : body
end