I have a sql query for my SelectCommand on my SqlDataSource. It looks like the following:
SELECT * FROM Books WHERE BookID = #BookID
A TextBox feeds the #BookID parameter using an Asp:ControlParameter.
When I view the SelectCommand when stepping through the code, I see this:
SELECT * FROM Books WHERE BookID = #BookID
What I want to actually see is that if the person types in 3 in the TextBox, I want to see
SELECT * FROM Books WHERE BookID = 3
I can't figure out how to access the above though?
One way to view the actual query is by using SQL Profiler.
The query is never executed as
SELECT * FROM Books WHERE BookID = 3
It's actually the parameterised query with the parameter passed.
You can do a "Find/Replace" on the query with the related parameters to see what it would look like.
(This answer presumes with the SqlClient implementation.)
No, you cannot see the executed sql code. The SqlCommand class calls sp_execute (see both SqlCommand.BuildExecute methods for the exact implementation) which separates the query from the parameters. You'll need to use Sql Profiler to see the exact query executed.
You could use the provided DbCommand (from the Selecting event) to parse your CommandText and replace the parameters with their actual values. This would need some logic for escaping, and it will not be the exact query that Sql Server executes.
Public Function GenSQLCmd(ByVal InSqlCmd As String, ByVal p As Data.Common.DbParameterCollection) As String
For Each x As Data.Common.DbParameter In p
InSqlCmd = Replace(InSqlCmd, x.ParameterName, x.Value.ToString)
Next
Return InSqlCmd
End Function
I guess you won't be able to see the select statement like you wish, since the parameter is not replaced in the statement with the value 3, but sent just like you wrote it to sql server (with the parameter).
That's actually good since it will prevent one to inject some malicious sql code in your textbox, for example.
Anyway, can't you retrieve the value passed to the parameter using this:
cmd.Parameters(0).Value
where cmd is your SqlCommand?
This is the C# version of Adam's answer
public string GenSQLCmd(string InSqlCmd, System.Data.Common.DbParameterCollection p) {
foreach (System.Data.Common.DbParameter x in p) {
InSqlCmd = InSqlCmd.Replace(x.ParameterName, "'" + x.Value.ToString() + "'");
}
return InSqlCmd;
}
Usage:
string DebugQuery = GenSQLCmd(cmd.CommandText, cmd.Parameters); //cmd is a SqlCommand instance
Yes, you can view that information but you need to do a bit coding for that.
Create an extension method called ToSqlStatement
public static class SqlExtensions
{
public static string ToSqlStatement(this IDbCommand cmd)
{
var keyValue = new List<string>();
foreach (SqlParameter param in cmd.Parameters)
{
var value = param.Value == null ? "NULL" : "'" + param.Value + "'";
keyValue.Add($"{param.ParameterName}={value}");
}
return $"{(cmd.CommandType == CommandType.StoredProcedure ? "exec " : string.Empty)}{cmd.CommandText} {string.Join(", ", keyValue)}";
}
}
Add OnSelecting event handler to SqlDataSource control on your page
In you code behind
protected void sqlDataSource_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
{
MyLogger.WriteLine(e.Command.ToSqlStatement());
}
Related
I am working on a project where the client has reported an SQL injection flaw in the code. Here is my codeā¦
1 public int ExecuteNonQuery(string query, SqlParameter[] parameters)
2 {
3 using (SqlCommand command = CreateCommand(query, parameters))
4 {
5 int rowsAffected = command.ExecuteNonQuery();
6 return rowsAffected;
7 }
8 }
And the CreateCommand method goes as
private SqlCommand CreateCommand(string commandText, SqlParameter[] parameters)
{
SqlCommand retVal = this.connection.CreateCommand();
retVal.CommandText = commandText;
retVal.CommandTimeout = this.commandsTimeout;
retVal.Parameters.AddRange(parameters);
return retVal;
}
The flaw is reported at line number 3. I am unable to understand what kind of attack an happen here as this is a console application. But I have to fix the flaw and I don't know how to fix it.
Query is
#"delete from {0} where runId in
( select runId from {0}
inner join
( select sId as sId_last,
wfId as wfId_last,
max(runId) as runId_last from {0} where endTime is NULL
group by sId, wfId ) t1
on endTime is NULL and sId = sId_last and wfId = wfId_last
and (runId <> runId_last or startTime < #aDateTime)
)";
Help appreciated.
Thanks.
that code is injection-free... But note that the methods that call ExecuteNonQuery could build the query by composing strings.
An injection attack happens when you do something like:
string name = ...; // A name selected by the user.
string query = "SELECT * FROM MyTable WHERE Name = '" + name + "'";
so when you compose a query using pieces of text that are of external origin.
Note that a more subtle injection attack could be multi-level:
string name = // The result of a query to the db that retrieves some data
// sadly this data has been manipulated by the attacker
string query = "SELECT * FROM MyTable WHERE Name = '" + name + "'";
In general you don't need a user interface to cause an injection attack...
You could query something from a web site/from the db, and use the unsanitized result to query the db (as in the last example), causing an injection attack... Or even using the content of the configuration file could cause an injection attack: the priviledges needed to modify the configuration file could be different than the ones needed to do something on the DB, and a malicious user could have the priviledges to modify the configuration file but not have direct access to the DB. So he could use the program as a trojan horse against the DB.
about the query
The weak point of that query (that is a composition of strings) is in how the {0} is calculated. Is it a string chosen in a group of fixed strings? Something like:
string tableName;
if (foo)
tableName = "Foo";
else if (bar)
tableName = "Bar";
or is it something more user controlled?
If the table names are fixed in code, then there shouldn't be any injection attack possible. If the table names are "extracted" from some user input/some other table the user could have access, we return to the problem I showed before.
You've exposed a public method which can be accessed by any code that allows any SQL expression to be executed.
I would look at changing that method to being internal or private instead so that not just any code can call that method.
Line 3:
using (SqlCommand command = CreateCommand(query, parameters))
Both Query and parameters are available in this line.
SQL injection should not be prevented by trying to validate your input; instead, that input should be properly escaped before being passed to the database.
How to escape input totally depends on what technology you are using to interface with the database.
Use prepared statements and parameterized queries. These are SQL
statements that are sent to and parsed by the database server
separately from any parameters. This way it is impossible for an
attacker to inject malicious SQL.
Lesson on SQL injection for your reference.link2
Recent bug report states that a method being called is crashing the service causing it to restart. After troubleshooting, the cause was found to be an obnoxious Oracle SQL call with thousands of strings passed. There is a collection of strings being passed to a method from an external service which often is more than 10,000 records. The original code used a where clause on the passed collection using the LIKE keyword, which I think is really, really bad.
public IList<ContainerState> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(#"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Join("OR", containerNumbers
.Select(item => string.Concat(" cntr_no LIKE '", item.SliceLeft(10), "%' ")))
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
Clarification of in house methods used which may be confusing:
DataBase.SelectQuery is an internal library method using generics which gets passed the sql string, a function to map the records to .NET objects, and the parameters being passed and returns an IEnumerable of Objects of type retuned by the Mapping function.
SliceLeft is an extension method from another internal helper library that just returns the first part of a string up to the number of characters specified by the parameter.
The reason that the LIKE statement was apparently used, is that the strings being passed and the strings in the database only are guaranteed to match the first 10 characters. Example ("XXXX000000-1" in the strings being passed should match a database record like "XXXX000000-8").
I believed that the IN clause using the SUBSTR would be more efficent than using multiple LIKE clauses and replaced the code with:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
String.Format(#"Select CTNR_NO, CNTR_STATE FROM CONTAINERS WHERE CTRN_SEQ = 0 AND ({0})",
string.Format("SUBSTR(CNTR_NO, 1, 10) IN ({0}) ",
string.Join(",", containerNumbers.Select(item => string.Format("\'{0}\'", item.SliceLeft(10) ) ) )
)
);
return DataBase.SelectQuery(sql, MapRecordToContainerState, new { }).ToList();
}
This helped slightly, and there were fewer issues in my tests, but when there are huge amounts of records passed, there is still an exception thrown and core dumps occur, as the SQL is longer than the server can parse during these times. The DBA suggests saving all the strings being passed to a temporary table, and then joining against that temp table.
Given that advice, I changed the function to:
public IList<ContainerRecord> GetContainerStates(IList<string> containerNumbers)
{
string sql =
#"
CREATE TABLE T1(cntr_num VARCHAR2(10));
DECLARE GLOBAL TEMPORARY TABLE SESSION.T1 NOT LOGGED;
INSERT INTO SESSION.T1 VALUES (:containerNumbers);
SELECT
DISTINCT cntr_no,
'_IT' cntr_state
FROM
tb_master
WHERE
cntr_seq = 0
AND cntr_state IN ({0})
AND adjustment <> :adjustment
AND SUBSTR(CTNR_NO, 1, 10) IN (SELECT CNTR_NUM FROM SESSION.T1);
";
var parameters = new
{
#containerNumbers = containerNumbers.Select( item => item.SliceLeft(10)).ToList()
};
return DataBase.SelectQuery(sql, MapRecordToContainerState, parameters).ToList();
}
Now I'm getting a "ORA-00900: invalid SQL statement". This is really frustrating, how can I properly write a SQL Statement that will put this list of strings into a temporary table and then use it in a SELECT Statement to return the list I need?
There are couple possible places could cause this error, it seams that the "DECLARE GLOBAL TEMPORARY" is a JAVA API, I don't think .net has this function. Please try "Create global temporary table" instead. And, I don't know whether your internal API could handle multiple SQLs in one select sql. As far as I know, ODP.net Command class can only execute one sql per call. Moreover, "create table" is a DDL, it therefore has its own transaction. I can't see any reason we should put them in the same sql to execute. Following is a sample code for ODP.net,
using (OracleConnection conn = new OracleConnection(BD_CONN_STRING))
{
conn.Open();
using (OracleCommand cmd = new OracleCommand("create global temporary table t1(id number(9))", conn))
{
// actually this should execute once only
cmd.ExecuteNonQuery();
}
using (OracleCommand cmd = new OracleCommand("insert into t1 values (1)", conn)) {
cmd.ExecuteNonQuery();
}
// customer table is a permenant table
using (OracleCommand cmd = new OracleCommand("select c.id from customer c, t1 tmp1 where c.id=tmp1.id", conn)) {
cmd.ExecuteNonQuery();
}
}
Is it possible to parse sql parameters from plain commandtext?
e.g.
//cmdtext = SELECT * FROM AdWorks.Countries WHERE id = #id
SqlCommand sqlc = new SqlCommand(cmdtext);
SqlParameterCollection parCol = sqlc.Parameters //should contain now 1 paramter called '#id'
If a SQL Server is available, the best option may be to simply ask the server what it thinks; the server has parsing and metadata functions built in, for example sp_describe_undeclared_parameters.
I ended up with this extention method (since I don't think there's a built in function):
public static class SqlParExtension
{
public static void ParseParameters(this SqlCommand cmd)
{
var rxPattern = #"(?<=\= |\=)#\w*";
foreach (System.Text.RegularExpressions.Match item in System.Text.RegularExpressions.Regex.Matches(cmd.CommandText, rxPattern))
{
var sqlp = new SqlParameter(item.Value, null);
cmd.Parameters.Add(sqlp);
}
}
}
usage:
//cmdtext = SELECT * FROM AdWorks.Countries WHERE id = #id
SqlCommand sqlc = new SqlCommand(cmdtext);
sqlc.ParseParameters();
sqlc.Parameters["#id"].Value = value;
I will have to make sure about this but I'm sure you must add the range of parameters to the command. Like I say I will have to come back with this but you can try doing something like:
// Create a collection of parameters with the values that the procedure is expecting in your SQL client.
SqlParameter[] parameters = { new SqlParameter("#id", qid),
new SqlParameter("#otherValue", value) };
// Add teh parameters to the command.
sqlc.Parameters.AddRange(parameters)
You would be very welcome to have a look at my VS2015 extension, QueryFirst, that generates wrapper classes from .sql files, harvesting parameter declarations directly from your sql. You need to declare your parameters in the --designTime section of your request, but then you find them again directly as inputs to the Execute(), GetOne() or ExecuteScalar() methods. These methods return POCOs with meaningul property names. There's intellisense everywhere, and you don't have to type a line of parameter code, or connection code, or command code, or reader code, among NUMEROUS OTHER ADVANTAGES :-).
I am writing a small program using an SQL database. The table name is StudentInfo.
I need to know the SQL code for the following
for (n=0; n<nRows; n++) {
string sql1="update StudentInfo set Position=" + n + " where <this has to be the row number>";
}
nRows is number of rows.
How can I get the row number for the above code?
best way to do this is to create a stored procedure in the database and use your code to pass the relevent information to the server
In order to accomplish this task you'll want to create a Stored Procedure or build a Query that actually accepts parameters. This will help you pass variables between, your method of concatenation will actually cause an error or become susceptible to SQL Injection attacks.
Non Parameter SQL Command:
using(SqlConnection sqlConnection = new SqlConnection("Database Connection String Here"))
{
string command =
"UPDATE Production.Product " +
"SET ListPrice = ListPrice * 2 " +
"WHERE ProductID IN " +
"(SELECT ProductID " +
"FROM Purchasing.ProductVendor" +
"WHERE BusinessEntityID = 1540);" +
using (SqlCommand sqlCommand = new SqlCommand(command, sqlConnection))
{
int execute = command.ExecuteNonQuery();
if( execute <= 0)
{
return false;
}
else
{
return true;
}
}
}
That method is essentially creation a connection, running our SQL Command, then we are using an integer to verify that it did indeed run our command successful. As you can see we simply using SQL to run our command.
The other important thing to note, you can't create a sub-query with an update; you have to create an update then run a select as the sub-query to hone in more specific data across so you can span across tables and so on.
The other alternative would be to use a parameter based query, where your passing variables between SQL and your Application.
I won't post code to that, because I believe you wrote the C# loop to demonstrate what you would like SQL to do for you. Which is only update particular rows; based on a specific criteria.
If you could post additional information I'd be more then happy to help you. But I'm just going to post what I believe you are trying to accomplish. Correct me if I'm wrong.
I'm trying to parameterize a raw SQL query for an Oracle synonym (non-entity) in EF 4 and I am having some problems. Currently I am doing something like the code below, based on some examples that I saw:
string term="foo";
OracleParameter p = new OracleParameter("#param1", term);
object[] parameters = new object[] { p };
var model = db.Database.SqlQuery<ProjectTask>("SELECT * FROM (SELECT * FROM web_project_task_vw WHERE project_num like '%#param1%') WHERE rownum<=100", parameters).ToList();
Running this doesn't return any results. If I replace the parameter with something like
"SELECT * FROM web_project_task_vw WHERE project_num like '%"+term+"%'"
it returns the results I expect, but this is obviously a SQL injection risk.
Can anyone point me in the right direction for how parameters are supposed to work in EF 4 for an Oracle DB?
Thanks.
First, like Mohammed wrote, you need to prefix the parameter with ':', but not as you define it, just in the query.
Second, you are currently searching not for the value of the parameter but rather strings that contains the string #param1. So surround the value of the parameter with % and you should get a result.
So it should look something like this:
string term="foo";
OracleParameter p = new OracleParameter("param1", term);
object[] parameters = new object[] { p };
var model = db.Database.SqlQuery<ProjectTask>("SELECT * FROM (SELECT * FROM web_project_task_vw WHERE project_num like '%'||:param1||'%') WHERE rownum<=100", parameters).ToList();
Your p might have an incorrect parameter name; the name should be param1, not #param1. Your query is also incorrect; replace '%#param1%' with '%:param1%'.