0

I coded a site with ASP.NET, C# and a SQL Server database. I want to search in database with full-text search by passing a query from C# to SQL Server. In summary, the problem occurs when I use parameters, but when use SQL query string, I get the correct results.

I must draw your attention to this point that my database language is Persian and I have to use NVarchar in FTS and use N for searching text like N'"text that want to search"' .

First I tried

SELECT
    TbSoore.IdSoore, TbSoore.NameSoore, TbAye.NumberAye, 
    TbAye.IdAye, TbAye.TextAye, TbAye.TextTarjome 
FROM
    TbAye 
INNER JOIN 
    TbSoore ON TbAye.IdSoore = TbSoore.IdSoore 
WHERE
    CONTAINS((TextTarjome, TextHadis), N'"متذکر شود"' )

in SQL Server directly and it worked.

Then use this code in C# and worked when I searched متذکر شود , too:

string forwardedSearchText = "N'\"" + Request.QueryString["SearchText"] + "\"'";//SearchText is entered by user in textbox.
sting forwardedSearchColumn;// This fill with checkboxes and it varies between 1 to 6 items.
strsql =
        $@"SELECT TbSoore.IdSoore, TbSoore.NameSoore, TbAye.NumberAye, 
        TbAye.IdAye, TbAye.TextAye, TbAye.TextTarjome
        FROM TbAye 
        INNER JOIN TbSoore ON TbAye.IdSoore = TbSoore.IdSoore 
        WHERE CONTAINS(({forwardedSearchColumn}),  " + forwardedSearchText + " )";

DataTable dt = new DataTable();

using (SqlConnection con = new SqlConnection(strcon))
{
    using (SqlCommand cmdSQL = new SqlCommand(strsql, con))
    {
        con.Open();
        dt.Load(cmdSQL.ExecuteReader());
    }

    ListViewSearchResultAye.DataSource = dt;
    ListViewSearchResultAye.DataBind();
}

But this code is not secure. I want to use a parameter to pass forwardedSearchText to SQL Server.

I changed my code to:

string forwardedSearchText = "N'\"" + Request.QueryString["SearchText"] + "\"'";//SearchText is entered by user in textbox.
sting forwardedSearchColumn;// This fill with checkboxes and it varies between 1 to 6 items.
strsql =
        $@"SELECT TbSoore.IdSoore, TbSoore.NameSoore, TbAye.NumberAye, 
        TbAye.IdAye, TbAye.TextAye, TbAye.TextTarjome
        FROM TbAye INNER JOIN TbSoore ON 
        TbAye.IdSoore = TbSoore.IdSoore WHERE 
        Contains(({forwardedSearchColumn}), @forwardedSearchText )";

DataTable dt = new DataTable();

using (SqlConnection con = new SqlConnection(strcon))
{
    using (SqlCommand cmdSQL = new SqlCommand(strsql, con))
    {
        cmdSQL.Parameters.AddWithValue("@forwardedSearchText", forwardedSearchText);
        con.Open();
        dt.Load(cmdSQL.ExecuteReader());
    }

    ListViewSearchResultAye.DataSource = dt;
    ListViewSearchResultAye.DataBind();
}

But when I search متذکر شود , I get this error:

Syntax error near '* شود*' in the full-text search condition 'N'"متذکر شود"''.

Description:An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details:System.Data.SqlClient.SqlException: Syntax error near 'شود' in the full-text search condition 'N'"متذکر شود"''.

I try cmdSQL.Parameters.Add("@forwardedSearchText", SqlDbType.NVarChar).Value = forwardedSearchText;

and cmdSQL.Parameters.Add(new SqlParameter("@forwardedSearchText", forwardedSearchText));

instead of cmdSQL.Parameters.AddWithValue("@forwardedSearchText", forwardedSearchText);

and it made no difference.

Note: I want use Contain, Not FreeText.

What can I do to correct this problem?

10
  • 2
    Avoiding the injection of the search term is somewhat of a moot point when you are still injecting the column name. Commented May 28, 2024 at 14:44
  • 1
    Using formatted strings is not query parameterisation. You need to use SQL query parameters (DbCommand.Parameters or as overridden by subclass). Commented May 28, 2024 at 14:53
  • Remove the ' quotes from the string forwardedSearchText = ... assignment, they are not allowed when doing parameters, since parameters handle the quoting stuff Commented May 28, 2024 at 15:05
  • @ThomA the column names enter by checkbox and it's secure but text search enter by textbox and not secure, so I should use parameters Commented May 28, 2024 at 18:15
  • 1
    Blindly injecting a column name is never secure. Commented May 28, 2024 at 18:33

