I have written a CLR stored procedure in C# like this
[Microsoft.SqlServer.Server.SqlProcedure]
public static void IsUserNameExists(string strUserName, out SqlBoolean returnValue)
{
using (SqlConnection connection = new SqlConnection("context connection=true"))
{
connection.Open();
SqlCommand command = new SqlCommand("Select count(UserName) from [User] where UserName='" + strUserName + "'", connection);
int nHowMany = int.Parse(command.ExecuteScalar().ToString());
if (nHowMany > 0)
returnValue = true;
else
returnValue = false;
}
}
Is it vulnerable to SQL injection? I am using SqlParameter. Any best practises?
The only correct way to prevent sql injection should be using parameterized queries.
What you are doing is not safe, since you are concatenating strings.
Look into this here for reference How do parameterized queries help against SQL injection?
For clearification, why your code is vulnerable:
In terms of SQLParameter even something like '); DROP TABLE YourTable;-- will be a valid input (since it is a string). This will then be used by you to create the inner query and there's your SQL-Injection.
Is it vulnerable to SQL injection?
It is:
SomeType.IsUserNameExists("'; insert into [User](UserName) values ('Malefactor_Username'); select '1", out returnValue);
Any best practises?
Always use parametrized queries.
CLR Stored procedure doesn’t prevent this by default. You need to do this yourself since CLR doesn’t do this automatically (I guess this was the actual questions you wanted to know)
Just update your code like this and you should be all good.
[Microsoft.SqlServer.Server.SqlProcedure]
public static void IsUserNameExists(string strUserName, out SqlBoolean returnValue)
{
using (SqlConnection connection = new SqlConnection("context connection=true"))
{
connection.Open();
SqlCommand command = new SqlCommand("Select count(UserName) from [User] where UserName=#UserName", connection);
command.Parameters.Add(new SqlParameter("#UserName", strUserName));
int nHowMany = int.Parse(command.ExecuteScalar().ToString());
if (nHowMany > 0)
returnValue = true;
else
returnValue = false;
}
}
Related
The code is based on https://dev.mysql.com/doc/connector-net/en/connector-net-programming-prepared-preparing.html
public void TableTest(string connectionString)
{
string sqlToCreateTable = #"CREATE TABLE IF NOT EXISTS my_table
(auction_key BIGINT NOT NULL, auction_owner VARCHAR(25), first_seen BIGINT,
PRIMARY KEY(auction_key))";
string sqlInsertOrUpdateAuction = "INSERT INTO my_table (auction_key) VALUES (#my_auc_id); ";
using (MySqlConnection dbConnection = new MySqlConnection(connectionString))
{
dbConnection.Open();
// is the table in the database?
MySqlCommand cmd = new MySqlCommand(sqlToCreateTable, dbConnection);
cmd.ExecuteNonQuery();
cmd.Parameters.AddWithValue("#my_auc_id", 123456);
cmd = new MySqlCommand(sqlInsertOrUpdateAuction, dbConnection);
cmd.ExecuteNonQuery();
}
}
The error is that 123456 is seen as null.
Message=Column 'auction_key' cannot be null
I tried changing the "strict" setting in my.ini and it makes no difference.
Help please.
Well, you add the parameter to the command and then instantiate a new command:
cmd.Parameters.AddWithValue("#my_auc_id", 123456);
cmd = new MySqlCommand(sqlInsertOrUpdateAuction, dbConnection);
If you do that, the command will no longer have the value for the #my_auc_id. Try switching those two lines:
cmd = new MySqlCommand(sqlInsertOrUpdateAuction, dbConnection);
cmd.Parameters.AddWithValue("#my_auc_id", 123456);
Hope this helps.
You could alleviate your issue, by simply doing the following:
using(var connection = new MySqlConnection(dbConnection))
{
connection.Open();
using(var command = new MySqlCommand(createTableQuery, connection))
{
command.ExecuteNonQuery();
}
using(var command = new MySqlCommand(insertOrUpdate, connection))
{
command.Parameters.Add("..", SqlDbType = SqlDbType.Int).Value = 123456;
command.ExecuteNonQuery();
}
}
Keep in mind that ExecuteNonQuery will return a zero or one, if it successfully worked. Also you may want to manually specify the SqlDbType. To avoid SQL inferring incorrectly. Also, this will correctly scope your MySqlCommand, so you can correctly utilize for the queries.
And according to the documentation, it does implement the IDisposable to utilize the using block. This ensures you instantiate your MySqlCommand again.
I am quite new to SQLCLR stored procedures. In my example I have two stored procedures, one without a parameter and one with a input parameter. Both target the same tables.
The one without the parameter is working fine and returns all rows in the result. But the one with an input parameter targeting the same tables is not returning any rows even though I am not receiving any errors. The input parameter in the .NET code is set as SqlString and in the database is NVARCHAR(50).
This is how my C# code looks:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public partial class StoredProcedures
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void AirlineSqlStoredProcedure (SqlString strAirline)
{
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "Context Connection=true";
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
conn.Open();
cmd.CommandText = "SELECT dbo.tblAirline.AirlineName, dbo.tblAircraft.AircraftUnits, dbo.tblAircraft.Manufacturer, dbo.tblAircraft.AircraftModel FROM dbo.tblAircraft INNER JOIN dbo.tblAirline ON dbo.tblAircraft.AirlineID = dbo.tblAirline.AirlineID WHERE AirlineName = '#strAirline' ORDER BY dbo.tblAircraft.AircraftUnits DESC";
SqlParameter paramAge = new SqlParameter();
paramAge.Value = strAirline;
paramAge.Direction = ParameterDirection.Input;
paramAge.SqlDbType = SqlDbType.NVarChar;
paramAge.ParameterName = "#strAirline";
cmd.Parameters.Add(paramAge);
SqlDataReader sqldr = cmd.ExecuteReader();
SqlContext.Pipe.Send(sqldr);
sqldr.Close();
conn.Close();
}
[Microsoft.SqlServer.Server.SqlProcedure]
public static void AirlineAircraftStoredProcedure()
{
//It returns rows from Roles table
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "Context Connection=true";
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
cmd.CommandText = "SELECT dbo.tblAirline.AirlineName, dbo.tblAircraft.AircraftUnits, dbo.tblAircraft.Manufacturer, dbo.tblAircraft.AircraftModel FROM dbo.tblAircraft INNER JOIN dbo.tblAirline ON dbo.tblAircraft.AirlineID = dbo.tblAirline.AirlineID ORDER BY dbo.tblAircraft.AircraftUnits DESC";
conn.Open();
SqlDataReader sqldr = cmd.ExecuteReader();
SqlContext.Pipe.Send(sqldr);
sqldr.Close();
conn.Close();
}
}
And when I execute the stored procedure I get empty rows:
USE [TravelSight]
GO
DECLARE #return_value Int
EXEC #return_value = [dbo].[AirlineSqlStoredProcedure]
#strAirline = N'American Airlines'
SELECT #return_value as 'Return Value'
GO
(0 row(s) affected)
(1 row(s) affected)
Also, for the input parameter I put an N before the string.
When running the stored procedure AirlineAircraftStoredProcedure targeting the same tables, I am getting all the rows back:
USE [TravelSight]
GO
DECLARE #return_value Int
EXEC #return_value = [dbo].[AirlineAircraftStoredProcedure]
SELECT #return_value as 'Return Value'
GO
(8 row(s) affected)
(1 row(s) affected)
What have I done wrong here?
Two (maybe 3) problems:
paramAge.Value = strAirline; should be:
paramAge.Value = strAirline.Value;
Notice the use of the .Value property.
WHERE AirlineName = '#strAirline' (within cmd.CommandText = "... ) should be:
WHERE AirlineName = #strAirline
Notice that the single-quotes were removed in the query text. You only use single-quotes for literals and not parameters / variables.
Replace the following 5 lines:
SqlParameter paramAge = new SqlParameter();
paramAge.Value = strAirline;
paramAge.Direction = ParameterDirection.Input;
paramAge.SqlDbType = SqlDbType.NVarChar;
paramAge.ParameterName = "#strAirline";
with:
SqlParameter paramAge = new SqlParameter("#strAirline", SqlDbType.NVarChar, 50);
paramAge.Direction = ParameterDirection.Input; // optional as it is the default
paramAge.Value = strAirline.Value;
Please note that the "size" parameter was set in the call to new SqlParameter(). It is important to always specify max string lengths.
With the technical problem out of the way, there are two larger issues to address:
Why is this being done in SQLCLR in the first place? Nothing specific to .NET is being done. Based solely on the code posted in the Question, this would be much better off as a regular T-SQL Stored Procedure.
If it must remain in SQLCLR, then you really need to wrap the disposable objects in using() constructs, namely: SqlConnection, SqlCommand, and SqlDataReader. For example:
using (SqlConnection conn = new SqlConnection("Context Connection=true"))
{
using (SqlCommand cmd = conn.CreateCommand())
{
...
}
}
and then you do not need the following two lines:
sqldr.Close();
conn.Close();
as they will be called implicitly by the call to each of their Dispose() methods.
What is the easiest and most SQL-like way to insert textbox values into a SQL Server table? I found several ways, and all of them are too complicated for this simple thing I want to do.
If LINQ is too foreign for you, you can still do things the old-fashioned way:
string statement = "INSERT INTO mytable(mycolumn) VALUES (#text)";
SqlCommand command = new SqlCommand(statement);
command.Parameters.AddWithValue("#text", myTextBox.Text);
try{
SqlConnection connection = new SqlConnection(myConnectionString);
connection.Open();
command.Connection = connection;
command.ExecuteNonQuery();
} catch {
//do exception handling stuff
}
Edit: Here's another version that uses using to ensure that messes are cleaned up:
string statement = "INSERT INTO mytable(mycolumn) VALUES (#text)";
using(SqlCommand command = new SqlCommand(statement))
using(SqlConnection connection = new SqlConnection(myConnectionString)) {
try{
command.Parameters.AddWithValue("#text", myTextBox.Text);
connection.Open();
command.Connection = connection;
command.ExecuteNonQuery();
} catch {
//do exception handling stuff
}
}
If you want to do something quickly, use LINQ to SQL. It will take care of your Data Access Layer & Business objects.
Just go to LINQ to SQL Classes on Visual Studio & map your SQL server and add any tables you want to it.
Then you can use the objects it creates in your code behind to update values from textboxes.
http://weblogs.asp.net/scottgu/archive/2007/05/19/using-linq-to-sql-part-1.aspx
public string ConnectionString
{
get
{
//Reading connection string from web.config
return ConfigurationManager.ConnectionStrings["ConnectionName"].ConnectionString;
}
}
public bool InsertEmployee()
{
bool isSaved = false;
int numberOfRowsAffected = 0;
string query = #"INSERT INTO Employee(EmployeeName, EmailAddress)
VALUES (#EmployeeName, #EmailAddress);
SELECT ##IDENTITY AS RowEffected";
SqlCommand cmd = new SqlCommand();
cmd.CommandText = query;
cmd.CommandType = System.Data.CommandType.Text;
cmd.Parameters.Add(new SqlParameter("#EmployeeName", txtEmployeeName.Text));
cmd.Parameters.Add(new SqlParameter("#EmailAddress", txtEmailAddress.Text));
try
{
using (SqlConnection cn = new SqlConnection(ConnectionString))
{
cmd.Connection = cn;
cn.Open();
object result = cmd.ExecuteScalar();
isSaved = Convert.ToInt32(result) > 0 ? true : false;
}
}
catch (Exception ex)
{
isSaved = false;
}
return isSaved;
}
But, in multiple layer or multi-tier application you do need to create DTO(Data Transfer Object) to pass the data from layer to layer(or tier to tier)
Here's a simple way to do it. It looks complex because of the number of rows: Inserting Data in SQL Database
I need to rapidly write code to insert/update SQL data. In classic ASP/VBScript I could do that like below:
Set rs = Server.CreateObject("adodb.Recordset")
sql = "SELECT * FROM _table WHERE id=" & id
rs.Open sql, cn, 1, 3
If rs.recordcount = 0 Then
rs.AddNew
End If
rs("data") = data
rs.Update
rs.Close
What could be the nearest C# port of this code snippet? Thanks!
Assuming MS-SQL:
using (SqlConnection conn = new SqlConnection("Connection String"))
using (SqlCommand comm = new SqlCommand("INSERT INTO MyTable (Name, Age) VALUES (#name, #age)", conn))
{
conn.Open();
comm.Parameters.AddWithValue("#name", "Adam");
comm.Parameters.AddWithValue("#age", 25);
comm.ExecuteNonQuery();
}
Connection string obviously needs fleshing out, and the parameterised SQL needs confirming, but this is the start of what you need.
You don't need to worry about closing as the using statement does this for you.
Update: not knowing classic ASP very well, I should clarify that this code only does an insert and not an update, if you need to do an update, change the SQL to use the update syntax.
Hopefully this gets you started. Make a reference to the ADODB class:
ADODB.Connection cn = new ADODB.Connection();
cn.Open("Provider=etc...", null, null, 0);
ADODB.Recordset rs = new ADODB.Recordset();
String sql = "SELECT * FROM _table WHERE id=" & id;
rs.Open(sql, cn, ADODB.CursorTypeEnum.adOpenKeyset, ADODB.LockTypeEnum.adLockOptimistic, -1);
if (rs.RecordCount == 0)
rs.AddNew;
rs("data") = data;
rs.Update();
rs.Close();
cn.Close();
I want to use a select statement to find if there is a record that already exists. I've put the code below but it throws an error at the dReader = comm.ExecuteReader(); and i'm unsure why. Any help?
string connString = "Data Source=KIMMY-MSI\\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True";
SqlDataReader dReader;
SqlConnection conn = new SqlConnection(connString);
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "SELECT * FROM Customers WHERE CustomerID == " + txtID.Text;
comm.Connection.Open();
dReader = comm.ExecuteReader();
if (dReader.HasRows == true)
{
Response.Write("Exists");
}
The error:
Invalid Column Name (whatever I input)
It seems to be looking for a column named what I input rather than looking for the actual data.
Change your == to =. That is invalid SQL as it is.
Also if txtID.Text is non-numeric then it needs to be in single quotes. You should not be constructing your SQL like this, instead use a parameter:
comm.CommandText = "SELECT * FROM Customers WHERE CustomerID = #CustomerID";
comm.Parameters.AddWithValue("CustomerID", txtID.Text);
More Info
C# using statement
SQL reference
SQL injection (why you should parameterize your queries)
It looks like your command has an issue:
SELECT * FROM Customers WHERE CustomerID == 1
In SQL you don't need to use the == operator to ensure something is equal to another.
Try:
SELECT * FROM Customers WHERE CustomerID = 1
In addition, you might want to read up about SQL Injection, the way you are binding the value is directly from a textbox value. This has a huge security hole which could lead to arbitrary sql command execution.
Change this line:
comm.CommandText = "SELECT * FROM Customers WHERE CustomerID == " + txtID.Text;
To this line:
comm.CommandText = "SELECT * FROM Customers WHERE CustomerID = #id";
comm.Parameters.AddWithValue("id", int.Parse(txtID.Text));
Assuming that your customer id is int on the database.
The equals operator in SQL is just a single =.
Also, you really shouldn't be concatenating SQL queries like that, you are just opening yourself up to SQL Injection attack. So change it to be like this:
comm.CommandText = "SELECT * FROM Customers WHERE CustomerID = #CustomerId";
comm.Parameters.AddWithValue("#CustomerId", txtID.Text);
See Stop SQL Injection Attacks Before They Stop You on MSDN.
You are using invalid SQL. You name to change "==" to "=".
You should also consider wrapping your IDisposable objects in using statements so that unmanaged objects are properly disposed of and connections are properly closed.
Finally, think about using parameters in your SQL, instead of concatenating strings, to avoid SQL injection attacks:
string connString = #"Data Source=KIMMY-MSI\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True";
string sql = "SELECT * FROM Customers WHERE CustomerID = #CustomerID";
using (SqlConnection conn = new SqlConnection(connString))
using (SqlCommand comm = new SqlCommand(sql, conn))
{
comm.Connection.Open();
comm.Parameters.AddWithValue("#CustomerID", txtID.Text);
using (SqlDataReader dReader = comm.ExecuteReader())
{
if (dReader.HasRows == true)
{
Response.Write("Exists");
}
}
}