SQL Server 2025 Security Vulnerability – In Memory DLL’s are Not Digitally Signed

Sources and References:

https://learn.microsoft.com/en-us/troubleshoot/sql/releases/sqlserver-2022/cumulativeupdate17#:~:text=Adds%20a%20server%20configuration%20option%20external%20xtp,implementation%20of%20those%20objects%20in%20C%20code.

https://learn.microsoft.com/en-us/sql/relational-databases/in-memory-oltp/create-in-memory-oltp-app-control-managed-installer?view=sql-server-ver17

An old article on the same topic that I have discussed before: https://databasesecurityninja.wordpress.com/2025/01/11/microsoft-sql-server-unsigned-dlls-and-its-security-implications/

Introduction:

hkdllgen.exe (known as the Hekaton DLL Generator) is an executable utility introduced by Microsoft in SQL Server 2022 (starting with Cumulative Update 17) to manage the compilation and digital signing of In-Memory OLTP (Hekaton) database components.

The parameter “external xtp dll gen util enabled” is enabled as shown below:

side remark…to enable it (because it’s not enabled by default):

I will then create a dummy database and enable in-memory feature for it:

create database memorydb2;

// I will add the file group to the database:

USE [master]

GO

ALTER DATABASE [memorydb2] ADD FILEGROUP [memory_file_group2] CONTAINS MEMORY_OPTIMIZED_DATA

GO

ALTER DATABASE [memorydb2]

ADD FILE(NAME = mem_dir, 

FILENAME = ‘C:\Program Files\Microsoft SQL Server\MSSQL17.MSSQL2025\MSSQL\DATA\XTP2’) 

TO FILEGROUP [memory_file_group2];

GO

// I will create a dummy table

USE [memorydb2]

GO

CREATE TABLE dbo.test(

    testo varchar(16) NOT NULL,

    Constraint PK_HekatonDataTable_Hekaton PRIMARY KEY NONCLUSTERED (testo)) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA);

// I will then insert random values in the table

USE [memorydb2]

GO

INSERT INTO dbo.test (testo)VALUES     (‘dumm_’ + CAST(ABS(CHECKSUM(NEWID())) % 1000 AS VARCHAR(10)));

GO 300

Its expected that the DLL file generated is digitally signed….however this is not the case as shown in the snpashot image:

Another Method is using sigcheck sysinternal utility:

A third approach, is to check by running the following t-sql query:

select * from sys.dm_os_loaded_modules where description=’XTP Native DLL’;

Conclusion:

While a valid cryptographic digital signature ensures the authenticity and integrity of a compiled binary, targeting dynamic database runtime components—such as In-Memory OLTP (XTP) directories—presents a severe attack vector if integrity controls are bypassed.

Because database services like SQL Server frequently operate under high-privilege service accounts (such as NT AUTHORITY\SYSTEM or a high-privileged virtual account), an adversary who achieves local administrative access could attempt to substitute or hijack dynamic runtime DLLs. A successful payload injection into the SQL Server process memory (sqlservr.exe) would inherit the service’s privileges, facilitating arbitrary code execution, local privilege escalation to SYSTEM, or the establishment of an outbound reverse shell.

Although modern SQL Server architectures and strict Code Integrity policies (along with Endpoint Detection and Response [EDR] solutions) actively block unsigned binaries, organizations must remain vigilant. If administrative exclusions or loose application whitelisting policies are improperly granted to dynamic application directories, it creates a dangerous security blind spot—allowing malicious, non-compliant binaries to be stealthily implanted and executed under the guise of trusted database operations.

The following is Microsoft MSRC response:

Your report describes a scenario in which a generated component is loaded without a signature, which was claimed to enable code substitution. In our review the generation step flags components as produced by a trusted managed installer rather than signing them, and customers can enforce policies to block components that are both unsigned and not from a managed installer; reaching the scenario requires administrative control that is already trusted. On that basis the reported behavior does not meet the definition of a security vulnerability. We are updating the documentation that described this inaccurately.

We appreciate the details provided in your report. The information has been shared with the responsible engineering team for awareness and internal review.

EDB Postgres Advanced Server Vulnerability: Exfiltration of Sensitive Data through a Histogram Dictionary View Without Detection (No Audit Log Generation)

