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.
Related
I want to query a table and only need one cell returned. Right now the only way I can think to do it is:
$query = $this->db->query('SELECT id FROM crops WHERE name = "wheat"');
if ($query->num_rows() > 0) {
$row = $query->row();
$crop_id = $row->id;
}
What I want is, since I'm select 'id' anyway, for that to be the result. IE: $query = 'cropId'.
Any ideas? Is this even possible?
Of course it's possible. Just use AND in your query:
$query = $this->db->query('SELECT id FROM crops WHERE name = "wheat" AND id = {$cropId}');
Or you could use the raw power of the provided Active Record class:
$this->db->select('id');
$this->db->from('crops');
$this->db->where('name','wheat');
$this->db->where('id',$cropId);
$query = $this->db->get();
If you just want the cropId from the whole column:
foreach ($query->result()->id as $cropId)
{
echo $cropId;
}
Try this out, I'm not sure if it will work:
$cropId = $query->first_row()->id;
Note that you want to swap your quotes around: use " for your PHP strings, and ' for your SQL strings. First of all, it would not be compatible with PostgreSQL and other database systems that check such things.
Otherwise, as Christopher told you, you can test the crop identifier in your query. Only if you define a string between '...' in PHP, the variables are not going to be replaced in the strings. So he showed the wrong PHP code.
"SELECT ... $somevar ..."
will work better.
Yet, there is a security issue in writing such strings: it is very dangerous because $somevar could represent some additional SQL and completely transform your SELECT in something that you do not even want to think about. Therefore, the Active Record as mentioned by Christopher is a lot safer.
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 code for query:
$repository = $em->getRepository('AcmeCrawlerBundle:Trainings');
$query = $repository->createQueryBuilder('p')
->where('p.title LIKE :word')
->orWhere('p.discription LIKE :word')
->setParameter('word', $word)
->getQuery();
$trainings = $query->getResult();
The problem is: even if matches exist, they not found by this query. I used this code to see full sql:
print_r(array(
'sql' => $query->getSQL(),
'parameters' => $query->getParameters(),
));
And what I've got:
FROM Trainings t0_ WHERE t0_.title LIKE ? OR t0_.discription LIKE ? [parameters] => Array ( [word] => Spoken )
(last part of query)
Tell me please what to change?
You forgot the % signs around the word:
->setParameter('word', '%'.$word.'%')
Below are some additional steps you can take to further sanitise input data.
You should escape the term that you insert between the percentage signs:
->setParameter('word', '%'.addcslashes($word, '%_').'%')
The percentage sign '%' and the symbol underscore '_' are interpreted as wildcards by LIKE. If they're not escaped properly, an attacker might construct arbitrarily complex queries that can cause a denial of service attack. Also, it might be possible for the attacker to get search results he is not supposed to get. A more detailed description of attack scenarios can be found here: https://stackoverflow.com/a/7893670/623685
I want to understand why its not possible to inject sql statements in the form POST parameters. I tried it with sqlmap and manually without success.
There is a defined function:
function mysql_get_result($sQuery, $hSocket) //$sQuery: mysql query,
$hSocket:MySQLSocket(mysql_connect).
{
$sResource = mysql_query($sQuery, $hSocket);
list($sValue) = mysql_fetch_row($sResource);
return $sValue;
}
and there is the actual query for the POST parameter:
(mysql_get_result("SELECT place FROM towns
WHERE place = '".$sR_place."' AND num = '".$iR_num."'", $hMySQLSocket)
== $sR_place and $sR_place != '')
No input sanitiazion at all. So why isnt it working ? Does sql injection only work when its mysql_query function without more steps to do?
Your code should vulnerable to SQL injection, since you're not sanitizing with mysql_real_escape_string, or parameterizing the query.
You should try revising your POST parameters, because the issue is most likely that your attack isn't properly formatted. Try a variety of attacks.
Setting the DBIC_TRACE environment variable to true:
BEGIN { $ENV{DBIC_TRACE} = 1 }
generates very helpful output, especially showing the SQL query that is being executed, but the SQL query is all on one line.
Is there a way to push it through some kinda "sql tidy" routine to format it better, perhaps breaking it up over multiple lines? Failing that, could anyone give me a nudge into where in the code I'd need to hack to add such a hook? And what the best tool is to accept a badly formatted SQL query and push out a nicely formatted one?
"nice formatting" in this context simply means better than "all on one line". I'm not particularly fussed about specific styles of formatting queries
Thanks!
As of DBIx::Class 0.08124 it's built in.
Just set $ENV{DBIC_TRACE_PROFILE} to console or console_monochrome.
From the documentation of DBIx::Class::Storage
If DBIC_TRACE is set then trace information is produced (as when the
debug method is set). ...
debug Causes trace information to be emitted on the debugobj
object. (or STDERR if debugobj has not specifically been set).
debugobj Sets or retrieves the object used for metric collection.
Defaults to an instance of DBIx::Class::Storage::Statistics that is
compatible with the original method of using a coderef as a callback.
See the aforementioned Statistics class for more information.
In other words, you should set debugobj in that class to an object that subclasses DBIx::Class::Storage::Statistics. In your subclass, you can reformat the query the way you want it to be.
First, thanks for the pointers! Partial answer follows ....
What I've got so far ... first some scaffolding:
# Connect to our db through DBIx::Class
my $schema = My::Schema->connect('dbi:SQLite:/home/me/accounts.db');
# See also BEGIN { $ENV{DBIC_TRACE} = 1 }
$schema->storage->debug(1);
# Create an instance of our subclassed (see below)
# DBIx::Class::Storage::Statistics class
my $stats = My::DBIx::Class::Storage::Statistics->new();
# Set the debugobj object on our schema's storage
$schema->storage->debugobj($stats);
And the definition of My::DBIx::Class::Storage::Statistics being:
package My::DBIx::Class::Storage::Statistics;
use base qw<DBIx::Class::Storage::Statistics>;
use Data::Dumper qw<Dumper>;
use SQL::Statement;
use SQL::Parser;
sub query_start {
my ($self, $sql_query, #params) = #_;
print "The original sql query is\n$sql_query\n\n";
my $parser = SQL::Parser->new();
my $stmt = SQL::Statement->new($sql_query, $parser);
#printf "%s\n", $stmt->command;
print "The parameters for this query are:";
print Dumper \#params;
}
Which solves the problem about how to hook in to get the SQL query for me to "pretty-ify".
Then I run a query:
my $rs = $schema->resultset('SomeTable')->search(
{
'email' => $email,
'others.some_col' => 1,
},
{ join => 'others' }
);
$rs->count;
However SQL::Parser barfs on the SQL generated by DBIx::Class:
The original sql query is
SELECT COUNT( * ) FROM some_table me LEFT JOIN others other_table ON ( others.some_col_id = me.id ) WHERE ( others.some_col_id = ? AND email = ? )
SQL ERROR: Bad table or column name '(others' has chars not alphanumeric or underscore!
SQL ERROR: No equijoin condition in WHERE or ON clause
So ... is there a better parser than SQL::Parser for the job?