2

I have my Select Insert statement and I'm wondering if I should prevent my code from SQL injection. This code is using BULK Insert and TEMP TABLE. I have never use this before and I'm not familiar if I can/should use cfqueryparam in this case or there is something else that I can apply in this case? Here is my code:

<cfquery datasource="testDB" name="InsertBulk">
    IF OBJECT_ID('tempdb..##TEMP_TBL') IS NOT NULL DROP TABLE ##TEMP_TBL;

    CREATE TABLE ##TEMP_TBL (#cols#)

    BULK INSERT ##TEMP_TBL
    FROM 'D:\myFiles\myTXT.txt'
    WITH (
        FIELDTERMINATOR = '\t', 
        ROWTERMINATOR = '\n'
    )
</cfquery>

<cfquery datasource="testDB" name="InsertUpdate">
    INSERT INTO myRecords(
        FIRST_NAME,
        LAST_NAME,
        GENDER,        
        DOB
    )
    SELECT 
        CASE WHEN LEN(LTRIM(RTRIM(FIRST_NAME))) <> 0 OR FIRST_NAME <> 'NULL' THEN FIRST_NAME END,
        CASE WHEN LEN(LTRIM(RTRIM(LAST_NAME))) <> 0 OR LAST_NAME <> 'NULL' THEN LAST_NAME END,
        CASE WHEN LEN(LTRIM(RTRIM(GENDER))) <> 0 OR GENDER <> 'NULL' THEN GENDER END,
        CASE WHEN LEN(LTRIM(RTRIM(BIRTH_DATE))) <> 0 OR BIRTH_DATE <> 'NULL' THEN BIRTH_DATE END
    FROM ##TEMP_TBL AS TempInsert 
    WHERE NOT EXISTS (
        SELECT * 
        FROM myRecords AS Dups
        WHERE Dups.userID = TempInsert.user_ID
    )

    UPDATE Records
    SET 
        Records.FIRST_NAME = CASE WHEN LEN(LTRIM(RTRIM(Temp.FIRST_NAME))) <> 0 OR Temp.FIRST_NAME <> 'NULL' THEN Temp.FIRST_NAME END,
        Records.LAST_NAME = CASE WHEN LEN(LTRIM(RTRIM(Temp.LAST_NAME))) <> 0 OR Temp.LAST_NAME <> 'NULL' THEN Temp.LAST_NAME END,
        Records.GENDER = CASE WHEN LEN(LTRIM(RTRIM(Temp.GENDER))) <> 0 OR Temp.GENDER <> 'NULL' THEN Temp.GENDER END,
        Records.DOB = CASE WHEN LEN(LTRIM(RTRIM(Temp.BIRTH_DATE))) <> 0 OR Temp.BIRTH_DATE <> 'NULL' THEN Temp.BIRTH_DATE END,
    FROM myRecords AS Records
        INNER JOIN ##TEMP_TBL AS Temp
            ON Records.userID = Temp.user_ID
    WHERE Records.userID = Temp.user_ID
</cfquery>

I have approached my problem using bulk because I tried to avoid using cfloop and creating multiple INSERT/UPDATE statements.

Matt
  • 74,352
  • 26
  • 153
  • 180
espresso_coffee
  • 5,980
  • 11
  • 83
  • 193
  • Asking if a specific thing is "ok" is fine on Stack Overflow, but for more open ended "improvement" requests, codereview.stackexchange.com is better suited. With this in mind, I've removed the last few sentences from your question. – Matt Nov 01 '16 at 08:34
  • @Matt I haven't heard of those two that you listed above. Thanks for letting me know. – espresso_coffee Nov 01 '16 at 13:12

1 Answers1

1

No, you cannot use cfqueryparam here. However, with the possible exception of #cols# you do not need it for those statements.

CFQueryparam is designed to prevent literals (ie simple strings, dates, etcetera) from being executed as sql commands. The SELECT statement does not need to be protected because it does not contain any literals that could be interpreted as commands.

The one possible risk in the statements above is the #cols# variable, which seems to represent a list of column names. Object names (column names, table names, etcetera) must be interpreted as part of a command. Using cfqueryparam is designed to prevent that from happening. So it cannot be used to protect the #cols# variable. If that variable is client supplied, you must do any scrubbing yourself.

Keep in mind, there are still risks even after data is saved to the database. Even if you are using cfqueryparam, malicious values can still be inserted the database. CFQueryparam does not magically make an input value safe. It just prevents any malicious commands within the value from being executed (and only in the current statement). It will not stop an INSERT or UPDATE from saving the value(s) to the database, and posing a risk later on. For applications using any kind of dynamic SQL, stored values can still pose a risk through second order SQL injection:

Second order SQL injection occurs when submitted values contain malicious commands that are stored rather than executed immediately. In some cases, the application may correctly encode an SQL statement and store it as valid SQL. Then, another part of that application without controls to protect against SQL injection might execute that stored SQL statement. This attack requires more knowledge of how submitted values are later used....

So it is very important to always use cfqueryparam, and to avoid dynamic SQL that does not use bind variables. For example in CF, avoid any use of PreserveSingleQuotes, or in SQL Server stored procedures, avoid EXEC and use sp_executeSQL if needed.

Community
  • 1
  • 1
Leigh
  • 28,765
  • 10
  • 55
  • 103
  • As an aside, any data scrubbing and filtering (ie trim, etcetera) should be done to the temp table, not in the UPDATE. Keep the UPDATE short and sweet. – Leigh Oct 25 '16 at 18:25
  • is there any way to prevent Injection? Your last paragraph makes me little confused on what I'm missing in my code. – espresso_coffee Oct 25 '16 at 20:27
  • 1
    See the updated answer. In short, always cfqueryparam and avoid (unparameterized) dynamic sql. – Leigh Oct 25 '16 at 21:32
  • can you provide any example of how cfqueryparam can be applied to my variable #cols#? Do I have to loop and set cfparam individually or there is some more efficient way to do that? Thanks for the answer. – espresso_coffee Oct 26 '16 at 02:18
  • Like I said, you cannot. Trying to use cfqueryparam on a column name will just cause an error. There is no really no shortcut. You have to validate each name yourself against a list of "good" columns. Though it raises the question, why does the create portion need to be dynamic? – Leigh Oct 26 '16 at 02:47
  • I have my client side validation that checks if all columns are in the correct format so if they are on the server side I'm taking the first row from the text file and creating my temp table columns. Also second reason is because number of columns can vary. First option is 4 columns and second is 8. So each tie I have to check the # of columns and then do the insert/update. I have provided just first option because my question was related to SQL injection. I hope this explains your question. Thanks for your help. – espresso_coffee Oct 26 '16 at 02:53
  • (Edit) You should still re-validate on the server side. As long as you do that, it is fine to use a variable for `#cols#`. The temp table column names do not *have* to match what is in the file. They can be anything you want, like 'Col1,Col2,Col3,...'. The only requirement is that the *number* of columns, and their data types, match what you are importing. – Leigh Oct 26 '16 at 12:57
  • In my case columns always have to be in the same order and have the same name. So I'm asking is the better option to hard code the temp table column names instead of taking them out of the text file? – espresso_coffee Oct 26 '16 at 13:12
  • I meant what is technically possible, versus how the app is currently coded :) Just to clarify, like I said there is nothing wrong with using the column names from the text file - *as long as you validate them server side*. – Leigh Oct 26 '16 at 14:02