Disclaimer: This finding has been reported to EDB, and they consider this as a security enhancement finding/observation and not a vulnerability.

Introduction:

Adversaries continuously exploit every available vector to exfiltrate sensitive data while evading detection. This risk is magnified because critical threats originate from both external cybercriminals and trusted insiders.

To counter this, robust database auditing acts as a foundational line of defense, enforcing tailored policies that align with corporate security and compliance mandates. By streaming these granular audit logs directly to a SIEM solution, Security Operations Center (SOC) analysts gain real-time visibility to spot anomalous queries and unauthorized modifications. Furthermore, these logs serve as an immutable evidentiary trail for post-incident forensics. Ensuring that the database security and logging engine functions reliably is therefore non-negotiable for enterprise risk mitigation, rapid incident response, and regulatory compliance.

Proof of Concept:

// I will create a dummy database and will call it “septdb”

postgres=# create database septdb;

// I will create a dummy table under public schema and will call it “t1” and insert some dummy values

postgres=# \c septdb

You are now connected to database “septdb” as user “enterprisedb”.

septdb=# create table t1 (emp_name text, salary number);

CREATE TABLE

septdb=# insert into t1 values (‘Aljandro Gonzales’, 4000);

INSERT 0 1

septdb=#

septdb=# insert into t1 values (‘James Brown’, 8300);

INSERT 0 1

septdb=#

septdb=# insert into t1 values (‘Abdullah Gole’, 6220);

INSERT 0 1

// I will analyze the table

septdb=# analyze t1;

ANALYZE

// I will create a user/role called “sept” and will grant it SELECT permission on the table

postgres=# create role sept with login;

CREATE ROLE

postgres=# \c septdb

You are now connected to database “septdb” as user “enterprisedb”.

septdb=#

septdb=# grant select on public.t1 to sept;

GRANT

septdb=# create table t2 (emp_name text);

CREATE TABLE

septdb=# insert into t2 values (‘EMAD’);

INSERT 0 1

// I will alter my session to “sept” account and will query pg_stats dictionary view

septdb=# set session authorization sept;

SET

septdb=>

septdb=> select current_user;

 current_user

————–

 sept

(1 row)

septdb=> select * from pg_stats where tablename=’t1′;

 schemaname | tablename | attname  | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs |                  histogram_boun

ds                   | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_b

ounds_histogram

————+———–+———-+———–+———–+———–+————+——————+——————-+——————————–

———————+————-+——————-+————————+———————-+————————+——————+——–

—————-

 public     | t1        | emp_name | f         |         0 |        14 |         -1 |                  |                   | {“Abdullah Gole”,”Aljandro Gonz

ales”,”James Brown”} |        -0.5 |                   |                        |                      |                        |                  |

 public     | t1        | salary   | f         |         0 |         5 |         -1 |                  |                   | {4000,6220,8300}

                     |         0.5 |                   |                        |                      |                        |                  |

(2 rows)

I will configure an audit policy against t1 table for any SELECT execution against the table:

postgres=# \c septdb

You are now connected to database “septdb” as user “enterprisedb”.

septdb=#

septdb=# ALTER TABLE t1 SET (edb_audit_group = ‘high_security’);

ALTER TABLE

septdb=#

septdb=# alter system set edb_audit_statement = ‘select@high_security’;

ALTER SYSTEM

septdb=#

septdb=# select pg_reload_conf();

 pg_reload_conf

—————-

 t

(1 row)

septdb=# select pg_reload_conf();

 pg_reload_conf

—————-

 t

(1 row)

septdb=# select pg_reload_conf();

 pg_reload_conf

—————-

 t

(1 row)

To verify audit settings and policy is active and in-place:

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

postgres=# \c septdb

You are now connected to database “septdb” as user “enterprisedb”.

septdb=#

septdb=# set session authorization sept;

SET

septdb=> \x

Expanded display is on.

septdb=>

septdb=> select * from pg_stats where tablename=’t1′;

-[ RECORD 1 ]———-+—————————————————-

schemaname             | public

tablename              | t1

attname                | emp_name

inherited              | f

null_frac              | 0

avg_width              | 14

