Рейтинг:
1
Vincent Maverick Durano
Я не нахожусь на своей машине разработки, поэтому не могу написать образец с использованием хранимых процедур. Но вот один из них использует динамический запрос:
private string GenerateDynamicQuery(string baseSql, StringCollection sc){
StringBuilder sb = new StringBuilder(string.Empty);
foreach (string item in sc){
sb.AppendFormat("{0}('{1}'); ", baseSql, item);
}
return sb.ToString();
}
private void InsertRecords(StringCollection sc){
const string sqlStatement = "INSERT INTO Employee (Name) VALUES";
string dynamicQuery = GenerateDynamicQuery(sqlStatement,sc);
using(SqlConnection connection = new SqlConnection("YOUR CONNECTION STRING HERE")){
using(SqlCommand cmd = new SqlCommand(dynamicQuery,connection)){
cmd.CommandType = CommandType.Text;
cmd.ExecuteNonQuery();
}
//bind GridView after
BindGrid(sc);
}
private void BindGrid(StringCollection sc){
StringBuilder sb = new StringBuilder(string.Empty);
foreach (string item in sc){
sb.AppendFormat("'{0}',", item);
}
string param = sb.ToString().TrimEnd(',');
string sqlStatement = string.Format("SELECT * FROM Employee WHERE Name IN ({0});",param);
using(SqlConnection connection = new SqlConnection("YOUR CONNECTION STRING HERE")){
using(SqlCommand cmd = new SqlCommand(sqlStatement,connection)){
DataTable dt = new DataTable();
SqlDataAdapter ad = new SqlDataAdapter(cmd);
ad.Fill(dt);
if (dt.Rows.Count > 0) { //check if the query returns any data
GridView1.DataSource = dt;
GridView1.DataBind();
}
else
{
//No records found
}
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
StringCollection sc = new StringCollection();
foreach (ListItem item in ListBox1.Items)
{
if (item.Selected){
sc.Add(item.Text);
}
}
InsertRecords(sc);
}
Вот еще одно решение с параметризацией запроса для "предотвращения" SQL-инъекции:
private void InsertRecords(StringCollection sc){
const string sqlStatement = "INSERT INTO Employee (Name) VALUES (@param1)";
foreach (string item in sc){
using(SqlConnection connection = new SqlConnection("YOUR CONNECTION STRING HERE")){
using(SqlCommand cmd = new SqlCommand(sqlStatement ,connection)){
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("@param1", item)
cmd.ExecuteNonQuery();
}
}
}
//bind GridView after
BindGrid(sc);
}
private void BindGrid(StringCollection sc){
string[] strArray = new string[sc.Count];
sc.CopyTo(strArray,0);
var parms = strArray.Select((s, i) => "@param1" + i.ToString()).ToArray();
var inclause = string.Join(",", parms);
string sqlStatement = "SELECT * FROM Employee WHERE Name IN ({0})";
using(SqlConnection connection = new SqlConnection("YOUR CONNECTION STRING HERE")){
using(SqlCommand cmd = new SqlCommand(string.Format(sqlStatement, inclause),connection)){
for (var i = 0; i < valuearray.Length; i++)
{
cmd.Parameters.AddWithValue(parms[i], strArray[i]);
}
DataTable dt = new DataTable();
SqlDataAdapter ad = new SqlDataAdapter(cmd);
ad.Fill(dt);
if (dt.Rows.Count > 0) { //check if the query returns any data
GridView1.DataSource = dt;
GridView1.DataBind();
}
else
{
//No records found
}
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
StringCollection sc = new StringCollection();
foreach (ListItem item in ListBox1.Items)
{
if (item.Selected){
sc.Add(item.Text);
}
}
InsertRecords(sc);
}
PS:никогда не тестировался на решении paramertize на самом деле, но это должно дать вам некоторое представление о том, как это сделать.
Vincent Maverick Durano
Я знаю о SQL-инъекции. на самом деле я написал об этом статью здесь: https://www.codeproject.com/Articles/1105224/Protect-Your-Data-Prevent-SQL-Injection
Причина, по которой я дал это решение, заключается в том, что:
(1) я не нахожусь на своей машине разработки, поэтому я не могу написать образец с использованием хранимых процедур. (я четко заявляю об этом на своем посту)
(2) параметр исходит из элементов списка, а не из входных данных (текстовое поле)
Я был бы признателен, если бы вы могли предоставить пользователю "решение" вместо того, чтобы сразу же проголосовать за мое решение.
Richard Deeming
Хранимые процедуры не имеют отношения к SQLi. Вполне возможно написать код без хранимых процедур, который не является уязвимым; и так же возможно написать код с использованием хранимых процедур, которые не являются уязвимыми. является уязвимый.
И нет никакой гарантии, что пользователь не имеет никакого контроля над элементами списка. Даже если проверка ViewState включена и работает должным образом, может существовать другой экран, на котором пользователи смогут управлять элементами, что приведет к сохраненной уязвимости SQLi.
Vincent Maverick Durano
"И нет никакой гарантии, что пользователь не имеет никакого контроля над элементами списка."
точка зрения принята. Но поскольку элементы из ListBox используются в качестве параметров для запроса, я предположил, что элементы поступают из базы данных. Опять же, даже если он жестко закодирован, элементы являются постоянными, а не входными данными от пользователя.
"там может быть другой экран, где пользователи могут управлять элементами"
Может, ты и прав. но мне любопытно, каким образом?
Richard Deeming
У вас может быть один экран, где пользователи могут вводить элементы, который использует правильно параметризованные запросы.
Пользователь вводит элемент со значением Robert');DROP TABLE Employee;--
, который правильно хранится в базе данных.
На следующем экране пользователь выбирает элемент списка со значением Robert');DROP TABLE Employee;--
, и нажимает на кнопку.
БУМ. Ваш динамический запрос пытается выполнить DROP TABLE
команда. У вас есть постоянная уязвимость SQLi.
Vincent Maverick Durano
Точка зрения принята. Я обновил свое решение, чтобы добавить запрос paramterize. Теперь доволен? ;)
Richard Deeming
Так - то лучше! :)
NB: Тебе это не нужно. GenerateDynamicQuery
позвоните в полицию. InsertRecords
метод уже не тот.
Vincent Maverick Durano
Я идиотка! Спасибо, что заметили! Обновленный ;)
Richard Deeming
Вопрос не совсем ясен, но я думаю, что рассмотрел обе возможные интерпретации в решении 2. :)
Рейтинг:
0
Richard Deeming
Лучший способ передать несколько значений одному параметру хранимой процедуры-это использовать табличный параметр:
Возвращающие Табличное Значение Параметры | Майкрософт Документы[^]
CREATE TYPE dbo.StringListTable As Table
(
Value varchar(50) NOT NULL
);
GO
CREATE PROCEDURE dbo.ListEmployees
(
@Names dbo.StringListTable READONLY
)
As
BEGIN
SELECT
*
FROM
Employee As e
WHERE
Exists
(
SELECT 1
FROM @Names As n
WHERE n.Value = e.Name
)
ORDER BY
Name
;
END
Использование:
var names = new DataTable();
names.Columns.Add("Value", typeof(string));
foreach (ListItem item in lbCT.Items)
{
if (item.Selected)
{
names.Rows.Add(item.Value);
}
}
using (var connection = new SqlConnection("... YOUR CONNECTION STRING HERE ..."))
using (var command = new SqlCommand("dbo.ListEmployees", connection))
{
command.CommandType = CommandType.StoredProcedure;
var pNames = command.Parameters.AddWithValue("@Names", names);
pNames.SqlDbType = SqlDbType.Structured;
pNames.TypeName = "dbo.StringListTable";
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
gvDS.DataSource = ds;
gvDS.DataBind();
}
Кроме того, для простого списка строк можно передать разделенный запятыми список значений и использовать
функция SPLIT_STRING[
^]
(SQL 2016), или
пользовательская функция разделения строк[
^] если вы используете более старую версию, чтобы разделить значения обратно в табличную переменную или временную таблицу.
А еще есть
подход XML[
^], но производительность, как правило, довольно плохая.
С другой стороны, если вы хотите передать несколько параметров текстовому запросу, вам нужно будет построить правильно параметризованный запрос:
using (var connection = new SqlConnection("... YOUR CONNECTION STRING HERE ..."))
using (var command = new SqlCommand("", connection))
{
int index = 0;
var sb = new StringBuilder("SELECT * FROM Employee");
foreach (ListItem item in lbCT.Items)
{
if (item.Selected)
{
string name = "@p" + index;
command.Parameters.AddWithValue(name, item.Value);
sb.Append(index == 0 ? " WHERE Name In (" : ", ");
sb.Append(name);
index++;
}
}
if (index != 0)
{
sb.Append(")");
}
sb.Append(" ORDER BY Name;");
cmd.CommandText = sb.ToString();
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
gvDS.DataSource = ds;
gvDS.DataBind();
}
Vincent Maverick Durano
Лучше, чем у меня. Только одна вещь, хотя это не имеет прямого отношения к вопросу, и это не имеет большого значения - когда вы имеете дело с одной таблицей, используйте DataTable вместо DataSet. :)
Richard Deeming
Действительно - это то, что я обычно делаю. Но ... DataSet
бит просто скопирован с вопроса. :)
Vincent Maverick Durano
НП. Еще я дал бы тебе 5!