Cybersecurity-Projects/PROJECTS/beginner/deserialization-gadget-lab/scripts/exploit-gate.sh

107 lines
3.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# ©AngelaMos | 2026
# exploit-gate.sh
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VULNERABLE_IMAGE="ruby:4.0.2-slim"
PATCHED_IMAGE="ruby:4.0.6-slim"
VULNERABLE_TAG="marshalsea-chain:vulnerable"
PATCHED_TAG="marshalsea-chain:patched"
build_probe_image() {
docker build -q \
--build-arg "RUBY_IMAGE=$1" \
-f "${HERE}/target/chain.Dockerfile" \
-t "$2" "${HERE}" >/dev/null
}
run_probe() {
local tag="$1"
local label="$2"
docker run --rm \
--network none \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=1m \
--user nobody \
-e "MATRIX_IMAGE=${label}" \
-v "${HERE}/lib:/app/lib:ro" \
-v "${HERE}/test/support/exploit_probe.rb:/app/probe.rb:ro" \
-w /app \
"${tag}" ruby -Ilib /app/probe.rb 2>&1
}
echo "CVE-2026-41316 exploit gate"
echo
echo "building probe images"
build_probe_image "${VULNERABLE_IMAGE}" "${VULNERABLE_TAG}" || {
echo "FAIL could not build the vulnerable probe image"
exit 1
}
build_probe_image "${PATCHED_IMAGE}" "${PATCHED_TAG}" || {
echo "FAIL could not build the patched probe image"
exit 1
}
echo
vulnerable_output="$(run_probe "${VULNERABLE_TAG}" "${VULNERABLE_IMAGE#ruby:}")"
vulnerable_status=$?
echo "${vulnerable_output}" | sed 's/^/ /'
patched_output="$(run_probe "${PATCHED_TAG}" "${PATCHED_IMAGE#ruby:}")"
patched_status=$?
echo "${patched_output}" | sed 's/^/ /'
echo
failures=0
expect_line() {
local output="$1" key="$2" want="$3" message="$4"
if echo "${output}" | grep -qx "${key}=${want}"; then
echo " PASS ${message}"
else
echo " FAIL ${message}"
failures=$((failures + 1))
fi
}
if [[ "${vulnerable_output}" == *"chain=FIRED"* ]]; then
echo " PASS the chain executes from Marshal.load alone on the vulnerable image"
else
echo " FAIL the chain did not execute on the vulnerable image"
failures=$((failures + 1))
fi
if [[ "${patched_output}" == *"chain=BLOCKED"* ]]; then
echo " PASS the patched image blocks the same chain"
else
echo " FAIL the patched image did not block the chain"
failures=$((failures + 1))
fi
expect_line "${vulnerable_output}" builder_did_not_execute_its_own_payload true \
"building the payload does not run it in the builder's own process"
expect_line "${vulnerable_output}" primitive_is_inert_until_the_application_calls_it true \
"the primitive is inert on load and needs an application call, unlike the chain"
expect_line "${vulnerable_output}" chain_carries_no_sink_tag true \
"the chain carries no sink tag, so only the class rules can catch it"
if [[ "${vulnerable_status}" -eq 0 && "${patched_status}" -eq 0 ]]; then
echo " PASS every observed outcome matched the chain metadata prediction"
else
echo " FAIL an observed outcome contradicted the chain metadata prediction"
failures=$((failures + 1))
fi
echo
if [[ ${failures} -eq 0 ]]; then
echo "GATE PASSED"
exit 0
fi
echo "GATE FAILED (${failures})"
exit 1