I'm currently working on Yii SQL Injection. I have the following command sql command to run:
SELECT p.email, p.email_secret, p.verificationcode, r.name
FROM personal p
JOIN profile r
ON p.email='example#example.com'
I have written the following code with yii:
$connection=Yii::app()->db;
$command=$connection->createCommand();
$command->select('p.email, p.email_secret, p.verificationcode, r.name');
$command->from('personal p');
$command->join('profile r', 'p.email = r.email');
$command->where('p.email=:email', array(':email'=>'yeoh.chan1#gmail.com'));
$rows=$command->queryAll();
I would like to know where this would be vulnerable SQL Injection and if so, what would be a better approach to deal with table joinings.
Since this has no variables in it, there is no possibility for SQL injection. However, I am guessing that you are planning to pass the email address in as a parameter, and since you have the :email parameter marker you are safe.
Just a tidbit here, you don't have to repeat $command-> on every line. You can write it like this:
$connection=Yii::app()->db;
$command=$connection->createCommand();
$command->select('p.email, p.email_secret, p.verificationcode, r.name')
->from('personal p')
->join('profile r', 'p.email = r.email')
->where('p.email=:email', array(':email'=>'yeoh.chan1#gmail.com'));
$rows=$command->queryAll();
This works because all the statement clauses (except distinct) return the command object, and can be strung together. If you need distinct, you can make ->setDistinct() the last item in the chain.
Related
What's wrong with this statement?
SELECT aufk~aufnr
zmm_limit_co~vd zmm_limit_co~matkl_code
zmm_limit_matkl~sign
FROM aufk
JOIN zmm_limit_co ON zmm_limit_co~auart = aufk~auart
left JOIN zmm_limit_matkl
on zmm_limit_matkl~matkl = zmm_limit_matkl~matkl_code
INTO CORRESPONDING FIELDS OF table lt_input
WHERE aufk~aufnr = <lf_new_pos>-aufnr.
When I'm trying to execute program, ABAP gives me an error:
'The elements in the "SELECT LIST" list must be separated using commas.'
I suppose, that the error is somehow connected with JOINs, when I'm removing "left" from it - it's compiling just fine, but with it.
You are (probably inadvertently) mixing the old (now obsolete) and new syntax of the OpenSQL SELECT statement, triggering this rather less-than-helpful error message. Check the release-specific change notes for some details on the changes. However, in the example given, I believe that the second join condition is the problem: You're not joining the contents of zmm_limit_matkl with any of the other two tables, but with itself. That doesn't look right and might confuse the compiler.
In my controller method for the the index view I have the following line.
#students_instance = Student.includes(:memo_tests => {:memo_target => :memo_level})
So for each Student I eager-load all necessary info.
Later on in a .map block, I call the .where() method on one of the relations as shown below.
#all_students = #students_instance.map do |student|
...
last_pass = student.memo_tests.where(:result => true).last.created_at.utc
difference_in_weeks = ((last_pass.to_i - current_date.to_i) / 1.week).round
...
end
This leads to a single SQL query for each student. And since I have over 300+ students, leads to very slow load times and over 300+ SQL queries.
Am I right in thinking that this is caused by the .where() method. I think this because I have checked everything else and these are the two lines that cause all of the queries.
More importantly, is there a better way to do this that reduces these queries to a single query?
The moment you ask where, the statement is translated to a query. Normally, the result should be sql-cached...
Anyway, in order to be sure, you can instead add programming logic to your statement. That way, you are not requesting a NEW sql statement.
last_pass = student.memo_tests.map {|m| m.created_at if m.result}.compact.sort.last
EDIT
I see the OP's question does not require sorting... So, leaving the sorting out:
last_pass = student.memo_tests.map {|m| m.created_at if m.result}.compact.last
compact is required to remove nil results from the array.
Is it possible to use a wildcard in a SQL LIKE statement within a ColdFusion cfscript query?
An example that doesn't work:
local.q = new Query();
local.q.setDatasource(variables.dsn);
local.q.addParam(name='lastname', value='%' & arguments.lastname, cfsqltype="cf_sql_varchar");
local.qString = 'SELECT name FROM users WHERE lastname LIKE :lastname';
local.q.setSQL(local.qString);
local.result = local.q.execute().getResult();
I also tried these, which didn't work:
local.qString = 'SELECT name FROM users WHERE lastname LIKE %:lastname';
local.qString = "SELECT name FROM users WHERE lastname LIKE '%:lastname'";
UPDATE:
I am using MS SQL Server 2008.
The query works fine within SQL Server Mgmt Studio... I think it has something to do with how to format the query within cfscript tags?
Yes, it is possible. You're setting it in the param, which is correct. I'm not sure why it's not working with you.
I did the following and it worked.
var qryArgsCol = {};
qryArgsCol.datasource = variables.datasource;
qryArgsCol.SQL = "
SELECT ID
FROM Users
WHERE LastName LIKE :searchStringParam
";
var qryGetID = new query(argumentCollection=qryArgsCol);
qryGetID.addParam(name="searchStringParam", value="%" & searchString, cfsqltype="cf_sql_varchar");
qryGetIDResult = qryGetID.execute().getResult();
There's a response here from Adam Cameron, which was apparently deleted by an overzealous mod.
Rather than repeat what he says, I've just copied and pasted (with emphasis added to the key parts):
Just to clarify that the syntax you tried in your first example does work. That is the correct approach here. To clarify / explain:
The <cfquery> version of the example you have would be along the lines of:
<cfqueryparam value="%foo">
So in the function version, the param would be ? or :paramName and the value of the param would continue to be "%foo".
The % is part of the param value, not the SQL string.
So given that "doesn't work" for you, it would help if you posted the error, or whatever it is that causes you to think it's not working (what your expectation is, and what the actual results are). Then we can deal with the actual cause of your problem, which is not what you think it is, I think.
Does the query work fine as a <cfquery>?
Depending on the dbms used, that single and double quotes may be interpreted when the sql statement is run. What dbms are you using? Your statement now doesn't select for the value in the variable, but for any user whose lastname is "lastname". It should be something like:
lastname like '%#lastname#'
Just remember that you ultimately need to see what CF gives the DB server. In this instance, you can try this mockup to get close and find the same error in SSMS by messing with the quotes/value in the param declaration:
declare #param1 varchar(max) = '%Eisenlohr';
SELECT name FROM users WHERE lastname LIKE #param1
I just ran into the same problem as the original poster where it "wasn't working" and I didn't get any results from the query of queries.
The problem for me is that the wildcard search is case-sensitive.
local.q = new Query();
local.q.setDatasource(variables.dsn);
local.q.addParam(name='lastname', value='%' & LCase(arguments.lastname), cfsqltype="cf_sql_varchar");
local.qString = 'SELECT name FROM users WHERE LOWER(lastname) LIKE :lastname';
local.q.setSQL(local.qString);
local.result = local.q.execute().getResult();
So what I did was made sure the incoming argument was lower case and made sure the comparing field in the SQL was lower case as well and it worked.
Use like this.
local.q = new Query();
local.q.setDatasource(variables.dsn);
local.q.addParam(name="lastname", cfsqltype="cf_sql_varchar",value='%ARGUMENTS.lastname' );
local.qString = 'SELECT name FROM users WHERE lastname LIKE :lastname';
local.q.setSQL(local.qString);
local.result = local.q.execute().getResult();
I would suggest using the CFQuery tag instead of attempting to run queries within CFScript. Unless you REALLY know what you are doing. I say this because the CFQuery tag has some built-in functionality that not only makes building queries easier for you but may also protect you from unforeseen attacks (the SQL injection type). For example, when using CFQuery it will automatically escape single-quotes for you so that inserting things like 'well isn't that a mess' will not blow up on you. You also have the benefit of being able to use the CFQueryParam tag to further battle against SQL injection attacks. While you may be able to use the CFQueryParam functionality within CFScript it is not as straight forward (at least not for me).
See this blog post from Ben Nadel talking about some of this.
So in CFQuery tags your query would look something like this:
<cfquery name="myQuery" datasource="#variables.dsn#">
SELECT name
FROM users
WHERE lastname LIKE <cfqueryparam cfsqltype="cf_sql_varchar" value="%:#arguments.lastname#" maxlength="256" />
</cfquery>
I have this site with the following parameters:
http://www.example.com.com/pagination.php?page=4&order=comment_time&sc=desc
I use the values of each of the parameters as a value in a SQL query.
I am trying to test my application and ultimately hack my own application for learning purposes.
I'm trying to inject this statement:
http://www.example.com.com/pagination.php?page=4&order=comment_time&sc=desc' or 1=1 --
But It fails, and MySQL says this:
Warning: mysql_fetch_assoc() expects parameter 1 to be resource,
boolean given in /home/dir/public_html/pagination.php on line 132
Is my application completely free from SQL injection, or is it still possible?
EDIT: Is it possible for me to find a valid sql injection statement to input into one of the parameters of the URL?
The application secured from sql injection never produces invalid queries.
So obviously you still have some issues.
Well-written application for any input produces valid and expected output.
That's completely vulnerable, and the fact that you can cause a syntax error proves it.
There is no function to escape column names or order by directions. Those functions do not exist because it is bad style to expose the DB logic directly in the URL, because it makes the URLs dependent on changes to your database logic.
I'd suggest something like an array mapping the "order" parameter values to column names:
$order_cols = array(
'time' => 'comment_time',
'popular' => 'comment_score',
... and so on ...
);
if (!isset($order_cols[$_GET['order'])) {
$_GET['order'] = 'time';
}
$order = $order_cols[$_GET['order']];
Restrict "sc" manually:
if ($_GET['sc'] == 'asc' || $_GET['sc'] == 'desc') {
$order .= ' ' . $_GET['sc'];
} else {
$order .= ' desc';
}
Then you're guaranteed safe to append that to the query, and the URL is not tied to the DB implementation.
I'm not 100% certain, but I'd say it still seems vulnerable to me -- the fact that it's accepting the single-quote (') as a delimiter and then generating an error off the subsequent injected code says to me that it's passing things it shouldn't on to MySQL.
Any data that could possibly be taken from somewhere other than your application itself should go through mysql_real_escape_string() first. This way the whole ' or 1=1 part gets passed as a value to MySQL... unless you're passing "sc" straight through for the sort order, such as
$sql = "SELECT * FROM foo WHERE page='{$_REQUEST['page']}' ORDER BY data {$_REQUEST['sc']}";
... which you also shouldn't be doing. Try something along these lines:
$page = mysql_real_escape_string($_REQUEST['page']);
if ($_REQUEST['sc'] == "desc")
$sortorder = "DESC";
else
$sortorder = "ASC";
$sql = "SELECT * FROM foo WHERE page='{$page}' ORDER BY data {$sortorder}";
I still couldn't say it's TOTALLY injection-proof, but it's definitely more robust.
I am assuming that your generated query does something like
select <some number of fields>
from <some table>
where sc=desc
order by comment_time
Now, if I were to attack the order by statement instead of the WHERE, I might be able to get some results... Imagine I added the following
comment_time; select top 5 * from sysobjects
the query being returned to your front end would be the top 5 rows from sysobjects, rather than the query you try to generated (depending a lot on the front end)...
It really depends on how PHP validates those arguments. If MySQL is giving you a warning, it means that a hacker already passes through your first line of defence, which is your PHP script.
Use if(!preg_match('/^regex_pattern$/', $your_input)) to filter all your inputs before passing them to MySQL.
I have a single normal SQL statement in the middle of my Active Records query. Is there any way I can 'add' this query to the AR one without having two independent queries? The SQL is as follows if someone would help me 'convert' it into Active Records-style in case it doesn't work.
SELECT birthday
FROM usertable
WHERE STR_TO_DATE(birthday, '%d.%m.%Y')
BETWEEN $minimumDate AND $maximumDate
You can simply do this:
$this->db
->select('birthday')
->where("STR_TO_DATE(birthday, '%d.%m.%Y') BETWEEN $minimumDate AND $maximumDate")
->get('usertable');
But you will have to escape the variables yourself in this case.
You could also do this:
$this->db
->select('birthday')
->where("STR_TO_DATE(birthday, '%d.%m.%Y') >", $minimumDate)
->where("STR_TO_DATE(birthday, '%d.%m.%Y') <", $maximumDate)
->get('usertable');
This will automatically escape the second parameter of where(), but now we're running STR_TO_DATE() twice and have a slightly less elegant query (not using BETWEEN).
Active Record can be difficult to use MySQL functions with. Sometimes it's best to just do the query manually if you are not building it dynamically. CI offers some methods of doing this easily while escaping your input properly for you.
See the section in the user guide about Query Bindings:
$sql = "SELECT birthday
FROM usertable
WHERE STR_TO_DATE(birthday, '%d.%m.%Y')
BETWEEN ? AND ?";
$this->db->query($sql, array($minimumDate, $maximumDate));
You can do this -
SELECT birthday
FROM usertable
WHERE STR_TO_DATE(birthday, '%d.%m.%Y')
BETWEEN $minimumDate AND $maximumDate
by chaining like this -
$this->db->select('birthday')->from('usertable')->where('STR_TO_DATE', '%d.%m.%Y')->limit($minimumDate, $maximumDate);