Class: Msf::DBManager::Import::MarshalValidator

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/db_manager/import/marshal_validator.rb

Overview

Walks a Marshal byte stream structurally, reading type bytes only in type positions and skipping over data payloads. Rejects any stream that attempts to instantiate a named class (object, struct, custom marshal, module extension, etc.).

This runs BEFORE Marshal.load so no objects are ever instantiated from an unsafe payload.

Reference: https://ruby-doc.org/3.3/Marshal.html Reference: https://github.com/ruby/ruby/blob/master/doc/marshal/marshal.md

Constant Summary collapse

MarshalValidationError =
Msf::DBManager::Import::MarshalValidationError
UNSAFE_TYPES =

Type bytes that always instantiate named classes — unconditionally blocked.

Set.new(%w[o c m C S e U d].map(&:ord)).freeze
DEFAULT_PERMITTED_CLASSES =

Default classes permitted for the 'u' (_dump/_load) serialization type.

%w[].freeze
MAX_NESTING_DEPTH =

Prevent deeply nested input from exhausting the Ruby call stack while the validator recursively walks compound values.

512

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data, permitted_classes: DEFAULT_PERMITTED_CLASSES) ⇒ MarshalValidator

Returns a new instance of MarshalValidator.

Parameters:

  • data (String) —

    raw Marshal binary data

  • permitted_classes (Array<String>) (defaults to: DEFAULT_PERMITTED_CLASSES) —

    class names allowed for _dump/_load ('u') deserialization. Defaults to DEFAULT_PERMITTED_CLASSES.



38
39
40
41
42
43
# File 'lib/msf/core/db_manager/import/marshal_validator.rb', line 38

def initialize(data, permitted_classes: DEFAULT_PERMITTED_CLASSES)
  @bytes = String.new(data).bytes
  @pos = 0
  @object_count = 0
  @permitted_classes = Set.new(permitted_classes)
end

Class Method Details

.marshalled_data?(data) ⇒ Boolean

Check whether the given data starts with the Marshal 4.8 version header, indicating it is a Marshal-serialized payload.

Parameters:

  • data (String) —

    raw binary data

Returns:

  • (Boolean)


73
74
75
# File 'lib/msf/core/db_manager/import/marshal_validator.rb', line 73

def self.marshalled_data?(data)
  data.length >= 2 && data.getbyte(0) == 4 && data.getbyte(1) == 8
end

.safe_load(data, permitted_classes: DEFAULT_PERMITTED_CLASSES) ⇒ Object

Convenience method: validate and then load.

Parameters:

  • data (String) —

    raw Marshal binary data

  • permitted_classes (Array<String>) (defaults to: DEFAULT_PERMITTED_CLASSES) —

    class names allowed for _dump/_load ('u') deserialization. Defaults to DEFAULT_PERMITTED_CLASSES.

Returns:

  • (Object) —

    the deserialized object (only primitives + permitted classes)

Raises:



59
60
61
62
63
64
65
66
# File 'lib/msf/core/db_manager/import/marshal_validator.rb', line 59

def self.safe_load(data, permitted_classes: DEFAULT_PERMITTED_CLASSES)
  # Validate and load the same plain String snapshot. In particular, do
  # not let a String subclass supply different data through #bytes, and
  # do not leave a window in which another thread can mutate the input.
  payload = String.new(data).freeze
  new(payload, permitted_classes: permitted_classes).validate!
  Marshal.load(payload) # rubocop:disable Security/MarshalLoad -- payload has been structurally validated above
end

Instance Method Details

#validate! ⇒ true

Validate the entire stream. Raises MarshalValidationError if unsafe.

Returns:

  • (true)


47
48
49
50
51
# File 'lib/msf/core/db_manager/import/marshal_validator.rb', line 47

def validate!
  read_version
  validate_value
  true
end