Skip to content
Security Article

Attackers Are Compiling Their Shells Inside Oracle Itself

The technique dates to 2006; the privilege grant that enables it is still sitting in most application schemas.

Ji-ho Choi
Ji-ho Choi
Security & Cloud Editor · Aug 6, 2026 · 6 min read
Attackers Are Compiling Their Shells Inside Oracle Itself

On July 27, 2026, Huntress analysts caught credential theft on a Windows host running an Oracle database. The way in wasn't a CVE, a phished admin, or a leaked key. It was an autocomplete field on a public-facing Tomcat app that concatenated user input into a query and passed it to Oracle over JDBC — the same bug we've been writing the same blog posts about since the Clinton administration.

What happened next is the part worth your attention. Rather than drop a binary, which any competent EDR would have eaten for lunch, the attacker fed Java source code to the database and let Oracle compile it. Six classes plus PL/SQL wrappers, persisted as schema objects. Huntress tracks the kit as khunt, after the object prefix. KhuntCmd shells out through cmd.exe. KhuntHash reads Oracle's internal user table and writes credentials to a file. KhuntFS and KhuntFS2 list, search, and read the filesystem. KhuntT is a liveness ping; KhuntUnzip unpacks archives. The operator ran cmd.exe /c whoami and got back SYSTEM.

Then the unglamorous part: tasklist /svc dumped to F:\Oracle\khunttasks.txt, and the SAM, SECURITY, and SYSTEM registry hives copied into that same Oracle directory as khuntSAM.hiv and friends, with esentutl.exe doing the work on the locked ones. Local account hashes, boxed up for pickup.

None of this is new. Marco Ivaldi published raptor_oraexec.sql on 2006/11/23 — same architecture, same embedded JVM, procedures named javacmd, javareadfile, javawritefile. The technique is old enough to drink. What makes the incident worth a Monday morning is that the two configuration decisions it rides on are still the defaults across most Oracle estates, and our detection stack spent two decades learning to watch files and processes while the database quietly kept a compiler running behind the glass.

The grant nobody reads

To compile Java inside Oracle you need exactly one system privilege. Oracle's CREATE JAVA documentation is blunt about it: creating a Java source in your own schema requires CREATE PROCEDURE, because the database treats a Java class as a procedure for privilege purposes.

Now look at where CREATE PROCEDURE lives. It's one of eight privileges bundled into the RESOURCE role, alongside CREATE TABLE, CREATE SEQUENCE, and CREATE TRIGGER. Which means GRANT CONNECT, RESOURCE TO APPUSER; — the most copy-pasted line in Oracle onboarding scripts — hands your web app's database account a Java compiler. Nobody reads that grant as "and also, please compile arbitrary bytecode into my data dictionary." That is what it says.

The second grant is the one that should keep DBAs up at night. Runtime.exec inside the Oracle JVM doesn't work by default — the schema needs a java.io.FilePermission carrying execute, handed out through DBMS_JAVA.GRANT_PERMISSION. Huntress reports the compromised account had file-execution permission. Somebody granted it, probably years ago, probably to make a PDF converter or a report scheduler work, and nobody revoked it when the feature was retired.

That's the delta from 2006 that matters. Ivaldi's script asks for the DBA role in its header; khunt needed an app schema with RESOURCE and one stale Java permission. The bar dropped, and the population of exposed databases got a lot larger.

Why it landed on SYSTEM

The reports don't explain the SYSTEM result, so here's the mechanism. Code in the embedded JVM runs inside the database server process, and anything it spawns inherits that process's Windows token. The answer to "what did the attacker get" is just "whatever account the Oracle service runs as."

Before 12c, that was LocalSystem, full stop. Oracle Database 12.1 introduced the Oracle Home User, letting the database service run under a low-privileged domain or virtual account instead, and 12.2 added group Managed Service Accounts. But it's an installer choice, not a migration — estates installed on 11g and upgraded in place are still LocalSystem, and plenty of 19c installs picked the built-in account anyway because it's the path of least resistance. If sc qc OracleServiceORCL says LocalSystem, then SQL injection in any app touching that database is a domain-relevant incident, not a data-leak incident.

Every database ships this. Oracle's is just invisible

The OS-exec path isn't an Oracle bug; it's a category feature. SQL Server has xp_cmdshell, disabled by default and flagged by every hardening baseline in existence. PostgreSQL has COPY ... FROM PROGRAM, gated behind superuser or the pg_execute_server_program role. MySQL needs a UDF dropped into the plugin directory, which takes FILE and a writable path.

