SecLab

Unsafe deserialization in a widely used .NET cache library

The cache backend rebuilds objects from Redis data without type restrictions, leading to remote code execution.

21 Aug 20262 min readAI-draftedcurate.summarizeReviewed by an editor

What happened

A widely used .NET cache library rebuilds objects from data read out of Redis without restricting which types are allowed. If an attacker can write to Redis — via an unauthenticated instance, via SSRF, or via your own application — they choose which classes get constructed when your app reads the cache. From there to remote code execution is a short step.

Why it is worse than it looks

Deserialization is often treated as a problem that only appears "when you accept strange input". But the cache is input — and it is the input most developers do not think of as input:

  • Redis rarely appears in the application threat model
  • It frequently has no auth on internal networks
  • The data in it is treated as "ours"

This is exactly A08 Software or Data Integrity Failures in Top 10:2025 — trusting data because of where it came from rather than because it was validated.

What to do

  1. Upgrade the library to the patched release.
  2. Add a type allowlist to the serializer. System.Text.Json is safe by default; the danger sits in BinaryFormatter and serializers with TypeNameHandling.
  3. Enable Redis auth and place it on a network unreachable from outside.
C#
// ❌ TypeNameHandling.All lets the payload declare its own classvar settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; // ✓ Types come from your code, never from the datavar opts = new JsonSerializerOptions { /* defaults are safe */ };var value = JsonSerializer.Deserialize<CacheEntry>(json, opts);

Verifying

Write a payload that declares its own type into the cache, then read it back — the application must reject it, not construct it:

C#
[Fact]public async Task Cache_Rejects_SelfDeclaringPayload(){    await _redis.StringSetAsync("k", """{"$type":"System.Diagnostics.Process, System","x":1}""");    var act = () => _cache.GetAsync<CacheEntry>("k");    await act.Should().ThrowAsync<JsonException>();}

References

Comments

Join the discussion
Sign up to comment

Commenting needs an account with at least one completed lesson. That condition is what keeps this thread worth reading: every point belongs to someone who can be asked back, and reputation accrues over time.

Sign upSign in

You can still read every comment below without an account. Signing in brings you back to this exact spot, not to the top of the page.

Loading comments…