I have a simple .aspx login website and I use OleDB for local validation.
Here is my problem: After finding SQL Injection vulnerability, I decided to use parameters. But after using parameters my response is always "0" (Same as "Authenticated=false"). But if I don't use parameters, my response is "1" (Same as "Authenticated=true").
Here some pics while debugging:
Without parameters where the response=1 (Authenticated):
With code:
string idstr = Request.QueryString["id"];
idstr.Replace("''", "");
string passpath = Request.QueryString["password"];
passpath.Replace("''", "");
OleDbConnection connect = new OleDbConnection();
OleDbCommand cmd = new OleDbCommand();
connect.ConnectionString = #"Provider=Microsoft.ACE.OLEDB.12.0; Data Source= C:\Users\hugow_000\Desktop\OSGS_Kantine_htm_design\Kantine_data.accdb; Persist Security Info = False;";
cmd.Connection = connect;
connect.Open();
cmd.CommandText = "select * from User_data where Stamnummer="+idstr+" and Wachtwoord="+ passpath;
OleDbDataReader read = cmd.ExecuteReader();
int code = 0;
while (read.Read())
{
code = code + 1;
}
if (code == 1)
{
Response.Redirect("~/AuthKeyGEN.aspx?Auth=true&id=" + idstr + "&password=" + passpath + "");
}
if (code > 1)
{
Response.Redirect("~/Login.aspx?response=0");
}
if (code < 1)
{
Response.Redirect("~/Login.aspx?response=0");
}
}
And with parameters where the response is 0 (Not Authenticated):
And with code:
string idstr = Request.QueryString["id"];
idstr.Replace("''", "");
string passpath = Request.QueryString["password"];
passpath.Replace("''", "");
OleDbConnection connect = new OleDbConnection();
OleDbCommand cmd = new OleDbCommand();
connect.ConnectionString = #"Provider=Microsoft.ACE.OLEDB.12.0; Data Source= C:\Users\hugow_000\Desktop\OSGS_Kantine_htm_design\Kantine_data.accdb; Persist Security Info = False;";
cmd.Connection = connect;
connect.Open();
cmd.CommandText = "select * from User_data where Stamnummer=#idstr and Wachtwoord=#passpath";
cmd.Parameters.Add("#idstr", OleDbType.BSTR).Value = idstr;
cmd.Parameters.Add("#passpath", OleDbType.BSTR).Value = passpath;
OleDbDataReader read = cmd.ExecuteReader();
int code = 0;
while (read.Read())
{
code = code + 1;
}
if (code == 1)
{
Response.Redirect("~/AuthKeyGEN.aspx?Auth=true&id=" + idstr + "&password=" + passpath + "");
}
if (code > 1)
{
Response.Redirect("~/Login.aspx?response=0");
}
if (code < 1)
{
Response.Redirect("~/Login.aspx?response=0");
}
}
I am using the same credentials in both scenarios,
So why is my response always 0 if I use parameters in here?
Thanks in advance!
Doesn't look anything wrong but try using OleDbType.VarChar instead of OleDbType.BSTR since both the parameter are of string type; like
cmd.Parameters.Add("#idstr", OleDbType.VarChar).Value = idstr;
cmd.Parameters.Add("#passpath", OleDbType.VarChar).Value = passpath;
Also as a side note, instead of using select * use a count() query like below in which case you can use ExecuteScalar() rather than using ExecuteReader()
"select count(*) from User_data
where Stamnummer=#idstr and Wachtwoord=#passpath";
Ms Access uses ? as parameter place holders and the order is important (your order is correct). The parameter objects can be named as the name is ignored by the engine so it really does not matter but might make for more readable code. See OleDbCommand.Parameters as reference.
cmd.CommandText = "select 1 from User_data where Stamnummer = ? and Wachtwoord= ?";
Also change the parameter types as #Rahul had pointed out to VarChar.
General recommendations
Wrap your connection in a using block.This ensures your connection is always closed even when an Exception is encountered.
Like #Rahul said use ExecuteScalar instead of ExecuteReader. Use either COUNT(*) or hardcode 1 as the result: select 1 from User_data ...
Never ever store passwords as plain text, ever! This is horrible practice and makes for a very unsecure app. I have submitted a complete solution to creating a password hash that you could copy/paste and use directly.
Related
I would like to create a simple login page in asp.net. here is my code:
protected void Button1_Click(object sender, EventArgs e)
{
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "Data Source=TEST-PC\\SQLSERVER2012;Initial Catalog=oncf;Integrated Security=True";
conn.Open();
string query = "SELECT COUNT(*) FROM Account WHERE acc_username= '" + TextBox1.Text + "' AND acc_password= '" + TextBox2.Text + "'";
SqlCommand cmd = new SqlCommand(query, conn);
SqlDataReader myreader = cmd.ExecuteReader();
int count = 0;
while(myreader.Read())
{
count = count + 1;
}
if(count==1)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
conn.Close();
}
I set a counter in order to know if the credentials entered are present in the DB. If the value of the counter goes to 1, the login is successful. Otherwise, the label with a message error is displayed!
However, whatever I enter as input in the username and login textboxes, it always redirect me to the other page !
For now, my concern is not the security aspects, I just want to test this simple code, I don't see any problem with the code, but still it doesnt work, it is driving me crazy...
The reason that you are always redirecting is that your reader always returns 1 row, whether there is a match or not. If there is a match in your database, then the query will return
(no column name)
---------------
1
If there is not a match then it will return:
(no column name)
---------------
0
Either way, myreader.Read() will return true, and you will increment count in this part:
while(myreader.Read())
{
count = count + 1;
}
if(count==1)
{
Response.Redirect("page2.aspx");
}
Rather than checking the if the query returns rows you can retrieve the value of the count using SqlCommand.ExecuteScalar(). In addition to this I would make three more changes:
1. Use parameterised queries
This is not just a security concern, parameterised queries are able to use cached plans, whereas if you concatenate the parameters into the query then a new plan is compliled for each new variable value. In addition, parameterised queries are more strongly typed, and you don't need to escape things like O'shea to ensure that your extra quote doesn't mess up the query.
2. Encrypt the passwords
This is directly to do with security so should really be overlooked as per your request to not comment on security, HOWEVER, this answer is not just for your benefit, and a half answer is likely to be read by someone in the future who may or may not be aware of the risks of storing plain text passwords. There is a simple encryption method in this answer.
3. Add using blocks to your code
A minor change, but when you have objects that implement IDisposable it is a good idea to use a using block to esnure they are disposed of properly.
So you might end up with:
string password = SomeStaticClass.Encrypt(TextBox2.Text);
string connectionString = "Data Source=TEST-PC\\SQLSERVER2012;Initial Catalog=oncf;Integrated Security=True";
string query = "SELECT UserCount = COUNT(*) FROM Account WHERE acc_username= #UserName AND acc_password= #Password";
using (var connection = new SqlConnection(connectionString))
using (var command = new SqlCommand(query, connection))
{
connection.Open();
command.Parameters.Add("#UserName", SqlDbType.VarChar, 50).Value = TextBox1.Text;
command.Parameters.Add("#Password", SqlDbType.VarChar, 50).Value = password;
int count = Convert.ToInt32(command.ExecuteScalar());
if(count==1)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
}
The problem you are experiencing is because the followinq query Always returns one row even if there isn't a match in the database:
SELECT COUNT(*) FROM Account WHERE acc_username=....
If there is no match, you get a row with one column, value 0.
You are checking the number of rows returned when you should just be checking the return value.
Use this instead
int count = Convert.ToInt32(cmd.ExecuteScalar());
if(count==1)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
I know you said you don't want advice on security but just to be sure:
Don't store passwords plain text in a database. Always hash them using a salt.
Don't use string concatenation when building sql. Use parameters.
don't use ExecuteReader when you want to return a single value, use ExecuteScalar:
int count = int.Pares(cmd.ExecuteScalar().toString());
if(count >= 1)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
You should always use Paremeterized queries Using parameters in SQL statements
string username=TextBox1.Text;
string password=TextBox2.Text;
SqlConnection conn = new SqlConnection();
conn.ConnectionString = "Data Source=TEST-PC\\SQLSERVER2012;Initial Catalog=oncf;Integrated Security=True";
conn.Open();
SqlCommand cmd = new SqlCommand("SELECT * FROM Account WHERE acc_username=#username and
AND acc_password=#password", conn);
cmd.Parameters.AddWithValue("#username",username);
cmd.Parameters.AddWithValue("#password",password);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataTable dt = new DataTable();
da.Fill(dt);
if (dt.Rows.Count > 0)
{
Response.Redirect("page2.aspx");
}
else
{
Label1.Visible = true;
}
Try adding if (myreader.HasRows) before while(myreader.Read())
I'm having a problem inputting variables into my database. I've seen other posts on how to pass a variable through by just escaping it, but those solutions do not apply because I am getting my variable's through an API. I'm cycling though data with a foreach loop by the way.
level = "" + x.Account_Level + "";
name = "" + x.name + "";
command.CommandText = "INSERT INTO `data` (`level`, `name`) VALUES(" + level + ", " + name + ")";
command.ExecuteNonQuery();
Sometimes, a variable will come back with an apostrophe and will screw up the code. Is it possible to insert a slash before every apostrophe or is there a way like in PHP to just push the whole variable through with single quotes? Thanks!
Edit:
Would this work? I think I need to add the i to change the name of the parameter each loop, due to it claiming the parameter as already declared.
using (var web = new WebClient())
{
web.Encoding = System.Text.Encoding.UTF8;
var jsonString = responseFromServer;
var jss = new JavaScriptSerializer();
var MatchesList = jss.Deserialize<List<Matches>>(jsonString);
string connectString = "Server=myServer;Database=myDB;Uid=myUser;Pwd=myPass;";
MySqlConnection connect = new MySqlConnection(connectString);
MySqlCommand command = connect.CreateCommand();
int i = 1;
connect.Open();
foreach (Matches x in MatchesList)
{
command.CommandText = "INSERT INTO `data` (`level`, `name`) VALUES(?level" + i + ", ?name" + i + ")";
command.Parameters.AddWithValue("level" + i, x.Account_Level);
command.Parameters.AddWithValue("mode" + i, x.name);
command.ExecuteNonQuery();
i++;
}
connect.Close();
}
The quick and dirty fix is to use something like:
level = level.Replace("'","whatever");
but there are still problems with that. It won't catch other bad characters and it probably won't even work for edge cases on the apostrophe.
The best solution is to not construct queries that way. Instead, learn how to use parameterised queries so that SQL injection attacks are impossible, and the parameters work no matter what you put in them (within reason, of course).
For example (off the top of my head so may need some debugging):
MySqlCommand cmd = new MySqlCommand(
"insert into data (level, name) values (?lvl, ?nm)", con);
cmd.Parameters.Add(new MySqlParameter("lvl", level));
cmd.Parameters.Add(new MySqlParameter("nm", name));
cmd.ExecuteNonQuery();
I have this legacy code :
private void conecta()
{
if (conexao.State == ConnectionState.Closed)
conexao.Open();
}
public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
{
List<string[]> historicos = new List<string[]>();
conecta();
sql =
#"SELECT *
FROM historico_verificacao_email
WHERE nm_email = '" + email + #"'
ORDER BY dt_verificacao_email DESC, hr_verificacao_email DESC";
com = new SqlCommand(sql, conexao);
SqlDataReader dr = com.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
string[] dados_historico = new string[6];
dados_historico[0] = dr["nm_email"].ToString();
dados_historico[1] = dr["dt_verificacao_email"].ToString();
dados_historico[1] = dados_historico[1].Substring(0, 10);
dados_historico[2] = dr["hr_verificacao_email"].ToString();
dados_historico[3] = dr["ds_tipo_verificacao"].ToString();
sql =
#"SELECT COUNT(e.cd_historico_verificacao_email) QT
FROM emails_lidos e
WHERE e.cd_historico_verificacao_email =
'" + dr["cd_historico_verificacao_email"].ToString() + "'";
tipo_sql = "seleção";
conecta();
com2 = new SqlCommand(sql, conexao);
SqlDataReader dr3 = com2.ExecuteReader();
while (dr3.Read())
{
//quantidade de emails lidos naquela verificação
dados_historico[4] = dr3["QT"].ToString();
}
dr3.Close();
conexao.Close();
//login
dados_historico[5] = dr["cd_login_usuario"].ToString();
historicos.Add(dados_historico);
}
dr.Close();
}
else
{
dr.Close();
}
conexao.Close();
return historicos;
}
I have created two separates commands to correct the issue, but it still continues: "There is already an open DataReader associated with this Command which must be closed first".
An additional info: the same code is working in another app.
Just add the following in your connection string:
MultipleActiveResultSets=True;
The optimal solution could be to try to transform your solution into a form where you don't need to have two readers open at a time. Ideally it could be a single query. I don't have time to do that now.
If your problem is so special that you really need to have more readers open simultaneously, and your requirements allow not older than SQL Server 2005 DB backend, then the magic word is MARS (Multiple Active Result Sets). http://msdn.microsoft.com/en-us/library/ms345109%28v=SQL.90%29.aspx. Bob Vale's linked topic's solution shows how to enable it: specify MultipleActiveResultSets=true in your connection string. I just tell this as an interesting possibility, but you should rather transform your solution.
in order to avoid the mentioned SQL injection possibility, set the parameters to the SQLCommand itself instead of embedding them into the query string. The query string should only contain the references to the parameters what you pass into the SqlCommand.
You can get such a problem when you are two different commands on same connection - especially calling the second command in a loop. That is calling the second command for each record returned from the first command. If there are some 10,000 records returned by the first command, this issue will be more likely.
I used to avoid such a scenario by making it as a single command.. The first command returns all the required data and load it into a DataTable.
Note: MARS may be a solution - but it can be risky and many people dislike it.
Reference
What does "A severe error occurred on the current command. The results, if any, should be discarded." SQL Azure error mean?
Linq-To-Sql and MARS woes - A severe error occurred on the current command. The results, if any, should be discarded
Complex GROUP BY on DataTable
I suggest creating an additional connection for the second command, would solve it. Try to combine both queries in one query. Create a subquery for the count.
while (dr3.Read())
{
dados_historico[4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
}
Why override the same value again and again?
if (dr3.Read())
{
dados_historico[4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
}
Would be enough.
I bet the problem is being shown in this line
SqlDataReader dr3 = com2.ExecuteReader();
I suggest that you execute the first reader and do a dr.Close(); and the iterate historicos, with another loop, performing the com2.ExecuteReader().
public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
{
List<string[]> historicos = new List<string[]>();
conecta();
sql = "SELECT * FROM historico_verificacao_email WHERE nm_email = '" + email + "' ORDER BY dt_verificacao_email DESC, hr_verificacao_email DESC";
com = new SqlCommand(sql, conexao);
SqlDataReader dr = com.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
string[] dados_historico = new string[6];
dados_historico[0] = dr["nm_email"].ToString();
dados_historico[1] = dr["dt_verificacao_email"].ToString();
dados_historico[1] = dados_historico[1].Substring(0, 10);
//System.Windows.Forms.MessageBox.Show(dados_historico[1]);
dados_historico[2] = dr["hr_verificacao_email"].ToString();
dados_historico[3] = dr["ds_tipo_verificacao"].ToString();
dados_historico[5] = dr["cd_login_usuario"].ToString();
historicos.Add(dados_historico);
}
dr.Close();
sql = "SELECT COUNT(e.cd_historico_verificacao_email) QT FROM emails_lidos e WHERE e.cd_historico_verificacao_email = '" + dr["cd_historico_verificacao_email"].ToString() + "'";
tipo_sql = "seleção";
com2 = new SqlCommand(sql, conexao);
for(int i = 0 ; i < historicos.Count() ; i++)
{
SqlDataReader dr3 = com2.ExecuteReader();
while (dr3.Read())
{
historicos[i][4] = dr3["QT"].ToString(); //quantidade de emails lidos naquela verificação
}
dr3.Close();
}
}
return historicos;
Add MultipleActiveResultSets=true to the provider part of your connection string. See the example below:
<add name="DbContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=dbName;Persist Security Info=True;User ID=userName;Password=password;MultipleActiveResultSets=True" providerName="System.Data.SqlClient" />
Try to combine the query, it will run much faster than executing an additional query per row.
Ik don't like the string[] you're using, i would create a class for holding the information.
public List<string[]> get_dados_historico_verificacao_email_WEB(string email)
{
List<string[]> historicos = new List<string[]>();
using (SqlConnection conexao = new SqlConnection("ConnectionString"))
{
string sql =
#"SELECT *,
( SELECT COUNT(e.cd_historico_verificacao_email)
FROM emails_lidos e
WHERE e.cd_historico_verificacao_email = a.nm_email ) QT
FROM historico_verificacao_email a
WHERE nm_email = #email
ORDER BY dt_verificacao_email DESC,
hr_verificacao_email DESC";
using (SqlCommand com = new SqlCommand(sql, conexao))
{
com.Parameters.Add("email", SqlDbType.VarChar).Value = email;
SqlDataReader dr = com.ExecuteReader();
while (dr.Read())
{
string[] dados_historico = new string[6];
dados_historico[0] = dr["nm_email"].ToString();
dados_historico[1] = dr["dt_verificacao_email"].ToString();
dados_historico[1] = dados_historico[1].Substring(0, 10);
//System.Windows.Forms.MessageBox.Show(dados_historico[1]);
dados_historico[2] = dr["hr_verificacao_email"].ToString();
dados_historico[3] = dr["ds_tipo_verificacao"].ToString();
dados_historico[4] = dr["QT"].ToString();
dados_historico[5] = dr["cd_login_usuario"].ToString();
historicos.Add(dados_historico);
}
}
}
return historicos;
}
Untested, but maybee gives some idea.
Here is a background on my program: each protein is made from a sequence of amino acids(or AA)
I have some tables :tblProInfo(that contains general info about proteins),tblOrderAA(that contains the sequence(AA sequence) of specific protein(for each protein there is a serial number that i set before))
now, I'm trying to retvive the science names of the protein that contains part of sequence that the user put in textbox1. It is likely that more than one protein contains the sequence that the user typed.
Here is my code. I got "Syntax error" and I'm sure I have more mistakes.Please HELP me!
public void OpenDB()
{
dataConnection = new OleDbConnection();
try
{
dataConnection.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Projects_2012\\Project_Noam\\Access\\myProject.accdb";
dataConnection.Open();
}
catch (Exception e)
{
MessageBox.Show("Error accessing the database: " +
e.Message,
"Errors",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
private string FromCodonsToProtein(string codons)
{
OpenDB();
int sizePro=0, i,counter=0,serialPro;
string st="",tempst="";
OleDbCommand datacommand = new OleDbCommand();
datacommand.Connection = dataConnection;
datacommand.CommandText = "SELECT tblProInfo.proInfoAAnum, tblProInfo.proInfoSerialNum,tblProInfo.proInfoScienceName FROM tblProInfo";
OleDbDataReader dataReader = datacommand.ExecuteReader();
while(dataReader.Read())
{
sizePro = dataReader.GetInt32(counter);
serialPro= dataReader.GetInt32(counter+1);
counter++;
OleDbCommand cmd= new OleDbCommand();
cmd.Connection = dataConnection;
cmd.CommandText = "SELECT tblOrderAA.orderAACodon1 FROM tblOrderAA"
+"WHERE (((tblOrderAA.orderAASerialPro)='"+serialPro+"'))";
OleDbDataReader rdr = cmd.ExecuteReader();
tempst="";
for (i = 0; i > sizePro; i++)
{
tempst = tempst + rdr.GetString(i);
}
if (tempst.Contains(codons))
{
st = st + " \n" + dataReader.GetString(counter);
}
}
return st;
}
Missing a space here
cmd.CommandText = "SELECT tblOrderAA.orderAACodon1 FROM tblOrderAA"
+"WHERE (((tblOrderAA.orderAASerialPro)='"+serialPro+"'))";
rewrite in this way
cmd.CommandText = "SELECT tblOrderAA.orderAACodon1 FROM tblOrderAA"
+" WHERE (((tblOrderAA.orderAASerialPro)='"+serialPro+"'))";
// ^ here
However you should use parametrized query (also with msaccess) to avoid possible errors and injection attacks.
Another problem is the global dataConnection. Don't do that, you gain nothing in this way.
Return the connection and encapsulate it with a using statement.
For example:
public OleDbConnection OpenDB()
{
dataConnection = new OleDbConnection();
dataConnection.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\Projects_2012\\Project_Noam\\Access\\myProject.accdb";
dataConnection.Open();
return dataConnection;
}
then in the calling code use this syntax
using(OleDbConnection cnn = OpenDB())
{
// in the rest of your code, replace dataConnection with cnn
// The using statement will ensure that in the case of exceptions
// your connection will be allways closed and properly disposed
........
}
EDIT: Can't give you a full working solutions, too many aspects of your problem are unknown to me, however a great simplification will be to change your query in this way
SELECT DISTINCT
tblProInfo.proInfoAAnum,
tblProInfo.proInfoSerialNum,
tblProInfo.proInfoScienceName
FROM tblProInfo LEFT JOIN tblOrderAA
ON tblOrderAA.orderAASerialPro = tblProInfo.proInfoSerialNum
WHERE tblOrderAA.orderAACodon1 = #codons
Try it directly in access using its query editor, if it works as you expected then change your code. You don't need two query and crossed loops to get the results.
Don't know why I can't reply to people on here with only a small amount of comment text, but my revised code exceeds that so I'm posting new.
This web service always returns 0. If I run it in SSMS it returns 3... not sure why, any ideas?
string ConnString = "Removed";
String query = "DECLARE #userSID varchar(255) SELECT COUNT(AD_SID) As ReturnCount FROM AD_Authorization WHERE AD_SID = #userSID ";
using (OleDbConnection conn = new OleDbConnection(ConnString))
{
using (OleDbCommand cmd = new OleDbCommand(query, conn))
{
cmd.Parameters.AddWithValue("userSID", SpartaCrypto.SpartaEncryptAES(userSID.ToString(), "s3cret!"));
conn.Open();
int returnCount = (Int32)cmd.ExecuteScalar();
conn.Close();
if (returnCount > 1)
{
return 1;
}
else
{
return 0;
}
}
}
Your query is not a good OLEDB parameterized query.
Try this instead:
"SELECT COUNT(AD_SID) As ReturnCount FROM AD_Authorization WHERE AD_SID = #userSID";
Also, the parameter name should match:
cmd.Parameters.AddWithValue("#userSID", SpartaCrypto.SpartaEncryptAES(userSID.ToString(), "s3cret!"));