0

Yesterday I asked a question about the table you guys helped a lot. Someone suggested that I don't directly store the strConnectionString so I changed what I had.

This is my code:

        private void main_B_login_Click(object sender, RoutedEventArgs e)
    {
        //connect to the database
        SqlConnection loginConn = null;
        SqlCommand cmd = null;
        SqlDataAdapter sda = null;
        DataTable dt = new DataTable();

        loginConn = new SqlConnection("server=localhost;" + "Trusted_Connection=yes;" + "database=Production; " + "connection timeout=30");
        cmd = new SqlCommand("Select Username FROM [User] WHERE Username =@UsernameValue", loginConn);
        loginConn.Open();
        cmd.CommandType = CommandType.Text;
        cmd.Parameters.Add("@UsernameValue", SqlDbType.VarChar).Value = Main_T_Username.Text;
        sda = new SqlDataAdapter(cmd);
        sda.Fill(dt);

        if (dt.Rows.Count > 0)
            {
                //MessageBox.Show("username");

                SqlConnection loginConn2 = null;
                SqlCommand cmd2 = null;
                SqlDataAdapter sda2 = null;
                DataTable dt2 = new DataTable();

                loginConn2 = new SqlConnection("server=localhost;" + "Trusted_Connection=yes;" + "database=Production; " + "connection timeout=30");
                cmd2 = new SqlCommand("Select Password FROM [User] WHERE Password =@PasswordValue", loginConn2);
                loginConn2.Open();
                cmd2.CommandType = CommandType.Text;
                cmd2.Parameters.Add("@PasswordValue", SqlDbType.VarChar).Value = Main_T_Password.Text;
                sda2 = new SqlDataAdapter(cmd2);
                sda2.Fill(dt2);

                if (dt2.Rows.Count > 0)
                {
                    MessageBox.Show("username and Password = Correct");
                }
                else
                {
                    MessageBox.Show("Password = Wrong");
                    loginConn2.Close();
                }

             }
            else
            {
                MessageBox.Show("WrongPass or Username!");
                loginConn.Close();
            }

        }

At the moment it works perfectly. I am not sure about two things.

  1. Is the connection string now as it stands still "bad" in terms of SQL INJECTION?

  2. I have the code basically check first the username then password..? i have stored them both as text values because I don't know how to change it to hashing.

Could I simplify the check to do both username and password? but still give out and error when the username is wrong and when the password is wrong?

raidensan
  • 1,099
  • 13
  • 31
Jared Barrett
  • 83
  • 1
  • 11
  • well imagine I set my password to "; drop table [user] .... byebye usertable... – BugFinder Apr 26 '16 at 08:32
  • 1
    You can't . He's using prepared statements. – fruggiero Apr 26 '16 at 08:33
  • At the moment i tried ; drop table [user] in the password field it just fails, does not do anything. – Jared Barrett Apr 26 '16 at 08:37
  • Connection strings are NEVER PRONE TO SQL INJECTION. As the term "SQL Injection" implies, it is about SQL. Did you ever bother to look at the connection string? It does not contain SQL. Ergo no possibility for SQL injection. – TomTom Apr 26 '16 at 08:37
  • Yesterday, http://stackoverflow.com/a/36842576/6251680 – Jared Barrett Apr 26 '16 at 08:40
  • 1
    "directly concatenating user input as executable code, which is a SQL injection vulnerability. Use query parameters instead." <- nothing to do with connection strings – Blorgbeard Apr 26 '16 at 08:46
  • you use *Stored Procedures* if security has top priority, then you can forget about SQL Injection. Otherwise *prepared statements* should be enough – Vladimir Apr 26 '16 at 08:52
  • security is always a good thing, so the better I can do, the better for the user. – Jared Barrett Apr 26 '16 at 09:53

5 Answers5

1

Connection string is not prone to sql injection.

you can check both username and password like this:

