Se este é um projeto legado que é codificado dessa maneira, embora não seja o ideal, atualmente não estou ciente de nenhuma maneira de ser suscetível à injeção de SQL, desde que cada string seja tratada dessa maneira e as consultas sejam apenas simples como você mostrou.
No entanto, não posso afirmar mais certeza do que isso. Sem usar consultas parametrizadas sempre existe a possibilidade de existir alguma vulnerabilidade que você ainda não considerou.
O escape manual das cotações é propenso a erros e, às vezes, pode falhar de maneiras difíceis de prever com antecedência. Por exemplo com a seguinte tabela
CREATE TABLE myTable(title VARCHAR(100))
INSERT INTO myTable VALUES('Foo')
E procedimento armazenado usando SQL dinâmico construído com concatenação de strings
CREATE PROC UpdateMyTable
@newtitle NVARCHAR(100)
AS
/*
Double up any single quotes
*/
SET @newtitle = REPLACE(@newtitle, '''','''''')
DECLARE @UpdateStatement VARCHAR(MAX)
SET @UpdateStatement = 'UPDATE myTable SET title=''' + @newtitle + ''''
EXEC(@UpdateStatement)
Você pode tentar o seguinte
Atualização normal
EXEC UpdateMyTable N'Foo'
SELECT * FROM myTable /*Returns "Foo"*/
Tentativa de injeção de SQL frustrada
EXEC UpdateMyTable N''';DROP TABLE myTable--'
SELECT * FROM myTable /*Returns "';DROP TABLE myTable--"*/
A tentativa de injeção de SQL é bem-sucedida e descarta a tabela
EXEC UpdateMyTable N'ʼ;DROP TABLE myTable--'
SELECT * FROM myTable /*Returns "Invalid object name 'myTable'."*/
O problema aqui é que a terceira consulta passa U+02BC em vez do apóstrofo padrão e, em seguida, a string é atribuída a um
varchar(max)
depois que o saneamento ocorre, o que silenciosamente converte isso em um apóstrofo regular. Até eu ler a resposta aqui esse problema nunca teria me ocorrido.