diff --git a/src/pentesting-web/deserialization/README.md b/src/pentesting-web/deserialization/README.md
index 99e46f67273..e89acdd71af 100644
--- a/src/pentesting-web/deserialization/README.md
+++ b/src/pentesting-web/deserialization/README.md
@@ -1174,6 +1174,38 @@ Using the arbitrary file write vulnerability, the attacker writes the crafted ca
Treat any path where untrusted bytes reach `Marshal.load`/`marshal_load` as an RCE sink. Marshal reconstructs arbitrary object graphs and triggers library/gem callbacks during materialization.[[35]](#references)
+
+#### Ruby 3.3–4.0 universal RubyGems chain
+
+A dependency-free chain demonstrated on Ruby `4.0.6` (and reported to work unchanged on `3.3` through `4.0.6`) uses RubyGems code shipped with Ruby. The exact chain needs outbound HTTPS access and a writable destination such as `/tmp`; command execution has the Ruby process privileges and is **not** a local privilege escalation.[[53]](#references)
+
+The serialized graph is ordered so that the download/write side effect completes before a later `Hash`-key callback evaluates the downloaded Ruby source:[[53]](#references)
+
+1. Resolving `Gem::SpecFetcher` near the start of `Marshal.load` activates RubyGems autoloading and makes later gadget classes available even in a bare process.
+2. A forged `Time` stores a `Gem::URI::Generic` object as its zone. During `Time._load`, `StringValueCStr` calls `to_str`; `Gem::URI::Generic#to_str` aliases `to_s`, which calls `@port.to_s` on an embedded `Gem::RequestSet::Lockfile` downloader.
+3. The downloader uses an `s3` URI to reach signing code that emits an HTTPS URL. Traversal components placed in the URI's attacker-controlled port-derived path escape the RubyGems cache, while `Gem::Util.inflate` converts the remote `.rz` response into `/tmp/quick/Marshal.4.8/name-.gemspec`.
+4. RubyGems then tries to parse that source file as serialized specification data and raises. This is useful because the file write already happened, and `time_mload` validates the zone inside `rb_rescue`, suppressing the post-write exception so graph reconstruction continues.
+5. A `Gem::StubSpecification` with `@loaded_from` set to the written path is restored as a `Hash` key. Hash reconstruction invokes its `hash` method, which reaches `Gem::Specification.load(@loaded_from)`; that method reads the file and evaluates it as Ruby.
+
+The hosted response must be a deflated Ruby program. Make its last expression a valid `Gem::Specification` so the subsequent `name`, `version`, and `platform` accesses succeed and `Marshal.load` can return without the otherwise expected warning/exception:[[53]](#references)
+
+```bash
+cat > payload.rb <<'RUBY'
+puts `id`
+Gem::Specification.new do |s|
+ s.name = "poc"
+ s.version = "1.0.0"
+end
+RUBY
+ruby -rrubygems -e 'File.binwrite("poc-id.rz", Gem.deflate(File.binread("payload.rb")))'
+```
+
+`Marshal.dump` cannot normally produce a `Time` with an arbitrary object as its zone because `Time#_dump` serializes native state. The generator therefore dumps a placeholder object and replaces its bytes with a `TYPE_USERDEF` (`u`) `Time` entry wrapped by `TYPE_IVAR` (`I`), leaving the already serialized zone gadget after it. Keep the same number and order of symbol definitions on both sides of the replacement because later `TYPE_SYMLINK` entries are positional. Also stub `Gem::StubSpecification#hash` while building the generator-side `Hash`; otherwise the key gadget fires before serialization.[[53]](#references)
+
+This chain also shows why partially broken gadgets should be retested rather than discarded: RubyGems type checking removed the older `Gem::Version` `to_s` wrapper, and moving Git executable names out of attacker-restorable instance variables removed the older execution sink, but the surviving fetch/write primitive was reusable behind new trigger and evaluation gadgets.[[53]](#references)
+
+Specific hunting indicators include Marshal blobs naming `Gem::SpecFetcher`, `Time`, `Gem::URI::Generic`, `Gem::RequestSet::Lockfile`, and `Gem::StubSpecification`; HTTPS egress during deserialization; unexpected files below `/tmp/quick/Marshal.4.8/`; or stack traces containing `Time._load`, `Gem::StubSpecification#hash`, and `Gem::Specification.load`.[[53]](#references)
+
`Gem::SafeMarshal` was introduced to deserialize a restricted set of RubyGems objects, but the exact version still matters. Research against the Ruby 3.4 prerelease implementation showed that allowlisted `Date` and `Time` object paths could escape the restrictions and re-enter unrestricted `Marshal.load`; consequently, `SafeMarshal` is defense in depth, not a substitute for preventing attacker-controlled bytes from reaching a deserializer.[[47]](#references)[[48]](#references) The RubyGems 3.6.2 integration was merged into Ruby immediately before the Ruby 3.4 final release.[[49]](#references)
@@ -1266,5 +1298,6 @@ Industrialized gadget discovery:
- [50] [NCC Group – Freddy](https://github.com/nccgroup/freddy)
- [51] [es.slideshare.net - Java Deserialization Vulnerabilities The Forgotten Bug Class](https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1)
- [52] [alphabot.com - Fastjson Exceptional Deserialization Vulnerabilities](https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html)
+- [53] [elttam – Ruby 4.0 Universal RCE Deserialization Gadget Chain](https://elttam.com/blog/ruby-4-0-universal-rce-deserialization-gadget-chain)
{{#include ../../banners/hacktricks-training.md}}