I know this is a loaded question but I want to understand the theory here.
I've tried googling and checking SO for this and i DO understand the SQL injection threat.
My question is: Do you ALWAYS needs parametised queries?
eg.
If I have a user input box to search for "pxName"
I have a string:
"SELECT * FROM tblPeople WHERE fName = '" & pxName & "'"
I completely see the risk here, we are open to attack because the user has the power to insert what they want into the input box.
If however I have a variable that I have created.
eg.
Created new record via an ADODB recordset.
Obtained new PK
run query:
"SELECT * FROM tblPeople WHERE personID = " & NewPrimaryKey
Is this a threat?? As in, should this be parametised as parametised queries are "what you do" or am I actually at risk of SQL injection even though there is no place for a user to even input their dodgy strings to perform an attack?
The main fact is I have created an access app just for me to use and now a bunch of friends with similar businesses are keen to try it. It isn't using Parametisation at all at the moment however of maybe 50 query strings in the VBA, there are only 2 with actual user input boxes, the rest are reports and self generated variable queries like the one above.
Thanks!
Related
Coming from years of web development where PHP and SQL statements were so simple, this recent task I've been required to undergo with MS Access and VBA is absolutely doing my head in at how much it complicates SQL statements. Mind you I have no prior knowledge about VBA so it could be extremely simple and I'm not just getting it, but all I want to do is
"SELECT type FROM tblMatter WHERE id='$id'"
When I wear my PHP cap, I want to think okay, we are going to have one row of data, that's going to be an array, and I want one object out of that array. Simple.
VBA, however, complicates the $#!t out of it. So far my code looks something like this
Dim matterSQL As String
Dim matterRS As Recordset
matterSQL = "SELECT type FROM tblMatter WHERE id'" & id & "'"
Set matterRS = CurrentDb.OpenRecordset(matterSQL)
MsgBox matterRS![type]
CurrentDb is defined much much earlier in the code to open the connection to the database, and the error is on the line containing OpenRecordset with the error: Data type mismatch in criteria expression.
As I said, I'm new to VBA so I don't know what the heck I'm doing, and all the documentation on the internet is nowhere near helpful. But all I want to do is to get one piece of data from the table.
Thanks in advance!
Edit: I needed to build upon this with another query that takes the info from the last query to run. Same kind of ordeal:
Dim costSQL As String
Dim costRS as Recordset
costSQL = "SELECT email FROM tblScaleOfDisb WHERE category=" & category
Set costRS = CurrentDb.OpenRecordset(costSQL)
MsgBox costRS![email]
This time I'm getting an error on the line containing OpenRecordset with the error: Too few parameters. Expected 1.
Which I don't understand because the code is practically the same as the first half of the question. What have I done wrong?
You are missing = in the condition
try below
matterSQL = "SELECT type FROM tblMatter WHERE id='" & id & "'"
Also if id is numeric you don't need '
matterSQL = "SELECT type FROM tblMatter WHERE id=" & id
Too few parameters. Expected 1.
This happens when the field name in your sql query do not match the table field name
if the field name are correct i believe the the datatype of category is not numeric then you have to use '
costSQL = "SELECT email FROM tblScaleOfDisb WHERE category='" & category &"'"
Always try to use parameterised query to avoid SQL injection
You must understand or prepare few things before you start coding on a new platform. such as
Using Keywords/ Reserved keywords
Capturing Errors
basic arithmetic operations/ string operations.
Available functions / methods
Ways of cleaning your variables after using it
in your case you also need to learn about MS ACCESS SQL. Which is pretty similar to standard SQL but (limited to and) strongly influenced by MS Access internal functions.
SQL execution will return n Rows as result. Each row will have n number of columns. You need to understand how you need to loop through result sets.
Please do have some error capturing method. I will help you to understand the direction before spending hours in Google.
in your first SQL: you have a reserved keyword Type. use square brackets to escape reserved keywords. In where condition numeric fields must not have string quotes and strings must have them.
Tip: You can use the MS Access visual query builder to build your query and copy the SQL to VBA.
list of reserved keywords in MS ACCESS: https://support.microsoft.com/en-us/kb/286335
list of functions: http://www.techonthenet.com/access/functions/
Error handling : http://allenbrowne.com/ser-23a.html
Clean/close your objects after usage by explicitly setting as nothing: Is there a need to set Objects to Nothing inside VBA Functions
An intern has written the following vb.net code:
Public Sub PopulateUsersByName (name As String)
'Here, the parameter 'name' is unsantized, coming straight from the form
Dim query As String = ""
query += vbNewLine + " SELECT Id, FirstName, LastName FROM dbo.[Users]"
query += vbNewLine + " WHERE FirstName LIKE '" + REPLACE(name, "'", "''") + "'"
query += vbNewLine + " OR LastName LIKE '" + REPLACE(name, "'", "''") + "'"
'Execute SQLCommand with above query and no parameters
'Then do some other stuff
END Sub
I have explained that in general, one should never use string concatenation when trying to do something like the above. The best practice is to use either an SP, or an SQLClient.SQLCommand with parameters.
His logic is: any sql varchar(xxx) gets sanitized by replacing all single quotes with two single quotes (and adding additional single quotes at the start and end of the string).
I am unable to provide an example of something the user could type that would get around this - I'm hoping I can find something more convincing than "But, as a general principal, one should avoid this - you know... coz... well, don't argue with me - I'M THE BOSS AND MY WISH IS YOUR COMMAND.".
Note: The code will always connect to our Microsoft SQL Server. But I can imagine it failing to sanitize the input on some other SQL Implementation.
UPDATE:
Just to make it a little clearer, what I'm looking for is a possible value of the parameter name which will allow someone to inject SQL into the query.
Try this with his code using '%_%' (with and without the single quotes) as the input....same as this in SQL....
select SELECT Id, FirstName, LastName FROM dbo.[Users] from TBL_EMPLOYEES where FirstName like '%_%' or LastName like '%_%'
irrespective if it failing or not, his is very poor code... fair enough this one is only a one liner, but anything more than that, including complicated SQL statements would be difficult to maintain and debug.. in any case, using Sps gets him used to using them AND allows him to take advantage of the flexibility and power of T-SQL... LOL, i'd dread to think how some of the SQL I have written would look like in code.....
You know, I come across code like this (and a lot worse) all the time... just because it might work for a while DOESN'T mean it's the right way to do it.... If your intern that does NOT listen to experience he will NEVER make a good developer and that is sadly a fact
I once reduced a junior developers attempt at importing a CSV file (50 million rows) in the same way your intern has done, from her 300 lines of code (which was never going to work) to just one line of LINQ to convert it to XML (we couldn't use Bulk Insert or bcp) and a fancy SP... Was bullet proof, job done....
I can get a list of all your users. If name = %%
Now I know the full name of everyone in your database.
I would consider that a security hole.
Sanitizing is not the answer. You can by pass quotes and use "smuggling". A good example is http://danuxx.blogspot.com.br/2011/08/sql-injection-bypassing-single-quotes.html
Also a good practice (to use dynamic queries) is to use parametric dynamic queries. Also SPs can do the trick (if you don't use dynamic queries inside it off course).
I'm working on some VB6 code to connect to a database to return a specific subset of data. All the info needed is in the one table but I'm having a bit of trouble in establishing the correct approach to selecting my results based off of the results from another query.
Below is the SQL generated by Access when I created my queries there but I'm trying to convert them for use in my VB6 application:
'*EDIT --
" & Chr(34) & lstAb.Text & Chr(34) & "
The above thanks to David pushed steered this code in the right direction. From there, I used the debug window to confirm my selections in Access which was truly a magnificent experience. Had to tweak my aliases a bit but everything became very apparent when Access was asking me to key in other values that should've already been declared.
Thanks again to David!
You might be far better off doing some proper joins between the two tables, rather than specifying a sub-query. However, that's not the problem, I don't think.
Looking at the code, I believe that you're missing some quotation marks around your criteria. You say:
"...clone_id = " & lstAb.Text & ") ORDER..."
This should probably be:
"...clone_id = " & Chr(34) & lstAb.Text & Chr(34) & ") ORDER..."
to give you quotations around the text found in lstAb.Text.
An easy way to test these kind of functions is to Debug.Print(sSql) and look at the Debug window to see what the SQL is looking like. Take a copy of that SQL, drop it into a new query in MSAccess (you can tell the query to "View SQL"), then try to figure out where it's going wrong.
This is much easier to see if your query doesn't have a nested query in it, as you can view the query designer.
You specify Details.func_ID) Like "Simp_*". In your data sample, there are no records with Simp_ string in it
Let me start by saying I am biased; I hate dynamic SQL under all circumstances. That being said, is this scenario considered good practice for dynamic SQL?
sqlDataSourceObject.SelectCommand = String.Concat(
"select top ", maxRows,
" col1, ",
" col2 as myData, ",
" '' as blah, ",
" col3 as Fromperson ",
" 'Corporate' as toPerson, ",
" Convert(char(11), orderDate) as orderDate, ",
" carrier, ",
sqlString1,
sqlString2,
sqlString3 + " AND areaCode = '" + currArea + "'"
);
This query may run once, then change the value for sqlString1,2,3, or currArea and run it again against a different SqlDataSource.
This code makes me angry to read. Its hard to read, it can change with the sqlString variables, I cant run it without copy/pasting into SSMS and I have to go track down several variables to make a single change.
But, like I said I am biased so I am asking you. Is this code, written in 2001 before LINQ, as good as a stored proc or some other technology, generally OK from a good practice perspective?
If not, how would you have improved it (remember no LINQ, this is 2001).
A point of clarification:
Dynamic SQL is normally taken to mean that the semantics of the statement change based on some external factor. In other words, the column names or even the base table(s) might be altered. This was common to do for pivot queries in the old days.
It's kind of hard to tell because I don't know what's going into those awfully-named sqlStringX parameters, but I think that what I'm seeing here is really just inline SQL which happens to be riddled with SQL injection vulnerabilities. It is trivially easy to parameterize. Fix this ASAP, please. Inline SQL is fine but there is no reason to be using raw strings instead of parameters.
Stored procedures would be one idea of how to better handle these types of queries. Granted the stored proc may just execute what the parameters pass but that would be my suggestion for one way to improve that code so that the DBA can know what indexes may be useful to help optimize the query. SQL injection attacks as #Jarrod Roberson points out are also quite likely with this kind of code.
PS: I wrote this kind of code back in 1998 where I had ~20 possible parameters in writing a "Find Customer" routine that was one of my first assignments out of university so I do understand where this kind of code can originate.
I'd use a stored procedure myself. But in any case, no matter what, use parameters. They way you' have it there is not secure at all, and as you say, makes me angry to look at. :-)
Here's one reference that might help (not stored procs per se, but still uses parms)
http://www.asp.net/data-access/tutorials/using-parameterized-queries-with-the-sqldatasource-vb
I built a prototype system with some database queries. Since it was just a prototype and I was very new to databases, I just used a direct string. This is the string I used and it works fine:
command = New OleDbCommand("SELECT * FROM " + prefix + "CanonicForms WHERE Type=1 AND Canonic_Form='" + item + "'", dictionary_connection)
Now, in putting it in a real system, I wanted to use the more secure parametized method, so after some googling I came up with this:
command = New OleDbCommand("SELECT * FROM #prefix WHERE Type=1 AND Canonic_Form=#form", dictionary_connection)
command.Parameters.AddWithValue("#prefix", prefix + "CanonicForms")
command.Parameters.AddWithValue("#form", item)
But all I get is an error for an incomplete query clause. What have I done differently between the two?
Your table name can't be a parameter. You might have to do some form of concatenation. It's not really a parameter in the formal sense of the word.
As ek_ny states, your table name can not be a parameter.
If you are really paranoid about injection then check the passed in table name against a white list of allowable values prior to buidling up the SQL string.
#cost, I agree that it would be nice but its just not a feature that exists. Generally its assumed that your WHERE clauses are the dynamic portion of the query and everything else, including the SELECT list and the table name, are static. In fact, most of the WHERE clause isn't even dynamic, only the search values themselves. You can't dynamically add column names this way either. This could definitely be built but it would require that the query engine be more aware of the database engine which is a can of worms that Microsoft didn't feel like opening.
Imagine a drop down menu with table names such as 'Jobs', 'People', 'Employers' that some naive developer built. On postback that value is used to SELECT * FROM. A simple SQL injection would be all that it takes to jump to another table not listed in that menu. And passing something really weird could very easily throw an error that could reveal something about the database. Value-only parametrized queries can still be broken but the surface area is much smaller.
Some people with multiple table prefixes use schemas, is that an option for you?