I've heard that "everyone" is using parameterized SQL queries to protect against SQL injection attacks without having to vailidate every piece of user input.
How do you do this? Do you get this automatically when using stored procedures?
So my understanding this is non-parameterized:
cmdText = String.Format("SELECT foo FROM bar WHERE baz = '{0}'", fuz)
Would this be parameterized?
cmdText = String.Format("EXEC foo_from_baz '{0}'", fuz)
Or do I need to do somethng more extensive like this in order to protect myself from SQL injection?
With command
.Parameters.Count = 1
.Parameters.Item(0).ParameterName = "#baz"
.Parameters.Item(0).Value = fuz
End With
Are there other advantages to using parameterized queries besides the security considerations?
Update: This great article was linked in one of the questions references by Grotok.
http://www.sommarskog.se/dynamic_sql.html
The EXEC example in the question would NOT be parameterized. You need parameterized queries (prepared statements in some circles) to prevent input like this from causing damage:
';DROP TABLE bar;--
Try putting that in your fuz variable (or don't, if you value the bar table). More subtle and damaging queries are possible as well.
Here's an example of how you do parameters with Sql Server:
Public Function GetBarFooByBaz(ByVal Baz As String) As String
Dim sql As String = "SELECT foo FROM bar WHERE baz= #Baz"
Using cn As New SqlConnection("Your connection string here"), _
cmd As New SqlCommand(sql, cn)
cmd.Parameters.Add("#Baz", SqlDbType.VarChar, 50).Value = Baz
Return cmd.ExecuteScalar().ToString()
End Using
End Function
Stored procedures are sometimes credited with preventing SQL injection. However, most of the time you still have to call them using query parameters or they don't help. If you use stored procedures exclusively, then you can turn off permissions for SELECT, UPDATE, ALTER, CREATE, DELETE, etc (just about everything but EXEC) for the application user account and get some protection that way.
Definitely the last one, i.e.
Or do I need to do somethng more extensive ...? (Yes, cmd.Parameters.Add())
Parametrized queries have two main advantages:
Security: It is a good way to avoid SQL Injection vulnerabilities
Performance: If you regularly invoke the same query just with different parameters a parametrized query might allow the database to cache your queries which is a considerable source of performance gain.
Extra: You won't have to worry about date and time formatting issues in your database code. Similarly, if your code will ever run on machines with a non-English locale, you will not have problems with decimal points / decimal commas.
You want to go with your last example as this is the only one that is truly parametrized. Besides security concerns (which are much more prevalent then you might think) it is best to let ADO.NET handle the parametrization as you cannot be sure if the value you are passing in requires single quotes around it or not without inspecting the Type of each parameter.
[Edit] Here is an example:
SqlCommand command = new SqlCommand(
"select foo from bar where baz = #baz",
yourSqlConnection
);
SqlParameter parameter = new SqlParameter();
parameter.ParameterName = "#baz";
parameter.Value = "xyz";
command.Parameters.Add(parameter);
Most people would do this through a server side programming language library, like PHP's PDO or Perl DBI.
For instance, in PDO:
$dbh=pdo_connect(); //you need a connection function, returns a pdo db connection
$sql='insert into squip values(null,?,?)';
$statement=$dbh->prepare($sql);
$data=array('my user supplied data','more stuff');
$statement->execute($data);
if($statement->rowCount()==1){/*it worked*/}
This takes care of escaping your data for database insertion.
One advantage is that you can repeat an insert many times with one prepared statement, gaining a speed advantage.
For instance, in the above query I could prepare the statement once, and then loop over creating the data array from a bunch of data and repeat the ->execute as many times as needed.
Your command text need to be like:
cmdText = "SELECT foo FROM bar WHERE baz = ?"
cmdText = "EXEC foo_from_baz ?"
Then add parameter values. This way ensures that the value con only end up being used as a value, whereas with the other method if variable fuz is set to
"x'; delete from foo where 'a' = 'a"
can you see what might happen?
Here's a short class to start with SQL and you can build from there and add to the class.
MySQL
Public Class mysql
'Connection string for mysql
Public SQLSource As String = "Server=123.456.789.123;userid=someuser;password=somesecurepassword;database=somedefaultdatabase;"
'database connection classes
Private DBcon As New MySqlConnection
Private SQLcmd As MySqlCommand
Public DBDA As New MySqlDataAdapter
Public DBDT As New DataTable
Public BindSource As New BindingSource
' parameters
Public Params As New List(Of MySqlParameter)
' some stats
Public RecordCount As Integer
Public Exception As String
Function ExecScalar(SQLQuery As String) As Long
Dim theID As Long
DBcon.ConnectionString = SQLSource
Try
DBcon.Open()
SQLcmd = New MySqlCommand(SQLQuery, DBcon)
'loads params into the query
Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))
'or like this is also good
'For Each p As MySqlParameter In Params
' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
' Next
' clears params
Params.Clear()
'return the Id of the last insert or result of other query
theID = Convert.ToInt32(SQLcmd.ExecuteScalar())
DBcon.Close()
Catch ex As MySqlException
Exception = ex.Message
theID = -1
Finally
DBcon.Dispose()
End Try
ExecScalar = theID
End Function
Sub ExecQuery(SQLQuery As String)
DBcon.ConnectionString = SQLSource
Try
DBcon.Open()
SQLcmd = New MySqlCommand(SQLQuery, DBcon)
'loads params into the query
Params.ForEach(Sub(p) SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value))
'or like this is also good
'For Each p As MySqlParameter In Params
' SQLcmd.Parameters.AddWithValue(p.ParameterName, p.Value)
' Next
' clears params
Params.Clear()
DBDA.SelectCommand = SQLcmd
DBDA.Update(DBDT)
DBDA.Fill(DBDT)
BindSource.DataSource = DBDT ' DBDT will contain your database table with your records
DBcon.Close()
Catch ex As MySqlException
Exception = ex.Message
Finally
DBcon.Dispose()
End Try
End Sub
' add parameters to the list
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New MySqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
End Class
MS SQL/Express
Public Class MSSQLDB
' CREATE YOUR DB CONNECTION
'Change the datasource
Public SQLSource As String = "Data Source=someserver\sqlexpress;Integrated Security=True"
Private DBCon As New SqlConnection(SQLSource)
' PREPARE DB COMMAND
Private DBCmd As SqlCommand
' DB DATA
Public DBDA As SqlDataAdapter
Public DBDT As DataTable
' QUERY PARAMETERS
Public Params As New List(Of SqlParameter)
' QUERY STATISTICS
Public RecordCount As Integer
Public Exception As String
Public Sub ExecQuery(Query As String, Optional ByVal RunScalar As Boolean = False, Optional ByRef NewID As Long = -1)
' RESET QUERY STATS
RecordCount = 0
Exception = ""
Dim RunScalar As Boolean = False
Try
' OPEN A CONNECTION
DBCon.Open()
' CREATE DB COMMAND
DBCmd = New SqlCommand(Query, DBCon)
' LOAD PARAMS INTO DB COMMAND
Params.ForEach(Sub(p) DBCmd.Parameters.Add(p))
' CLEAR PARAMS LIST
Params.Clear()
' EXECUTE COMMAND & FILL DATATABLE
If RunScalar = True Then
NewID = DBCmd.ExecuteScalar()
End If
DBDT = New DataTable
DBDA = New SqlDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
' CLOSE YOUR CONNECTION
If DBCon.State = ConnectionState.Open Then DBCon.Close()
End Sub
' INCLUDE QUERY & COMMAND PARAMETERS
Public Sub AddParam(Name As String, Value As Object)
Dim NewParam As New SqlParameter(Name, Value)
Params.Add(NewParam)
End Sub
End Class
This question already has an answer here:
When clicking "btnLogin", my forms decrease in size.
(1 answer)
Closed 4 years ago.
Whenever my program needs too query the database, it'll result in GUI bugs like the form fonts changing randomly, the form itself resizing, the data in the datagrid being mixed together, and it affect the overall program, not just the form that is calling the query function.
I use this connection
"Provider=Microsoft.ACE.OLEDB.12.0;"
Public DBDA As OleDbDataAdapter
Public DBDT As DataTable
DBDT = New DataTable
DBDA = New OleDbDataAdapter(DBCmd)
RecordCount = DBDA.Fill(DBDT)
and here is my Execute Query function:
Public Sub ExecQuery(Query As String)
'RESET QUERY STATS
RecordCount = 0
Exception = ""
Try
'OPEN A CONNECTION
DBCon.Open()
'CREATE DB COMMAND
DbCmd = New OleDbCommand(Query, DBCon)
'LOAD PARAMETERS INTO COMMAND
Params.ForEach(Sub(Par) DbCmd.Parameters.Add(Par))
'CLEAR PARAMETERS LIST
Params.Clear()
'EXECUTE COMMAND & FILL DATA
DBDT = New DataTable
DBDA = New OleDbDataAdapter(DbCmd)
RecordCount = DBDA.Fill(DBDT)
Catch ex As Exception
Exception = ex.Message
End Try
'CLOSE CONNECTION
If DBCon.State = ConnectionState.Open Then DBCon.Close()
End Sub
and an example of how I query from a form
Private QC As New QueryControl
Public Sub RefreshGrid()
'RUN QUERY
QC.ExecQuery("SELECT * FROM Database")
If Not String.IsNullOrEmpty(QC.Exception) Then MsgBox(QC.Exception) : Exit Sub
'POPULATE DATAGRID
Datagridview1.DataSource = QC.DBDT
End Sub
I tried running the debugging the program while commenting each line individually and concluded that the ExecQuery() function is what causes the issue. Anyone met something similar?
I had the problem of the form resizing. It was the 32 bit Ace provider. I downloaded the 64 bit Ace provider and installed it. In the solution properties uncheck the prefer 32 bit box and all should be well.
I'm attempting to overload the "Delete" method of a TableAdapter (approach). How can I execute an SQL statement from 'here' to handle the delete?
I've got:
Namespace AFL_BackendDataSetTableAdapters
Partial Class Log_entry_unitTableAdapter
Public Overloads Function Delete(ByVal LogEntryID As Integer) As Integer
Dim SQL As String
SQL = "DELETE FROM log_entry_unit WHERE log_entry_unit_id=" & LogEntryID
'?????.Execute SQL
Return 0
End Function
End Class
End Namespace
The overload is working fine, but I don't know how to do the hard part and actually manipulate the data from here. Previously, I've just gone into the Dataset Designer and manually updated the generated methods to work like I want them, but whenever I use the wizard to regenerate the dataset, that (as expected) gets overwritten.
I've previously only ever manipulated Data using the generated methods, and now I'm stuck.
EDIT w/ Final Answer
Based on William's help below here's the final working solution (Note I just had to use OleDb instead of SQL since my Dataset is Access:
Imports System.Data.OleDb
Namespace AFL_BackendDataSetTableAdapters
Partial Class Log_entry_unitTableAdapter
Public Overloads Function Delete(ByVal LogEntryID As Integer) As Integer
Dim queryString As String = "DELETE FROM log_entry_unit WHERE log_entry_unit_id=" & LogEntryID
Dim command As New OleDbCommand(queryString, Connection)
Dim r As Integer
Try
Connection.Open()
r = command.ExecuteNonQuery()
Catch ex As Exception
MsgBox(ex.Message)
r = 0
Finally
Connection.Close()
End Try
Return r
End Function
End Class
End Namespace
I hardcoded a connection string for reference only. This should be in a config file. As an example:
Dim connectionString As String = _
"Data Source=(local);Initial Catalog=YourDatabase;" _
& "Integrated Security=true"
Dim queryString As String = "DELETE FROM log_entry_unit WHERE log_entry_unit_id=" & LogEntryID
' Create and open the connection in a using block. This
' ensures that all resources will be closed and disposed
' when the code exits.
Using connection As New SqlConnection(connectionString)
' Create the Command
Dim command As New SqlCommand(queryString, connection)
' Open the connection in a try/catch block.
Try
connection.Open()
command.ExecuteNonQuery()
Catch ex As Exception
' handle exception here
End Try
End Using
EDIT
I probably should of mentioned you will probably want to fill your adapter again after the delete.
Aiming to load a table from a db3 (sqlite) file into a datatable. Then load this datatable into a datagridview to allow editing of it. Then I want to save the edited datatable back to the original db3 file - overwriting any changes.
I've got as far as getting the table from the db3 + into the DGV. I wont include any code around the datatable to dgv. Just needing how to write this datable back to the db3 file. How do I do this? Code:
Imports System.Data.SQLite
Public Class DBOps
Public Function ImportGEDb3(Filepath As String) As DataTable
Dim dt As New DataTable("Data")
Dim cnn As New SQLiteConnection("Data Source='" & Filepath & "'")
cnn.Open()
Dim mycommand As New SQLiteCommand(cnn)
mycommand.CommandText = "Select * from Data"
Dim reader As SQLiteDataReader = mycommand.ExecuteReader()
dt.Load(reader)
reader.Close()
cnn.Close()
Return dt
End Function
Public Sub SaveGEDb3(dt as datatable, filepath as string)
' save passed datatable to File above
end sub
end class
The SQLiteDataAdapter class is the simplest choice for this kind of updates. The method Update of the class scans the DataTable passed and for each row that has the RowState property different from DataRowState.Unchanged executes the appropriate INSERT, DELETE, UPDATE command (if the SELECT command extracts from the DataTable the primary key). So assuming that your SELECT * FROM Data returns also the primary key of the table you could change your code to take advantage of the SQLiteDataAdapter functionality
Imports System.Data.SQLite
Public Class DBOps
' Global because you create it in the ImportGEDb3 and use it in the SaveGEDB3 '
Private daImport As SQLiteDataAdapter
Public Function ImportGEDb3(Filepath As String) As DataTable
Dim dt As New DataTable("Data")
daImport = new SQLiteDataAdapter("Select * from Data", "Data Source='" & Filepath & "'")
daImport.Fill(dt)
' This is critical, it is the SQLiteCommandBuilder that takes '
' the SQLiteDataAdapter SELECT statement and builds the required'
' INSERT,UPDATE,DELETE commands.'
Dim builder = new SQLiteCommandBuilder(daImport)
Return dt
End Function
Public Sub SaveGEDb3(dt as datatable)
if daImport IsNot Nothing Then
daImport.Update(dt)
End If
End Sub
End class
I am using vb.net to load data from sql server database. I have defined a dataset in my module
and want to use it in many forms Public dsDataset As DataSet. When I load the main form I load the options that are likely to be the same in all the forms like the departments and sections, so that I do not have to load them again in all the forms. When Navigating from main form to an other form I pass this dataset in a the form constructor.
In the main form I am loading these options:
Sub loadOptions()
Dim da As SqlDataAdapter
Dim sql As String
Try
sqlConn = New SqlConnection(connString)
sqlConn.Open()
sql = " select depId, name from DEPARTMENT "
da = New SqlDataAdapter(sql, sqlConn)
da.Fill(dsDataset, "department")
sql = " select select depId, sectionId, name from SECTION "
da = New SqlDataAdapter(sql, sqlConn)
da.Fill(dsDataset, "section")
'----------------------------------------------------------------------
sqlConn.Close()
Catch ex As Exception
sqlConn.Close()
MsgBox(Err.Description)
End Try
End Sub
Then lets say I have printers form where I load data from from the database and use the option in the dsDataset instead of loading them again. I pass the dataset as follows:
Dim printers As frmPrinters = New frmPrinters(dsDataset)
printers.ShowDialog()
and in the printers form I have a constructor as follows:
Sub New(ByRef dsDataset As DataSet)
InitializeComponent()
cmbDepartment.DataSource = dsDataset.Tables("department")
cmbDepartment.DisplayMember = "name"
cmbDepartment.ValueMember = "depId"
cmbSection.DataSource = dsDataset.Tables("section")
cmbSection.DisplayMember = "name"
cmbSection.ValueMember = "sectionId"
End Sub
and load all the printers to the dataset with no problem:
sql = "select * from printer where printerId=" & printerId
daPrinter = New SqlDataAdapter(sql, sqlConn)
daPrinter.Fill(dsDataset, "printer")
dgvPrinters.DataSource = dsDataset.Tables("printers")
Now when I double click any of the printers I go an other form to load the details of that particular printer as follows:
printerId = dgvPrinters.Rows(dgvPrinters.CurrentRow.Index).Cells(0).Value
printerMode = "modify"
Dim printer As New frmPrinter(dsDataset)
printer.ShowDialog()
In the new form I use the same constructor above but the problem happens there. The following error comes:
Value cannot be null.
Parameter name: dataSet
What I am doing wrong?
It doesn't look like you are saving the dsDataset anywhere in your constructor. For example in a private variable. My guess is that inside frmPrinters you will have a public dsDataSet variable which you never set to anything.
So what you should do is the following:
Create a private dsDataSet variable in frmPrinter called _dsDataset:
Private _dsDataset As DataSet
In the constructor do this:
_dsDataset = dsDataset
And finally in the doubleclick event send the private variable:
Dim printer As New frmPrinter(_dsDataset)
And then you should be fine.