cmd = new SqlCommand("Select Username FROM [User] WHERE Username =@UsernameValue AND Password =@PasswordValue", loginConn);
loginConn.Open();
cmd.CommandType = CommandType.Text;
cmd.Parameters.Add("@UsernameValue", SqlDbType.VarChar).Value = Main_T_Username.Text;
cmd2.Parameters.Add("@PasswordValue", SqlDbType.VarChar).Value = Main_T_Password.Text;
Nino
  • 6,931
  • 2
  • 27
  • 42
  • But doing it like that wouldn't permits to differentiate between wrong password or wrong username. – fruggiero Apr 26 '16 at 09:13
  • differentiation is not a good thing. If anyone tries to guess someone's username/password, he'll have a lot easier job if applications tells him that username is non existant or that the password is wrong. – Nino Apr 26 '16 at 09:17
  • I think it's more a matter of UX than security. Ideally an application must lock the login for a while, after the user has done too failed tentatives (and there are other methods too, to protect against what you said). For a final user of the application, it's better to know if it's wrong on his password or his username rather than a generic error message. – fruggiero Apr 26 '16 at 09:54
0

For the Connection string part use SqlConnectionStringBuilder class wtih conjuction with your App.Config rather using string manipulation straight on the code.

Public string GetConnectionString()
{
    //Read the Application Settings from App.Config 
    SqlConnectionStringBuilder sqlConstrBuilder = new SqlConnectionStringBuilder();
    sqlConstrBuilder.DataSource = dataSource;
    sqlConstrBuilder.InitialCatalog = databaseName;
    sqlConstrBuilder.UserID = ConfigurationManager.AppSettings["UserId"].ToString();
    sqlConstrBuilder.Password = ConfigurationManager.AppSettings["Password"].ToString();

    return sqlConstrBuilder.ConnectionString;
}
Irshad
  • 3,071
  • 5
  • 30
  • 51
  • for the app.config I am assuming it is going to have a text file that you can edit the database location? I don't want users to be able to change the database location etc.. that is why i am doing it directly.. – Jared Barrett Apr 26 '16 at 08:44
  • @JaredBarrett for that reason remove the `App.Config` conjuction. but it's good practice to use the `SqlConnectionStringBuilder` class to create yourconnection string. – Irshad Apr 26 '16 at 08:52
  • thank you ill take a look bud – Jared Barrett Apr 26 '16 at 09:05
0

firstly is the connection string now as it stands still "bad" in terms of SQL INJECTION?

The connection string is not relevant.

You are using prepared sql statements , and this is what is good to prevent sql injection.

secondly I have the code basically check first the username then password..? i have stored them both as text values because I don't know how to change it to hashing.

Never save passwords as clear text in a DB

You must save an hash of the password to DB. When a user want to login, compare an hash of the password that the user entered with the hash of the passwords present on DB.

See here for some examples: How to hash a password

Could I simplify the check to do both username and password? but still give out and error when the username is wrong and when the password is wrong?

Yes , you must simplify it.

At the moment you are basically doing 2 hits on DB when you want just one.

You can simply do a select on DB by the username , then if you have 0 rows the username doesn't exists.

