Cybersecurity-Projects/PROJECTS/beginner/deserialization-gadget-lab/test/support/exploit_probe.rb

80 lines
2.2 KiB
Ruby

# ©AngelaMos | 2026
# exploit_probe.rb
# frozen_string_literal: true
require "fileutils"
require "marshalsea"
CANARY_PATH = "/tmp/marshalsea-canary"
CANARY_MARKER = "fired"
RESULT_FIRED = "FIRED"
RESULT_BLOCKED = "BLOCKED"
RESULT_INERT = "INERT"
def canary_present?
File.exist?(CANARY_PATH) && File.read(CANARY_PATH) == CANARY_MARKER
end
def clear_canary
FileUtils.rm_f(CANARY_PATH)
end
def classify(fired, detail)
return RESULT_FIRED if fired
return RESULT_BLOCKED if detail.start_with?("ArgumentError")
RESULT_INERT
end
def observe
clear_canary
detail = begin
yield
"returned"
rescue StandardError => e
"#{e.class}: #{e.message}"
end
[canary_present?, detail]
end
erb_version = Gem::Specification.find_all_by_name("erb").map(&:version).max.to_s
image = ENV.fetch("MATRIX_IMAGE", "?")
chain = Marshalsea::Chains::ErbDefModule.canary(CANARY_PATH, CANARY_MARKER)
primitive = Marshalsea::Chains::ErbDefMethod.canary(CANARY_PATH, CANARY_MARKER)
chain_blob = chain.serialize
primitive_blob = primitive.serialize
built_without_firing = !canary_present?
chain_fired, chain_detail = observe { Marshal.load(chain_blob) }
chain_outcome = classify(chain_fired, chain_detail)
primitive_load_fired, = observe { Marshal.load(primitive_blob) }
primitive_fired, primitive_detail = observe do
Marshal.load(primitive_blob).def_method(Module.new, "marshalsea_probe")
end
primitive_outcome = classify(primitive_fired, primitive_detail)
clear_canary
predicted = Marshalsea::Chains::ErbDefModule.affects?(erb_version) ? RESULT_FIRED : RESULT_BLOCKED
inspection = Marshalsea::Marshal::Parser.new(chain_blob).parse
puts format("%-9s erb=%-9s chain=%-8s primitive=%-8s predicted=%-8s sinks=%d %s",
image, erb_version, chain_outcome, primitive_outcome, predicted,
inspection.sinks.length, chain_detail[0, 60])
checks = {
"builder_did_not_execute_its_own_payload" => built_without_firing,
"chain_matches_prediction" => chain_outcome == predicted,
"primitive_matches_prediction" => primitive_outcome == predicted,
"primitive_is_inert_until_the_application_calls_it" => !primitive_load_fired,
"chain_carries_no_sink_tag" => inspection.sinks.empty?
}
checks.each { |name, ok| puts "#{name}=#{ok}" }
exit(checks.values.all? ? 0 : 1)