After getting great help in securing against SQL injection from classic ASP protection against SQL injection, I've encountered a major issue which cannot be solved using parameterized queries.
name = Trim(Request.QueryString("name"))
flds = Trim(Request.QueryString("flds"))
sql = "set rowcount 0 select " & flds & " from [TABLE] where Name = '" & name & "'"
From what I understand, a parameterized query will protect against SQL injection in the WHERE clause (in this case, the name field.
flds is a comma-separated list of parameters that the users wants returned. As it is obvious, it is very vulnerable to SQL injection.
One idea I have to secure my code is to have a statically generated dict of valid fields, split the flds string by ",", verify each one of the values against the dict, and construct the SQL query that will consist of all the fields that are present in the dict.
It seems to me that although this method will work for security, it will require me to modify the static list at every change in the database (however rare those are).
Are there better/proper ways of securing this code against SQL injection attacks?
Create a split function in SQL Server (there are better ones for newer versions, but this is what you get in SQL Server 2000):
CREATE FUNCTION dbo.SplitStrings
(
#List NVARCHAR(4000),
#Delimiter CHAR(1)
)
RETURNS #Items TABLE
(
Item NVARCHAR(4000)
)
AS
BEGIN
DECLARE
#Item VARCHAR(12),
#Pos INT;
WHILE LEN(#List)>0
BEGIN
SET #Pos = CHARINDEX(#Delimiter, #List);
IF #Pos = 0
SET #Pos = LEN(#List)+1;
SET #Item = LEFT(#List, #Pos-1);
INSERT #Items SELECT LTRIM(RTRIM(#Item));
SET #List = SUBSTRING(#List, #Pos + LEN(#Delimiter), LEN(#List));
IF LEN(#List) = 0 BREAK;
END
RETURN;
END
GO
Then create a stored procedure:
CREATE PROCEDURE dbo.RunScaryQuery
#columns NVARCHAR(4000),
#table NVARCHAR(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #collist NVARCHAR(4000), #sql NVARCHAR(4000);
SELECT #collist = COALESCE(#collist + ',', '') + c.name
FROM syscolumns AS c
INNER JOIN dbo.SplitStrings(#columns, ',') AS s
ON s.Item = c.name
WHERE c.id = OBJECT_ID(#table);
SELECT #sql = 'SELECT ' + #collist + ' FROM ' + #table
-- where ...
;
EXEC sp_executesql #sql;
END
GO
Now call that stored procedure from ASP with a properly parameterized command object.
This will ensure that your SQL query is generated only using column names that actually exist in the table. (Any nonsense will be ignored.)
This presumes that you will get at least one valid column name in the list.
I'm at home, no db to test but this should do it
Basically, get all the fields from the db that fit the where, get the requested fields in an array and compare the two lists, putting out only the requested fields.
name = Trim(Request.QueryString("name"))
flds = split(Trim(Request.QueryString("flds")),",")
sql = "set rowcount 0 select * from [TABLE] where Name = '" & name & "'"
set oRst = oConn.execute(sql)
on error resume next
do while not oRst.eof
result = ""
separator = ""
for each field in flds
for each requested_field in flds
if uCase(field.name) = uCase(trim(requested_field)) then
result = result & separator & field.value
separator = ","
end if
next
next
response.write result & "<br>"
oRst.movenext
loop
hm... so I'm going with another solution.
I first have an SQL query that return all the valid fields
select
tcol.name
from
sysObjects tobj
join syscolumns tcol on tobj.id = tcol.id
where
tobj.xtype = 'U'
and tobj.name = '[TABLE]'
and then I validate every element as suggested by #peter. All the validated parameters are then used to build the query string, and name is passed as a parameter in the second query.
This seems to minimize the overhead and the strain on the database.
Have a look at http://www.userfriendlythinking.com/Blog/BlogDetail.asp?p1=7013&p2=119&p7=3001
which shows usage of parameterized queries.
Related
How do I convert this code from T-SQL into DB2 LUW, it seems so easy with T-SQL but in DB2 can't find any solution. See code below:
DECLARE #sqlCommand varchar(1000)
DECLARE #columnList varchar(75)
DECLARE #city varchar(75)
SET #columnList = 'AddressID, AddressLine1, City'
SET #city = '''London'''
SET #sqlCommand = 'SELECT ' + #columnList + ' FROM Person.Address WHERE City = ' + #city
EXEC (#sqlCommand)
The problem is that you can’t ‘select to nowhere’ in a compound statement in DB2. Db2 CLP can return you the result set of a single sql statement, but it doesn’t try to do the same for select statements in a compound statement. If you want to print the result set from a select statement in a compound statement, you can, for example, declare a cursor, fetch it in a loop, and use dbms_output.put_line calls to print the values of variables.
Not Pretty but you can find an example of the bottom of this page:
Stored Procedures and Dynamic SQL Returning a Result set
Essentially you most:
1) create a dynamic SQL string
2) prepare the string into a statement
3) Link the statement to a cursor you're going to declare as WITH RETURN
Opening the cursor will be the last line in your procedure.
We are using a third party product which references a stored procedure in MSSQL. This stored proc looks something like this:
CREATE PROCEDURE [dbo].[example]
#a nvarchar(255)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #strSQL nvarchar(3000)
SET #strSQL = 'SELECT * FROM test WHERE x = ''1'''
IF IsNull(#a, '') <> ''
SET #strSQL = #strSQL + ' AND a = ''' + #a + ''''
EXEC(#strSQL)
END
This stored proc doesn't actually output its results to the website but I'm still sure that it is vulnerable to SQL injection. I can input t' + 'est and get the same result as I would from inputing test.
We obviously need to get them to change this but I need to demonstrate that it is an issue first. How can I do something like insert a row in to a table by passing SQL in as #a? If I do
'; INSERT INTO blah VALUES('test')
Then I get:
Incorrect syntax near ';'.
yes, it's vulnerable, but by chance you've injected the wrong text, producing a syntax error:
SELECT * FROM test WHERE x = "1" AND a =; INSERT INTO blah VALUES('test')
^--your syntax error
If your injection text had been:
a; INSERT blah blah blah
^---
then you'd have ended up with two valid queries and test in your blah table.
Yes, you can set your # to have an secape character and thus create mutiple Execs ulimately leading to execcmd format C: or other - google SQL injection attacks
However:
Create proc db.eg #a nvarchar(255)
AS
BEGIN
Update Mytable SET Mycol = #a WHERE Condition etc..
END
IS not open to SQL injection as the string goes directly to the table column, it is nt exec'd
I'm writing a parameterized stored proc. I know that you can set the parameter value such that it displays all the results when the parameter is not mentioned in the execute command.. But i'm unable to recall how that is achieved. Any help is highly appreciated... Please..
I'd recommend parameterized dynamic sql (sp_executesql)
Going this route, you can discard any irrelevant parameter when building your where clause.
Example procedure:
create proc dbo.SearchForStuff
(
#Id int = 0
,#Description varchar(100) = ''
)
as
begin
set nocount on;
declare #select nvarchar(max) = '
select
s.*
from Stuff as s'
declare #where varchar(max) = ''
if isnull(#ID,0) != 0 begin
set #where += case #where when '' then ' where ' else ' and ' end + 's.Id = #Id'
end
if isnull(#Description,'') != '' begin
set #where += case #where when '' then ' where ' else ' and ' end + 's.[Description] = #Description'
end
set #select += #where
exec sp_executesql
#select
,N'
,#Id int = 0
,#Description varchar(100) = '''''
,#Id
,#Description
end
Usage:
exec SearchForStuff #Id = 1, #Description = 'omg' -- Returns every item where Id is 1 and Description is 'omg'
exec SearchForStuff #Id = 1 -- Returns every item where Id is 1
exec SearchForStuff #Description = 'omg' -- Returns every item where Description is 'omg'
exec SearchForStuff --returns every item
In this fashion your final query is not littered with useless conditions. Further, you can get a bit more granular than I did here. Based upon which parameters were passed, you can tailor your where/join clauses to take advantage of your indexes such that you get the best possible performance. The only drawback is a slight loss of readability (imo).
You can make your WHERE conditions like this:
WHERE (#myParam IS NULL OR #myParam = someValue)
You may be able to use OPTION (RECOMPILE) is SQL2008SP1+ (or similar, don't know other options) in the sproc, depending on your RDBMS, to get this to be performant.
Method from Erland Sommarskog:
http://www.sommarskog.se/dyn-search-2008.html#static
From the link:
"The effect of all the #x IS NULL clauses is that if that input parameter is NULL, then that AND-condition is always true. Thus, the only conditions that are in effect are those where the search parameter has a non-NULL value.
As far as maintainability goes, it's difficult to think of a better solution for the search conditions at hand. It's compact, easy to read and to extend. And performance? Very good as long as you include the query hint OPTION (RECOMPILE). This hint forces the query to be recompiled each time, in which case SQL Server will use the actual variable values as if they were constants."
If it is an int you can use
SELECT X,Y
FROM T
WHERE C BETWEEN COALESCE(#P, -2147483648) AND COALESCE(#P, 2147483647)
The definitive article on the subject
In DB2, I can get a list of tables with the following sql statement:
select tabname from syscat.tables where `tabschema = 'DBO'
Assuming that each table has a field named a1, how can I
loop through the tables and check for a value in that field
in every table?
There are two general ways. One would be to write a program that processes each file to check that column. The program could use embedded SQL to retrieve the count of the chosen value from each table. Or you could create a stored proc that accepts a table and schema name as inputs and sets an output value as essentially a boolean indicator of whether or not that table had the chosen value.
Potentially, you could perhaps create an outer proc to loop through the list of tables. And for each table it would call the inner proc that tests presence of the value.
This is a test proc that I used to verify the basic principle. It checks a column for APFILE='ACCPTH'. It returns either (1) or (0) depending on whether any row has that value or not.
-- Generate SQL
-- Version: V6R1M0 080215
-- Generated on: 03/22/14 02:59:07
-- Relational Database: TISI
-- Standards Option: DB2 for i
DROP SPECIFIC PROCEDURE SQLEXAMPLE.CHKFLDVAL ;
SET PATH "QSYS","QSYS2","SYSPROC","SYSIBMADM","mylib" ;
CREATE PROCEDURE SQLEXAMPLE.CHKFLDVAL (
IN TABLENAME VARCHAR(128) ,
IN SCHEMANAME VARCHAR(128) ,
OUT VALFOUND SMALLINT )
LANGUAGE SQL
SPECIFIC SQLEXAMPLE.CHKFLDVAL
NOT DETERMINISTIC
READS SQL DATA
CALLED ON NULL INPUT
SET OPTION ALWBLK = *ALLREAD ,
ALWCPYDTA = *OPTIMIZE ,
COMMIT = *NONE ,
CLOSQLCSR = *ENDMOD ,
DECRESULT = (31, 31, 00) ,
DFTRDBCOL = *NONE ,
DLYPRP = *NO ,
DYNDFTCOL = *NO ,
DYNUSRPRF = *USER ,
RDBCNNMTH = *RUW ,
SRTSEQ = *HEX
P1 : BEGIN
DECLARE STMTSQL VARCHAR ( 256 ) ;
DECLARE RTNRESULT SMALLINT ;
SET STMTSQL = 'VALUES (select CASE WHEN count(*) = 0 THEN 0 ELSE 1 END as chkVal from ' CONCAT SCHEMANAME CONCAT '.' CONCAT TABLENAME CONCAT ' where APFILE=''ACCPTH'' group by APFILE) INTO ?' ;
PREPARE STMT_NAME FROM STMTSQL ;
EXECUTE STMT_NAME USING RTNRESULT ;
SET VALFOUND = RTNRESULT ;
END P1 ;
COMMENT ON SPECIFIC PROCEDURE SQLEXAMPLE.CHKFLDVAL
IS 'Check field value in some table' ;
If I call it with a different TableName or SchemaName parameter value, I can get different values returned in rtnResult.
SQL is all that's actually needed. It's not a particularly good thing for SQL to do.
You cannot do this using just SQL statements. You will have to do a bit of scripting or programming of some sort to create new queries based on the table names you find and run them.
I have the following statement in a stored procedure. I am passing the name of the column as parameter and also the value to be checked in another variable. Is it possible to accomplish this in SQL server. Please let me know.
SELECT CaseId FROM app_Case
where #SearchCat=#keywords
ORDER BY CreatedDate DESC
I think the only way to do this would be to generate a dynamic SQL statement. The other option would be to take all column values as parameters, default them to null, and check for that.
ie
WHERE (cola = #cola OR #cola IS NULL) AND (colb = #colb OR #colb IS NULL) etc.
You need to create a string of SQL inside the SP and execute it.
Declare #SQL As VARCHAR(8000)
SET #SQL = 'SELECT CaseId FROM app_Case where ' +
#SearchCat + ' = '' + #keywords +
'' ORDER BY CreatedDate DESC'
EXEC(#SQL)
You can build a dynamic query Essentially you build a string and then execute it. (Watch out for SQL injection attacks).
Another approach would be to use a case statement which if you don't have a lot of options might be worth trying:
select CaseId from app_Case
where case when #searchCat='field1'
then field1
else #searchVal
end = #searchVal and
case when #searchCat='field2'
then field2
else #searchVal
end = #searchVal
Another approach is do the same thing using or clauses:
select CaseId from app_Case
where (#searchCat='Field1' and Field1=#searchVal) OR
(#serachCat='Field2' and Field2=#searchVal)