Otherwise the username exists and you can compare the password (the hash of the password) the user given to you with the password from the selected row (isn't necessary to do a 2nd select, you have already the row from the previous select).

Something like this (refinement is up to the reader):

   private void main_B_login_Click(object sender, RoutedEventArgs e)
{
    //connect to the database
    SqlConnection loginConn = null;
    SqlCommand cmd = null;
    SqlDataAdapter sda = null;
    DataTable dt = new DataTable();

    loginConn = new SqlConnection("server=localhost;" + "Trusted_Connection=yes;" + "database=Production; " + "connection timeout=30");
    cmd = new SqlCommand("Select Username, Password FROM [User] WHERE Username =@UsernameValue", loginConn);
    loginConn.Open();
    cmd.CommandType = CommandType.Text;
    cmd.Parameters.Add("@UsernameValue", SqlDbType.VarChar).Value = Main_T_Username.Text;
    sda = new SqlDataAdapter(cmd);
    sda.Fill(dt);

    if (dt.Rows.Count > 0)
        {
            if ((string) dt.Rows[0]['Password'] == Main_T_Password.Text)
            {
                MessageBox.Show("username and Password = Correct");
            }
            else
            {
                MessageBox.Show("Password = Wrong");
            }
         }
        else
        {
            MessageBox.Show("WrongPass or Username!");

        }
        loginConn.Close();
    }
Community
  • 1
  • 1
fruggiero
  • 943
  • 12
  • 20
  • Oh thanks, Could I ask you to explain how did you select the `cmd = new SqlCommand("Select Username FROM [User] WHERE Username =@UsernameValue", loginConn);` then `if (dt['Password'] == Main_T_Password.Text)` compare the password here? without calling the password column? – Jared Barrett Apr 26 '16 at 08:49
  • Updated the code (the query). Basically Username and Password are present on the same **User** table right? So isn't necessary a 2nd select, we have now the result on our dataTable that we get from the 1st select. – fruggiero Apr 26 '16 at 08:54
  • i thought `cmd = new SqlCommand("Select Username, Password FROM [User] WHERE Username =@UsernameValue", loginConn);` would only choose user and pass from USER table? so where does the datatable pull everything? – Jared Barrett Apr 26 '16 at 09:07
  • The query `Select Username, Password FROM [USER] WHERE Username = @UsernameValue` will select all the rows in the `User` table where the username is what you have passed as a parameter. The rows will have the columns **Username** and **Password** inside. When you do in your code that `sda.Fill(dt)` you are basically filling an in-memory datatable with the result set of that query. So, in that datatable you will have ideally 1 row with column "Username" and "Password". When i write `dt.rows[0]['Password']` i'm obtaining the Password column of the first row, – fruggiero Apr 26 '16 at 09:22
  • Alright, so from that one parameter, I can pull the users name surname email etc obviously? its all in the same row? – Jared Barrett Apr 26 '16 at 09:50
  • It's dependant on what you are _selecting_ with the SELECT statement. If you want all columns you can `SELECT * FROM TABLE WHERE ...` where the `*` means all columns. If you want to select only a number of columns you can do it like that: `SELECT column1,column2,col3 FROM table WHERE ....` – fruggiero Apr 26 '16 at 09:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110243/discussion-between-jared-barrett-and-fruggiero). – Jared Barrett Apr 26 '16 at 10:02
0

Move connection string to config file and read it with ConfigurationManager. Sql Injections is not about connection string, it is about sql command. Writing sql command in the code is not good practice, move it to stored procedure(Actually using orm is better for most cases). If you want to keep sql command in code, replace single quote with double quote to avoid sql injection.

And last, read more before start coding.

londondev
  • 231
  • 2
  • 13
  • Thank you bud, I have to try it to understand what i am doing and how it is working.. and why it is doing that..could you give me some sort of example of what you mean? double quotes? – Jared Barrett Apr 26 '16 at 08:55
  • Read this, https://msdn.microsoft.com/en-us/library/ff648339.aspx. There are more things you need to consider. – londondev Apr 26 '16 at 09:00
0

You can do 3 things to improve and simplify your code :

1 - store your configuration string in your web.config or App.config

<connectionStrings>
    <add name="ModelDB" connectionString="server=localhost;user id=User;password=Password;port=3333;database=database" providerName="MySql.Data.MySqlClient" />
</connectionStrings>

2 - store the passwords as byte[] in your database and use a hash function (you can use MD5 from System.Security.Cryptography)

using System.Security.Cryptography;

byte[] password = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(Main_T_Password.Text));

3 - use linq and Ado.Net Entity Data Model to simplify your requests (look for turotials https://msdn.microsoft.com/en-us/data/ff728653.aspx)

You can then generate C# classes from your database and you will be able to use linq and to treat results from your requests as objects which will simplify the code.

For exemple the ModelDB object below will automatically connect to the mysql database using the connectionString from the Web.config or App.config file.

You can use differents providers depending on your database (use providerName="System.Data.SqlClient" if your are using SQLServer)

ModelDB sql = new ModelDB();
var users = from users in sql.Users where Username = Main_T_Username.Text select users;

if(users.Count() == 0)
    MessageBox.Show("WrongPass or Username!");
else if(users.Count() > 0 && users[0].Password.SequenceEqual(password))
    MessageBox.Show("username and Password = Correct");
else
    MessageBox.Show("Password = Wrong");
VVN
  • 1,607
  • 2
  • 16
  • 25
Nicolas
  • 127
  • 1
  • 10