Read-only clause for a query - sql-server

I am wanting to allow users, via an ASP page, to run a query on my database, however I want this query to be restricted to read-only.
I will try to detect as much as I can in the ASP itself (detect words drop, update, delete etc.) and not run the query in these scenarios, but, is there a way I can just append a flag to the query when my ASP submits it to have it execute as read-only regardless of its content?
I realise that, if this is possible, update/delete/drop statements will error - this is fine.
I also want to avoid setting the whole DB or even table as read-only.
I also don't want to replicate my database in any form.
Thankyou for your help.
Dan

Your query should be dynamic. Main problem - sql injection.
I recommend this way:
create database role [DYNAMIC_SQL]. Give required select permissions on tables to [DYNAMIC_SQL] role.
create database user without login [dynamic_sql_proxy]. Map with [DYNAMIC_SQL] role.
create procedure [dynamic_sql__call] with hardcoded execution rights as [dynamic_sql_proxy]. Pass your custom query to this procedure.
Scripts:
(1)
USE [your_db]
GO
CREATE ROLE [DYNAMIC_SQL]
GO
GRANT SELECT ON [dbo].[your_table] TO [DYNAMIC_SQL] AS [dbo]
***
(2)
CREATE USER [dynamic_sql_proxy] WITHOUT LOGIN WITH DEFAULT_SCHEMA = [dbo]
ALTER ROLE [DYNAMIC_SQL] ADD MEMBER [dynamic_sql_proxy]
(3)
CREATE PROCEDURE [dbo].[dynamic_sql__call]
#err_code INT OUTPUT
,#err_msg NVARCHAR(4000) OUTPUT
,#sql NVARCHAR(MAX)
WITH EXECUTE AS 'dynamic_sql_proxy'
AS
BEGIN
SET #err_code = 0;
SET #err_msg = SPACE(0);
BEGIN TRY
EXEC(#sql);
END TRY
BEGIN CATCH
PRINT 'Please handle your error';
END CATCH;
RETURN #err_code;
END;

Thankyou Allan S. Hansen, I used the username/password method to good effect.

Related

SQL Server Permission Chaining

I have the following issue:
I have two different databases, db1 and db2. I have an application that loads data into db2 or db3. db1 has a few tables that the application uses to determine behavior, including which db the application should load data into.
Users need to have write access to db1 to operate the application (there is a console application that writes to tables in db1, operating with windows authentication).
Users should not have DML privileges to db2 and db3, with the exception of a few predetermined operations. We grant AD groups database roles to control access from and organization perspective. Specifically, I'm trying to build a stored procedure in db1 that operators can use to reverse data loaded to db2 or db3 with appropriate logging.
I'm attempting to use create proc ... execute as owner to accomplish this, but it does not seem to be working when I try to hit tables in db2/db3 (I'm thinking that "execute as owner" operates on db level users an not server level logins?). The following causes a permission error stating that the owner (myself) does not have permissions to db2/db3.
use db1
go
create proc dbo.wrapper #recordid int
as begin
/*
capturing user
*/
declare #usr varchar(255) = SUSER_SNAME()
exec dbo.inner #usr , #recordid
end
use db1
go
create proc dbo.inner #usr varchar(255), #recordid int
with execute as owner
as begin
/*
logic to determine whether to update db2 or db3 goes here
*/
insert db2.rolled_back
select * , #usr from db2.transactions where id = #recordid
delete from db2.transactions where id = #recordid
insert db3.rolled_back
select * , #usr from db3.transactions where id = #recordid
delete from db3.transactions where id = #recordid
end
Is there a way to get this to work? I've heard that certificate signing could do this, does anyone have any experience using certificate users. Our DBA's would rather not have to maintain certificates, so if there is a way to get this to work without certificates that would be best.
Any advice would be helpful.
Thank You!
I'm going to cover the cross database chaining side of thing here. note that there are certainly security considerations when using this method. For example someone with permissions to create objects in one database can give themselves access to data in another database with the owner, when they themselves have no access to the other database. The security concerns, however, are out of scope of this answer.
Firstly, let's create a couple of test databases.
USE master;
GO
CREATE DATABASE Chain1;
CREATE DATABASE Chain2;
Now I'm going to CREATE a LOGIN, which is disable and make that the owner of these 2 databases. The databases having the same owner is important, as otherwise the chaining won't work.
CREATE LOGIN ChainerOwner WITH PASSWORD = N'SomeSecurePassword123';
ALTER LOGIN ChainerOwner DISABLE;
GO
ALTER AUTHORIZATION ON DATABASE::Chain1 TO ChainerOwner;
ALTER AUTHORIZATION ON DATABASE::Chain2 TO ChainerOwner;
I'm also going to create a LOGIN which we're going to use to test on:
CREATE LOGIN SomeUser WITH PASSWORD = N'SomeSecurePassword123';
Great, now I can create a few objects; a table in Chain1, a PROCEDURE in Chain2 that accesses the TABLE, and a USER in both databases for SomeUser. In Chain1 the USER will be given no permissions, and in Chain2 the user will be given the permsision to EXECUTE the PROCEDURE:
USE Chain1;
GO
CREATE TABLE dbo.SomeTable (I int IDENTITY,
S varchar(10));
INSERT INTO dbo.SomeTable (S)
VALUES ('abc'),
('xyz');
GO
CREATE USER SomeUser FOR LOGIN SomeUser;
GO
USE Chain2;
GO
CREATE PROC dbo.CrossDBProc #I int AS
BEGIN
SELECT I,
S
FROM Chain1.dbo.SomeTable
WHERE I = #I;
END;
GO
CREATE USER SomeUser FOR LOGIN SomeUser;
GO
GRANT EXECUTE ON dbo.CrossDBProc TO SomeUser;
GO
Great, all the objects are created, now let's try to EXECUTE that PROCEDURE:
EXECUTE AS LOGIN = 'SomeUser';
GO
EXEC dbo.CrossDBProc 1; --This fails
GO
REVERT;
GO
This fails, with a permission error:
The SELECT permission was denied on the object 'SomeTable', database 'Chain1', schema 'dbo'.
This is expected, as there is no ownership chaining. let's, therefore enable that now.
USE master;
GO
ALTER DATABASE Chain1 SET DB_CHAINING ON;
ALTER DATABASE Chain2 SET DB_CHAINING ON;
Now if I try the same again, the same SQL works:
USE Chain2;
GO
EXECUTE AS LOGIN = 'SomeUser';
GO
EXEC dbo.CrossDBProc 1; --This now works
GO
REVERT;
GO
This successfully returns the result set
I
S
1
abc
So, yes you can chain cross database, but it requires some set up, and (again) there are security considerations you need think about.
Clean up:
USE master;
GO
DROP DATABASE Chain1;
DROP DATABASE Chain2;
GO
DROP LOGIN ChainerOwner;
DROP LOGIN SomeUser;

