Cybersecurity-Projects/PROJECTS/beginner/deserialization-gadget-lab/scripts/render_matrix.rb

95 lines
2.9 KiB
Ruby

# ©AngelaMos | 2026
# render_matrix.rb
require "json"
require "rube"
TRACKED_CLASSES = %w[Gem::SpecFetcher Gem::Source::Git Gem::URI Net::WriteAdapter].freeze
MARK_YES = "yes"
MARK_NO = "no"
MARK_UNKNOWN = "?"
RULE_WIDTH = 78
CHAIN = Rube::Chains::ErbDefMethod
def cve_patched?(version)
!CHAIN.affects?(version)
end
rows = File.readlines(ARGV.fetch(0)).reject { |line| line.strip.empty? }.map { |line| JSON.parse(line) }
abort "no probe results" if rows.empty?
def mark(value)
case value
when true then MARK_YES
when false then MARK_NO
else MARK_UNKNOWN
end
end
def short(image)
image.sub("ruby:", "")
end
def section(title)
puts title
puts " #{'-' * RULE_WIDTH}"
yield
puts
end
section("RUNTIME") do
puts format(" %-14s %-8s %-9s %-8s %-9s %-7s", "image", "ruby", "rubygems", "psych", "erb", "marshal")
rows.each do |r|
puts format(" %-14s %-8s %-9s %-8s %-9s %-7s",
short(r["image"]), r["ruby"], r["rubygems"], r["psych"], r["erb"], r["marshal_format"])
end
end
section("GADGET SURFACE") do
puts format(" %-14s %-12s %-14s %s",
"image", "git gadget", "safe_marshal", TRACKED_CLASSES.map { |c| format("%-13s", c.split("::").last) }.join)
rows.each do |r|
present = TRACKED_CLASSES.map { |c| format("%-13s", mark(r["classes_baseline"][c])) }.join
puts format(" %-14s %-12s %-14s %s", short(r["image"]), r["git_gadget"], r["safe_marshal"], present)
end
end
section("ERB @_init GUARD (CVE-2026-41316), anchor = def_method") do
puts format(" %-14s %-9s %-9s %-24s %s", "image", "erb", "guarded", "delegating", "cve says")
rows.each do |r|
guard = r["erb_guard"]
expected = cve_patched?(r["erb"]) ? "patched" : "affected"
puts format(" %-14s %-9s %-9s %-24s %s",
short(r["image"]), r["erb"], mark(guard["guarded"]),
guard["delegating"].join(","), expected)
end
end
git_states = rows.map { |r| r["git_gadget"] }.uniq
guard_states = rows.map { |r| r["erb_guard"]["guarded"] }.uniq
agreements = rows.map do |r|
[short(r["image"]), r["erb_guard"]["guarded"] == cve_patched?(r["erb"])]
end
disagreements = agreements.reject { |_, ok| ok }.map(&:first)
section("CONTROLS") do
puts format(" git gadget states observed : %s", git_states.join(", "))
puts format(" erb guard states observed : %s", guard_states.map { |v| mark(v) }.join(", "))
puts format(" guard vs published CVE : %d/%d agree", agreements.count { |_, ok| ok }, agreements.length)
end
failures = []
failures << "git gadget reported '#{git_states.first}' on every image" if git_states.length < 2
failures << "erb guard reported the same value on every image" if guard_states.length < 2
failures << "guard disagrees with the CVE ranges on: #{disagreements.join(', ')}" unless disagreements.empty?
if failures.empty?
puts "PASS - the matrix discriminates on both axes and matches the published CVE ranges"
exit 0
end
failures.each { |f| puts "FAIL - #{f}" }
exit 1