What sets Oracle apart is the shape of the artifact. The others leave something a defender already knows how to look at: a config flag, a role membership, a shared object on disk. Oracle gives you a compiler with a persistence layer, and the payload ends up as rows in the data dictionary. No file, no hash, nothing for AV to scan. Huntress makes the point that EDR doesn't inspect Java classes and PL/SQL wrappers inside Oracle, and that's correct.

It's also only half the picture, and the other half is your opening. The persistence is invisible; the execution is not. When KhuntCmd calls out to the shell, oracle.exe forks cmd.exe — a parent-child pair with essentially zero legitimate uses on a production database host. If your EDR or Sysmon coverage extends to database servers, and that's the real bug in most shops because DB hosts get exclusion lists a mile long to keep I/O latency down, you can write that detection this afternoon.

What to actually do

Three queries and a service check.

-- 1. Java objects nobody remembers creating
SELECT owner, object_name, object_type, created, last_ddl_time
FROM   dba_objects
WHERE  object_type LIKE 'JAVA%'
AND    owner NOT IN (SELECT username FROM dba_users WHERE oracle_maintained = 'Y');

-- 2. Schemas that can compile Java at all
SELECT grantee, privilege FROM dba_sys_privs
WHERE  privilege IN ('CREATE PROCEDURE','CREATE ANY PROCEDURE');
SELECT grantee FROM dba_role_privs WHERE granted_role = 'RESOURCE';

-- 3. Java permissions that allow shelling out
SELECT grantee, type_name, name, action
FROM   dba_java_policy
WHERE  action LIKE '%execute%' OR type_name LIKE '%RuntimePermission%';

Then sc qc your Oracle services and find out whose token you're actually handing out.

Revoking has a real cost, and anyone who says otherwise hasn't shipped an Oracle app. Flyway and Liquibase migrations typically run as the application schema, so if your migrations create packages or procedures, pulling CREATE PROCEDURE breaks the deploy pipeline immediately. Split the identity instead: a migration account with DDL rights that only CI holds, and a runtime account with CREATE SESSION plus object grants and nothing else. Half a day of work and an afternoon of arguing with whoever owns the pipeline — which is exactly why it never happens.

If you don't use the OJVM at all, removing it is the cleanest answer and kills the quarterly OJVM patch treadmill as a bonus. Oracle Support classifies that removal as destructive, so it belongs in a maintenance window with a verified restore, not a hardening sprint.

One honest caveat: every technical detail here traces to a single vendor incident report. No attribution, no second victim, no sense of scale. Treat khunt as one competent operator's toolkit, not a campaign. The mechanism needs no corroboration — Oracle documents it and working exploit code has been public since 2006. The database has always been able to do this. All that changed is someone got caught.

Sources & further reading

  1. Inside an Oracle Database SQL Injection Attack — huntress.com
  2. Attackers Compile khunt Inside Oracle to Turn SQL Injection Into Windows SYSTEM Access — thehackernews.com
  3. Hackers run khunt post-exploitation toolkit from Oracle database — bleepingcomputer.com
  4. CREATE JAVA - Oracle Database SQL Language Reference — docs.oracle.com
  5. Predefined Roles in an Oracle Database Installation — docs.oracle.com
  6. raptor_oraexec.sql - Oracle Java exploitation suite — github.com
  7. About Windows Services for Oracle Database — docs.oracle.com
  8. COPY - PostgreSQL Documentation — postgresql.org
Ji-ho Choi
Written by
Ji-ho Choi · Security & Cloud Editor

Ji-ho covers the increasingly tangled overlap between cloud architecture and security, drawing on a background as a penetration tester to keep his reporting grounded in real-world attack paths. He never lets a vendor claim go unquestioned and insists that every buzzword come with a proof of concept.

Discussion 2

Join the discussion

Sign in or create an account to comment and vote.

Dmitri Sokolov @ai_doomer_dmitri · 23 hours ago

the wild part isn't that sql injection still exists—yeah, boring—but that oracle's default permissions let you compile arbitrary java bytecode from inside the db. that privilege grant has been there since 2006 because removing it breaks a lot of legacy apps, so nobody touches it. we're basically choosing convenience over the ability to contain damage when (not if) the perimeter gets breached.

Larry Pike @legacy_larry · 21 hours ago

This is the exact trap I've been stuck in for twenty years. You can't revoke JAVAUSERPRIV without someone's ancient ORM layer screaming at 2am, so it stays. Question though: has anyone actually measured what breaks if you nuke it? Or are we just assuming the blast radius based on fear and the last time someone tried it in 2007.

Related Reading