3 Answers 3

1

When you are using query parameters, you do not need to quote/convert/format the values you put into the parameters.

So in:

string forwardedSearchText = "N'\"" + Request.QueryString["SearchText"] + "\"'";

which is then used

cmdSQL.Parameters.Add(new SqlParameter("@forwardedSearchText", forwardedSearchText));

there will be double quoting of the search text and likely to interact badly.

The value of the search text will be quoted twice (note the number of quotes in the error message. Just use

cmdSQL.Parameters.Add(new SqlParameter("@forwardedSearchText", Request.QueryString["SearchText"]));

The reason SQL query parameterisation is important is to let the database's own code handle the necessary quoting/conversion/formatting rather than every programmer trying to do it (or forgetting) and likely getting it wrong. All the necessary escaping and quoting will be done by the database (either client or server side: really does not matter).

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you. But I have to use N before text that want to search like N'"text that want to search"'. where should I use that in your answer?
@Zahra free text search is for human text including word variations (this is why the indexing is different). Searching for SQL syntax is not a thing that "normal" users do. I suspect you would get nothing useful from FTS.
1

A few things:

Parameters and SQL injection safe code does NOT allow a "parameter" for a column name. So, criteria can and should use a @Parameter, but for column names a different approach is required.

You can still create injection safe code, and the approach is that in these cases (the column name is to be passed), then you simply check that the column passed is legal. You only are going to have a few possible columns allowed here, right? Say 2-5.

So, your code then becomes this:

        string forwardedSearchText = Request.QueryString["SearchText"];
        //SearchText is entered by user in textbox.
        string forwardedSearchColumn = "";// This fill with checkboxes and it varies between 1 to 6 items.


        List<string> LegalColumns = new List<string> { "Notes", "Descripiton", "ProductDesc"};
        string[] sCols = forwardedSearchColumn.Split(',');  // assume no spaces in this string

        foreach (string sCol in sCols) 
        {
            if (!LegalColumns.Contains(sCol)) 
                return;     // if columns don't verify - we exit
        }

        // if we get here, then only legal columns passed

        string strsql =
                $@"SELECT TbSoore.IdSoore, TbSoore.NameSoore, TbAye.NumberAye, 
                TbAye.IdAye, TbAye.TextAye, TbAye.TextTarjome
                FROM TbAye INNER JOIN TbSoore ON 
                TbAye.IdSoore = TbSoore.IdSoore WHERE 
                Contains(({forwardedSearchColumn}), @forwardedSearchText)";

        DataTable dt = new DataTable();

        using (SqlConnection con = new SqlConnection(strcon))
        {
            using (SqlCommand cmdSQL = new SqlCommand(strsql, con))
            {
                cmdSQL.Parameters.Add("@forwardedSearchText", SqlDbType.NVarChar).Value =  forwardedSearchText;
                con.Open();
                dt.Load(cmdSQL.ExecuteReader());
            }

And note we don't use "AddWith". We use add, and strong type the parameter.

So, simply verify that columns passed are legal.

To be fair, since the UI has check boxes for this, then it going to be "more difficult" to inject non legal columns or SQL. However, by verifying the columns are legal, then that removes the injection issues if a user were to mess with the check box values by use of browser debug tools before the post-back.

In my experience, for indexed free text searching, then only a few columns are going to be free text indexed in such databases, and thus a simple list of legal columns in your code will suffice for the vast majority of such cases. Since we verify that all columns are legal, then we are free to inject such columns directly into the SQL query.

So, here is a working sample:

Markup:

        <h3>Search for Hotel</h3>
        <asp:CheckBox ID="chkDescription" runat="server"  Text="Search Description"/>
        <asp:CheckBox ID="chkComments" runat="server"  Text="Search Comments"
            style="margin-left:35px"
            />
        <br />
        <br />


        <asp:TextBox ID="txtSearch" runat="server" 
            TextMode="MultiLine" Height="114px" Width="348px"></asp:TextBox>
        <br />
        <br />
        <asp:Button ID="cndSearch" runat="server" Text="Search"
            OnClick="cndSearch_Click"
            CssClass="btn btn-dark"
            />

        <asp:GridView ID="GridView1" runat="server"
            CssClass="table"
            width="40%">
        </asp:GridView>

So, in above, we have a check box, we can select 2 columns to search.

Our code behind is thus this:

    protected void cndSearch_Click(object sender, EventArgs e)
    {
        string sFields = "";

        if (chkComments.Checked)
            sFields = "Comments";


        if (chkDescription.Checked)
        {
            if (sFields != "")
                sFields += ", ";  // each field specified is to be seperated by , then space
            sFields += "Description";
        }

        SearchHotels(sFields, txtSearch.Text);
    }

    void SearchHotels(string FieldList, string sText)
    {
        List<string> LegalFields = new List<string> { "Description", "Comments" };

        string[] MyCheckList = FieldList.Split(new String[] { ", " }, StringSplitOptions.None);

        foreach (string MyField in MyCheckList) 
        {
            if (!LegalFields.Contains(MyField))
                return;
        }

        // we get here, then list of fields passed = legal fields
        DataTable dt = new DataTable();
        string strSQL =
            $@"SELECT HotelName, Description, Comments FROM tblHotels
            WHERE CONTAINS(({FieldList}), @SearchText)
            ORDER BY HotelName";

        Debug.Print(strSQL);
        using (SqlConnection conn = new SqlConnection(Properties.Settings.Default.TEST4))
        {
            using (SqlCommand cmd = new SqlCommand(strSQL, conn))
            {
                cmd.Parameters.Add("@SearchText", SqlDbType.NVarChar).Value = sText;
                conn.Open();
                dt.Load(cmd.ExecuteReader());

            }
        }

        GridView1.DataSource = dt;
        GridView1.DataBind();   
    }

And the result is now this:

I searched for Deer or Pool.

Remember, the text typed in MUST have valid syntax. (User must type in "And" or "or" between keywords).

Hence the result is now this:

enter image description here

6 Comments

It's right but my problem is not about columns, is about injection the text of search and the error that I describe above!! With your answer my problem is not solved.
try typing in only one keyword for the search. So, you can free text search for cat anywhere in the column by typing in cat. If you want text with a cat or a dog, then the user will have to type in "cat or dog" (without the quotes). So, yes, the above example should work.
See my edit, I posted a full working example. The posted code is sql injection safe.
Thank you. But I have to use N before text that want to search like N'"text that want to search"'. where should I use that in your answer?
You don't need it, since I set the nvcarchar with Sqldbtype.Nvarchar. So, the N prefix means to use a nvarchar type. Since I strong type and set the parameter type in code, by using SqlDbType.Nvarchar, then the N prefix is not required. In fact, this is just ANOTHER one of MANY great reasons to NEVER use AddWith, but always use .Add with a correctly set SqlDbType.
|
0

Thank AlbertD.Kallal for his complete answer, his answer (and another answer and commands) helped me but my problem solved by this tip:

Despite the opinion of friends in commands, I have to use " both sides of Request.QueryString["SearchText"] in command.parameters.

So the correct code that works, is:

    strsql =
               $@"Select TbSoore.IdSoore, TbSoore.NameSoore, TbAye.NumberAye, TbAye.IdAye, TbAye.TextAye, TbAye.TextTarjome
               From TbAye INNER JOIN TbSoore ON TbAye.IdSoore = TbSoore.IdSoore
               Where Contains(({forwardedSearchColumn}), @forwardedSearchText )";
            }
            DataTable dt = new DataTable();
            using (SqlConnection con = new SqlConnection(strcon))
            {
                using
                    (SqlCommand cmdSQL = new SqlCommand(strsql, con))
                {
                    
                    cmdSQL.Parameters.Add("@forwardedSearchText", SqlDbType.NVarChar).Value =
                    "\"" + Request.QueryString["SearchText"] + "\"";
                    
                    con.Open();
                    dt.Load(cmdSQL.ExecuteReader());
                }
                ListViewSearchResultAye.DataSource = dt;
                ListViewSearchResultAye.DataBind();

            }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.