n_distinct             | -1

most_common_vals       |

most_common_freqs      |

histogram_bounds       | {“Abdullah Gole”,”Aljandro Gonzales”,”James Brown”}

correlation            | -0.5

most_common_elems      |

most_common_elem_freqs |

elem_count_histogram   |

range_length_histogram |

range_empty_frac       |

range_bounds_histogram |

-[ RECORD 2 ]———-+—————————————————-

schemaname             | public

tablename              | t1

attname                | salary

inherited              | f

null_frac              | 0

avg_width              | 5

n_distinct             | -1

most_common_vals       |

most_common_freqs      |

histogram_bounds       | {4000,6220,8300}

correlation            | 0.5

most_common_elems      |

most_common_elem_freqs |

elem_count_histogram   |

range_length_histogram |

range_empty_frac       |

range_bounds_histogram |

The low-privileged account “sept” was able to view the actual data stored in the table public.t1 without the need to actually querying the “monitored” base table itself and NO security audit records were generated.

The next step, I have extended the audit policy for “edb_audit_statement” parameter to include select@high_security,dml,ddl as shown below.

And, nothing was generated in the security audit logs that pg_stats view SELECT statement was captured.

Why this is considered a serious security problem ?

  • A superuser account can view the data from histogram view pg_stats dictionary view without the need to explicitly execute SELECT statement against the monitored sensitive table. The configured audit policy to monitor SELECT execution will not capture this. This is also applicable to the low-privileged account with SELECT permission on the table. The idea is to detect any exfiltration of sensitive table data.
  • The only security audit policy that will capture querying pg_stats view if the parameter “edb_audit_statement” is set to “all”. Of course this is not parctical as organizations/companies will tailore their configured audit policy to balance between security, performance, and the capcity of the destination SIEM. Also, it will not help in detecting that the data of a sensitive monitored table has been taking place (as a security threat case).

  • In row-level security feature, an account that can view subset of data can’t view any of the stored histogram data stored in the dictionary view pg_stats (select statement will return zero rows), so pg_stats was considered a data exposure problem.

  • Finally, detecting exfiltration of data from specific set of tables hosting senstivie data is a common cybersecurity threat case that will definetly be implimented by many companies/organization using EDB EPAS technology….all possible techniques for fetching sensitive data should be covered.

References:

https://www.postgresql.org/docs/current/view-pg-stats.html

https://www.enterprisedb.com/docs/epas/latest/database_administration/01_configuration_parameters/03_configuration_parameters_by_functionality/07_auditing_settings/09_edb_audit_statement

SQL Server Indexes Security

I was exploring SQL Server Text Indexes, and while exploring it I stumbled upon the function sys.dm_fts_index_keywords.

According to the “current” version of the documentation (up to 1 September 2026): https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-objects/sys-dm-fts-index-keywords-transact-sql?view=sql-server-ver17

sysadmin role is required to run this function, I found out that this is not true !

USE [master]GOCREATE LOGIN [test] WITH PASSWORD=N’test’, DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFFGO

Then, the account will be added as database user:

USE [demodb]

GO

CREATE USER [test] FOR LOGIN [test]

GO

And, this database user will be granted the “control” permission

use [demodb]

GO

GRANT CONTROL TO [test]

GO

Connecting to the SQL Server Instance as “test” account, I was able to execute the function successfully as shown below and partial data exposure of “comment” coulmn were displayed:

SELECT *

FROM sys.dm_fts_index_keywords(DB_ID(‘demodb’), OBJECT_ID(‘dbo.MyTable’));

GO

At this stage there is two possible explanations: documentation error, OR the design is intended that only account with sysadmin role could execute the function to limit data exposure.

I have opened a case with Microsoft MSRC ,and they concluded that this was a documentation error and the web page will be updated: https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-objects/sys-dm-fts-index-keywords-transact-sql?view=sql-server-ver17

Indexes is one of the area’s in database systems that can be exploited for “data exposure”, this is why its important to know how to deal with them……

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

OR

you can run the following t-sql query:

SELECT * FROM GENERATE_SERIES(-9223372036854775807,9223372036854775808) ORDER BY 1 DESC

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 Prevent , Detect 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