CVE-2023-27531 - Exploiting a Kredis JSON Deserialization Vulnerability in Ruby

In early 2023, a critical vulnerability—CVE-2023-27531—was publicly disclosed in Kredis, a popular Ruby library for managing Redis-backed types in Rails applications. This vulnerability involves insecure deserialization of JSON data, which could potentially allow attackers to execute arbitrary code or manipulate application logic.

In this post, we'll break down how this vulnerability works, provide code snippets, and show how an exploitation scenario could unfold. All in plain English for maximum clarity.

What is Kredis?

Kredis is a Ruby gem used mostly in Rails apps. It lets developers work with Redis data using familiar Ruby types. The library handles things like hashes, sets, lists, flags, numbers, and, relevant for this write-up, JSON.

It’s widely used, as it streamlines complex caching, session storage, and real-time features in apps that run on Rails.

About CVE-2023-27531

* Vulnerability Type: Deserialization of Untrusted Data
* Component: Kredis (JSON Type)
* Severity: HIGH
* Affected Versions: Kredis up to v1.3.2
* Patched Version: v1.3.3

Let’s get technical for a moment. Deserialization attacks usually happen when an application takes “serialized” (converted to a storable form) data from outside and turns it back (“deserializes”) into internal object(s) without checking it’s safe. If the attacker can insert things into this flow, very nasty things can happen—including remote code execution.

References

- Github Advisory Database CVE-2023-27531
- Kredis Issue #207

How Deserialization Gets Dangerous

Kredis's JSON type is designed to let you set/get structured data. Under the hood, it would use Ruby’s JSON.parse or, worse, sometimes Marshal.load for performance/compatibility. If an app developer stores raw JSON from user input, and Kredis naively deserializes it, a malicious payload could break out of just “plain data” and get interpreted as Ruby objects—even dangerous ones!

Let's see a vulnerable code example

# app/models/user.rb
class User < ApplicationRecord
  kredis_json :profile_data
end

# app/controllers/users_controller.rb
def update
  @user = User.find(params[:id])
  @user.profile_data.value = params[:profile_data]
  @user.save!
end

In the controller above, whatever JSON the user sends to profile_data gets written straight into Redis, without any filtering.

When someone reads @user.profile_data.value, Kredis deserializes it blindly, like so

# Vulnerable deserialization
def load(json)
  JSON.parse(json)  # Insecure if input is attacker-controlled
end

Suppose you have an endpoint that lets users write their profile, for example

POST /users/123
{
  "profile_data": "{\"class\": \"Dangerous::Evil\", \"payload\": \"run_this\"}"
}

If the attacker can craft content that Kredis or the app interprets as a Ruby class, and if the internal code does further processing on the deserialized structure (such as using constantize, send, or eval), you have a full code execution vector. While standard JSON.parse by itself is not always sufficient for RCE, many Rails apps use mixed serialization (sometimes Marshal, sometimes JSON), and Kredis's abstraction layer can easily bring you into that gray area, especially on older or custom implementations.

Alternatively, if another piece of code blindly trusts the data (e.g., processes payload as Ruby code or passes it to eval), the attacker could chain together a working exploit.

In a real-world example

# Somewhere else in the app
klass = profile_data['class'].constantize
klass.perform(profile_data['payload'])   # If 'class' is attacker-controlled, this is RCE!

The Patch

Kredis maintainers released a fix in v1.3.3. The new code ensures deserialization is safe and never loads unexpected Ruby objects, just plain data.

Patched code changes

# Secure deserialization
def load(json)
  JSON.parse(json, object_class: Hash, array_class: Array)  # Ensures only basic data types
end

And they specifically warn against ever using Marshal for data coming from untrusted sources.

Upgrade to Kredis v1.3.3+ immediately.

- Never trust user input for deserialization. Always sanitize and validate before storing in Redis, especially for any free-form fields.
- Search your codebase for uses of kredis_json or any code that reads/writes raw JSON or uses Marshal.load. Wrap or refactor as needed.

Conclusion

CVE-2023-27531 in Kredis is a classic, but dangerous, deserialization-of-untrusted-data bug. Left unpatched, attackers can exploit it to gain deeper access to any app that uses Kredis for JSON-typed storage.

Serialization is a powerful tool, but with great power comes great responsibility: always validate and sanitize, and keep your gems up to date.

Read More

- Kredis GitHub Security Advisory
- Ruby Serialization Security Guide
- CVE-2023-27531 NIST Entry

Timeline

Published on: 01/09/2025 01:15:07 UTC
Last modified on: 01/09/2025 22:15:26 UTC