How does impersonation and security permissions in tSQLt tests work?

I have a tSQLt test which I expect to fail but it runs with success. When I run the code outside the procedure, it fails as expected, but when executing the test with Run, no error occurs.
I have read the question tSQLt Testing SQL Server security permissions but the accepted answer does not solve my problem.
My test looks like this:
CREATE PROCEDURE TestSecurity.[test AFDK_Reader cannot read from AWS schema]
AS
BEGIN
--EXEC tSQLt.ExpectException
EXECUTE AS USER = 'AFDK_Reader'
select *
from sys.user_token
SELECT * FROM fn_my_permissions('AWS', 'SCHEMA')
ORDER BY subentity_name, permission_name ;
SELECT *
FROM [AWS].[ADRESSEPUNKT_HISTORIK]
REVERT
END
The role has granted select permissions on the AFDK schema only and that is the only database role membership the SQL user has.
The AFDK_Reader has no permissions to read from the AWS schema.
Can anybody tell me how to get on with my debugging? Thanks in advance.
EXECUTE AS... REVERT commands don't behave in the way you are expecting inside a stored procedure. This is a general feature of stored procedure security in SQL Server; one common use of stored procedures is to abstract permissions. The MS docs page Customizing Permissions with Impersonation in SQL Server says:
SQL Server does not check the permissions of the caller if the stored
procedure and tables have the same owner.
and that's effectively what's happening here. Even though the EXECUTE AS changes the security context, that security context isn't checked inside the stored procedure.
The docs page also says:
However, ownership chaining doesn't work if objects have different
owners or in the case of dynamic SQL.
One way to get the behaviour you're expecting would be to run the SELECT statement from inside a dynamic SQL statement, which means that the active security context is tested against the table permissions:
CREATE PROCEDURE TestSecurity.[test AFDK_Reader cannot read from AWS schema]
AS
BEGIN
EXEC tSQLt.ExpectException
EXECUTE AS USER = 'AFDK_Reader'
EXEC ('SELECT * FROM [AWS].[ADRESSEPUNKT_HISTORIK]')
REVERT
END
A alternative (better?) solution might be to use the built-in permission functions to test permissions settings through metadata. Here's one way, still using EXECUTE AS... REVERT and sys.fn_my_permission:
CREATE PROCEDURE TestSecurity.[test AFDK_Reader cannot read from AWS schema]
AS
BEGIN
EXECUTE AS USER = 'AFDK_Reader'
DECLARE #permissionCount int = (SELECT COUNT(*) FROM sys.fn_my_permissions('[AWS].[ADRESSEPUNKT_HISTORIK]', 'OBJECT') WHERE permission_name = 'SELECT' AND subentity_name = '')
REVERT
EXEC tSQLt.AssertEquals 0, #permissionCount
END

