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 thg 8, 20262 phút đọcAI soạn nhápcurate.summarizeĐã qua editor duyệtChưa có bản tiếng Việt

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>();}

Nguồn tham khảo

Bình luận

Tham gia thảo luận
Đăng ký để bình luận

Bình luận cần tài khoản đã hoàn thành ít nhất một bài học. Điều kiện đó là thứ giữ cho luồng thảo luận này còn đáng đọc: mỗi ý kiến gắn với một người có thể bị hỏi lại, và reputation tích luỹ theo thời gian.

Đăng kýĐăng nhập

Bạn vẫn đọc được toàn bộ bình luận dưới đây mà không cần tài khoản. Đăng nhập xong bạn sẽ quay lại đúng chỗ này, không phải đầu trang.

Đang tải bình luận…