Estou tentando encontrar uma maneira de permitir que um aplicativo crie tabelas e insira dados nelas em um SQL Server 2019 enquanto protege contra ataques de injeção caso as credenciais do aplicativo vazem. Minha experiência é limitada quando se trata de escrever código que pode ser executado em paralelo e escrever sql dinâmico protegido contra ataques de injeção de sql.
O nome da tabela é baseado na entrada do aplicativo, ou seja, se a entrada for 'nds', o nome da tabela deve ser lake.nds_raw_log.
É meu entendimento que não há como fazer isso por meio da concessão direta de permissões à função para este aplicativo, pois a criação de tabelas não é separada da exclusão ou alteração delas.
O que eu criei é executar um procedimento armazenado como dbo. Claro que não é longo, mas eu tenho dois problemas com ele:
- parece artificial, o que, pela minha experiência, diz que existe uma maneira mais fácil.
- Acredito que preciso executá-lo como serializável para evitar tabelas órfãs se eu recuperar a tabela errada quando consultar minha tabela recém-criada. Isso não deve ser um problema tão grande, já que não acontecerá com tanta frequência após o primeiro início da produção, então talvez eu não deva me importar com isso.
create procedure [lake].[create_terminal_raw_log_table]
(
@terminal_name nvarchar(100)
)
with execute as 'dbo'
as
begin try
set transaction isolation level serializable
begin transaction
--create table
declare @dynamic_sql nvarchar(1000) =
'create table [lake].' + quotename(@terminal_name) + '
(
id bigint not null,
[timestamp] datetime2(3) not null,
cmd varbinary(max) not null
);'
exec sp_executesql @dynamic_sql
/*get name of new table, this is why I believe that I need serializable isolation
since other tables can be created in parallel*/
declare @table_name nvarchar(100) =
(
select top 1
[name] as table_name
from sys.tables
order by create_date desc
)
--rename table
declare
@old_name nvarchar(100) = '[lake].' + @table_name,
@new_name nvarchar(100) = @table_name + '_raw_log'
begin try
exec sp_rename
@objname = @old_name,
@newname = @new_name
end try
begin catch
set @dynamic_sql = 'drop table ' + @old_name
exec sp_executesql @dynamic_sql
;throw
end catch
--create primary key
set @dynamic_sql = 'alter table [lake].' + @new_name + ' add constraint pk__' + @new_name + ' primary key(id)'
exec sp_executesql @dynamic_sql
commit transaction
end try
begin catch
rollback --I thought a rollback would occur when I throw after dropping the table but that doesn't seem to be the case
;throw
end catch
Então eu acho que isso se resume a 3 perguntas:
- Este procedimento armazenado é realmente seguro contra ataques de injeção de SQL?
- Existe uma maneira mais fácil de fazer isso?
- É correto que definir o nível de transação como serializável protegerá o código de selecionar a tabela errada ao selecionar sys.tables?
A menos que esteja faltando um requisito de negócios ou um detalhe em seu código, acho que você está tornando seu procedimento muito mais complicado do que o necessário.
Não há necessidade de criar a tabela, procurar o nome da tabela, renomear a tabela e adicionar a restrição de chave primária, tudo como etapas separadas, agrupadas em uma transação para garantir a consistência. Em vez disso, você pode fazer tudo em uma única etapa.
Algumas outras notas de revisão de código em seu código:
nvarchar
variável para dar suporte a unicode, mas fazendo a atribuição usando aspas simples "regulares". Para oferecer suporte a strings unicode, você precisará usar oN'
prefixo para citar strings unicode.CREATE TABLE
sys
prefixo do esquema emsp_executesql
.Aqui está a minha versão do seu procedimento:
Certifique-se de testar!
Você vai querer fazer alguns testes rápidos de sanidade para ter certeza de que seu procedimento realmente funciona. Gosto de ter certeza de testar com caracteres unicode (sempre uso emojis) e quaisquer outras preocupações específicas (como injeção de SQL, espaço em branco nos nomes dos objetos, comprimento mínimo ou máximo, etc).
Por exemplo:
Retorna estes resultados: