As GreyNoise researcher, you always have things to write detection rules for. Some of them aren’t always exciting, but they become more interesting as you dive deeper. Let’s jump right in and take a look at CVE-2024-6633:
The default credentials for the setup HSQL database (HSQLDB) for Forta FileCatalyst Workflow are published in a vendor knowledgebase article.
No need to put my hands on the FileCatalyst Workflow, as the HyperSQL Database Engine part of it is open source.
HSQLDB comes with server and client tools, hsqldb.jar
. The following starts a server instance (which by default uses port 9001
and creates a test
database):
~/GN/hsqldb-2.3.4/hsqldb/lib % java -cp hsqldb.jar org.hsqldb.Server
<SNIP>
[Server@776ec8df]: Database [index=0, id=0, db=file:test, alias=] opened successfully in 324 ms.
[Server@776ec8df]: Startup sequence completed in 331 ms.
[Server@776ec8df]: 2024-12-03 11:51:18.689 HSQLDB server 2.3.4 is online on port 9001
[Server@776ec8df]: To close normally, connect and execute SHUTDOWN SQL
[Server@776ec8df]: From command line, use [Ctrl]+[C] to abort abruptly
I need to replicate the configuration FileCatalyst Workflow uses by creating server.properties
in the same directory where hsqldb.jar
is located:
~/GN/hsqldb-2.3.4/hsqldb/lib % cat server.properties
server.database.0=file:hsqldb,user=sa;password=GOSENSGO613
server.dbname.0=hsqldb
server.port=4406
There’s a JayDeBeApi module that allows you to connect from Python code to databases using Java JDBC, so I quickly put together a script to figure out how HSQLDB connection looks like on the wire:
import jaydebeapi, logging
= "sa"
default_user = "GOSENSGO613"
default_pass = "127.0.0.1"
db_host = "4406" # default is 9001
port
= logging.getLogger(__name__)
logger
print(f"[i] attempting hsqldb connection to {db_host}:{port}")
try:
= jaydebeapi.connect(
conn "org.hsqldb.jdbcDriver",
f"jdbc:hsqldb:hsql://{db_host}:{port}/hsqldb", [default_user, default_pass],
"hsqldb.jar",
)if conn:
print(f"[!] success, hsqldb at {db_host}:{port} is vulnerable")
conn.close()
except Exception as e:
"[x] %s", e) logger.error(
Running the snippet with HSQLDB server (and Wireshark) in the background results in a successful check:
~ % python3 cve-2024-6633.py
[i] attempting hsqldb connection to 127.0.0.1:4406
[!] success, hsqldb at 127.0.0.1:4406 is vulnerable
Now, onto inspecting the capture with tshark
, server responses are omitted for brevity:
~ % tshark -r hsqldb.pcapng -Y "tcp.flags.push == 1" -V
<SNIP>
TCP payload (4 bytes)
Data (4 bytes)
0000 ff e1 04 c0 ....
Data: ffe104c0
[Length: 4]
<SNIP>
TCP payload (60 bytes)
Data (60 bytes)
0000 1f 00 00 00 3b 00 00 00 06 68 73 71 6c 64 62 00 ....;....hsqldb.
0010 00 00 02 53 41 00 00 00 0b 47 4f 53 45 4e 53 47 ...SA....GOSENSG
0020 4f 36 31 33 00 00 00 10 41 6d 65 72 69 63 61 2f O613....America/
0030 4e 65 77 5f 59 6f 72 6b ff ff b9 b0 New_York....
Data: 1f0000003b000000066873716c64620000000253410000000b474f53454e53474f363133…
[Length: 60]
<SNIP>
Looks very straightforward, and HSQLDB seemingly does not care about the usernames being lower or upper case. At this point I could write a crude Suricata rule, and call it a day.
~/GN/suricata-test-zone % cat test.rules && echo && suricata --runmode autofp -k none -v -r ~/hsqldb.pcapng -s test.rules | grep Alert
alert tcp any any -> any 4406 (msg: "CVE-2024-6633 Check"; flow:to_server, established; content:"SA"; nocase; content:"GOSENSGO613"; distance:0; sid:31337; rev:1;)
Info: counters: Alerts: 1
However, not knowing what’s the deal with the first data packet sent by client really grinds my gears!
TCP payload (4 bytes)
Data (4 bytes)
0000 ff e1 04 c0 ....
Data: ffe104c0
[Length: 4]
Having some experience working with network protocols, I automatically assume that ffe104c0
is some sort of HSQL-specific magic number. Unfortunately, the protocol is not documented, but there’s usually some hope that there are references to this number in the source code. Not this time (at least, not directly):
~/GN/hsqldb-2.3.4/hsqldb/src/org/hsqldb % rg ffe104c0 || echo ":("
:(
Cooking ffe104c0
with various CyberChef recipes also did not produce any sensible results (I somewhat managed to figure out why). However, I got lucky while hesitating doing a code review and poking around with Python REPL, struct
, and ripgrep:
>>> import struct
>>> magic = bytes.fromhex("ffe104c0")
<SNIP>
>>> print(struct.unpack(">i", magic))
-2030400,) (
~/GN/hsqldb-2.3.4/hsqldb/src/org/hsqldb % rg 2030400
ClientConnection.java
89: public static final int NETWORK_COMPATIBILITY_VERSION_INT = -2030400;
Zoom and enhance:
public class ClientConnection implements SessionInterface, Cloneable {
/**
* Specifies the Compatibility version required for both Servers and
* network JDBC Clients built with this baseline. Must remain public
* for Server to have visibility to it.
*
* Update this value only when the current version of HSQLDB does not
* have inter-compatibility with Server and network JDBC Driver of
* the previous HSQLDB version.
*
* Must specify all 4 version segments (any segment may be the value 0,
* however). The string elements at (position p from right counted from 0)
* are multiplied by 100 to power p and added up, then negated, to form the
* integer representation of version string.
*/
public static final String NETWORK_COMPATIBILITY_VERSION = "2.3.4.0";
public static final int NETWORK_COMPATIBILITY_VERSION_INT = -2030400;
According to the version control system, the NETWORK_COMPATIBILITY_VERSION_INT
value haven’t changed since 2016, thus the resulting Suricata rules have decent accuracy and look like this:
alert tcp any any -> any 4406 (msg: "CVE-2024-6633 Check"; flow:to_server, established; content: "|ff e1 04 c0|"; depth:4; content:"SA"; nocase; distance:0; content:"GOSENSGO613"; distance:0; sid:31337; rev:1;)
and
alert tcp any any -> any 9001 (msg: "HSQL Protocol Scanner"; flow:to_server, established; content: "|ff e1 04 c0|"; depth:4; sid:31338; rev:1;)
,
which is pretty close (except for the secret sauce, of course) to what GreyNoise is running behind the scenes to power these tags:
Thanks for reading!