Sql Server: How to create a db-snapshot from within stored procedure for non-privileged user?

I'm trying to create a stored procedure that creates a dB-snapshot for a non privileged user.
The idea is to provide to a normal user a way to create a dB snapshot in order to run queries against it and delete the snapshot when it is done with it.
I thought it would be possible to use the 'with execute as owner" in the procedure declaration. However, I always get the following error:
CREATE DATABASE permission denied in database 'master'.
Here is my code:
-- The user that create the sp has sysadmin right
CREATE OR ALTER PROCEDURE [dbo].[makeSnapshot] WITH EXECUTE AS OWNER
AS
-- just an extract of the code (should test if exist...)
DECLARE #exec NVARCHAR(2000)
set #exec = 'CREATE DATABASE test_dbss1900 ON ( NAME = test, FILENAME =
''C:\\Program Files\\Microsoft SQL Server\\MSSQL14.SQLSERVER2017\\MSSQL\\Data\\test_1900.ss'' ) AS SNAPSHOT OF test';
EXEC (#exec)
GO
-- try to execute it (with any user)
EXEC dbo.[makeSnapshot]
Has anyone an idea how I can come up with a stored proc that will allow a normal user to create a db snapshot?
Thank for any help!
José
I actually found a solution by looking at - http://www.sommarskog.se/grantperm.html#serverlevel (chapter 5.3)
The way was to use certificates

Stored procedure to act differently based on user role

I'm trying to figure out if there is a way to achieve the converse of this:
can a SQL Server stored proc execute with higher permission than its caller?
I want to create a stored procedure which does one thing if the user is in a role, but a fallback option if they're not.
My first attempt tried to query the current user's roles, based on this:
How to query current user's roles
I tried to query what role a user was in, and decide what to do based on that. But if you set "mydomain\Domain Users" to a role (for example), users who belong to Domain Users aren't listed in the sys.database_role_members view. So users who were supposed to have permissions don't.
From here https://msdn.microsoft.com/en-us/library/ee677633.aspx
IS_ROLEMEMBER always returns 0 when a Windows group is used as the database
principal argument, and this Windows group is a member of another Windows
group which is, in turn, a member of the specified database role.
My next attempt works like this. Create a stored procedure with the actual permissions, and then a wrapper around it which calls the with lower permissions, and if that fails, perform the fallback action:
CREATE PROCEDURE [internal_myproc]
AS
BEGIN
-- do something here
END
GO
GRANT EXECUTE ON [internal_myproc] TO [Role1] AS [dbo]
GO
CREATE PROCEDURE [myproc]
AS
BEGIN
BEGIN TRY
EXEC [internal_myproc]
END TRY
BEGIN CATCH
-- perform fallback action
END CATCH
END
GO
GRANT EXECUTE ON [internal_myproc] TO [Role1] AS [dbo]
GO
GRANT EXECUTE ON [internal_myproc] TO [Role2] AS [dbo]
GO
GRANT EXECUTE ON [internal_myproc] TO [Role3] AS [dbo]
GO
The problem with this is that [Role1] and [Role2] both succeed in executing [internal_myproc] via [myproc]. If you take the code out of the stored procedure, it behaves the way it should, but because it's hidden inside a stored procedure, it gets implicit permissions to execute other stored procedures. I've experimented with "WITH EXECUTE AS" stuff, but it doesn't seem to change anything.
I also tried "IF HAS_PERMS_BY_NAME('internal_myproc', 'OBJECT', 'EXECUTE') = 1", suggested here MS SQL Server: Check to see if a user can execute a stored procedure , but that seems to not work in certain situations I haven't understood yet.
Is there a way to NOT grant those implicit permissions, to do a permission check inside a stored procedure? Or something equivalent?

How to set DENY permissions on a data user

For a .net application we can store database connectionstrings like so
<connectionstrings config="cnn.config" />
I am trying to get as little as permissions as possible but there always seems to be a different way. To get info.
I am using the settings because they are more secure for one and two I have other people working on my application.
So I've set
USE database_name;
GRANT EXECUTE TO [security_account];
So the user can execute commands that's fine.
Then I've got db_reader and db_writer so they can read and write and this seems like a perfect marriage.
Still bad news the user can login and see the tables and store procedures definitions but not alter them, however, they can still see them.
How can I set the permissions to where the user can read, write. execute, and that is it PERIOD!?
Being able to see the definition of tables, stored procedures, etc is governed by the VIEW DEFINITION permission. So you can do:
DENY VIEW DEFINITION TO [yourUser];
And that user won't be able to see the definition for any object in the database. This also includes the ability to see other users.
If you want to stop the user from viewing the sp definition you need to specify the WITH ENCRYPTION option in the sp.
Adding a user to the db_datareader and db_datawriter role and granting EXECUTE will limit the user to reading writing and executing but they will still be able to see the sp definition by using the sp_helptext stored procedure and sys.sql_modules catalog view. See here for more information on sp and funciton encryption.
exec sp_addrolemember #rolename = 'db_datareader', #membername = 'testUser'
exec sp_addrolemember #rolename = 'db_datawriter', #membername = 'testUser'
GRANT EXECUTE TO testUser;
You can create an sp the WITH ENCRYPTION option by adding it before the AS keyword like this. See the WITH ENCRYPTION section of the CREATE PROCEDURE definition here
CREATE PROCEDURE HumanResources.uspEncryptThis
WITH ENCRYPTION
AS
SET NOCOUNT ON;
SELECT BusinessEntityID, JobTitle, NationalIDNumber, VacationHours, SickLeaveHours
FROM HumanResources.Employee;
GO
You can also alter functions by adding it after the returns keyword.
ALTER FUNCTION dbo.getHash ( #inputString VARCHAR(20) )
RETURNS VARBINARY(8000) WITH ENCRYPTION

Resources