Ran a security scan against an URL and received the report below:
The vulnerability affects
/rolecall.cfm , bbb_id
This is the rolecall.cfm code:
<cfscript>
if (isDefined("url") and isDefined("url.bbb_id")) {
if (url.dept_id eq -1)
_include("sql", "getB");
else
_include("sql", "getBNow");
}
/*...*/
_include("sql", "getDPlaces");
/*Set up the model and go*/
model = {
add = 1,
edit = 0,
remove = 0,
places = getDPlaces
};
</cfscript>
If you're using IIS, you should read this article to see how to add SQL Injection protection directly to the web server. This will keep attack requests from ever reaching ColdFusion.
Be cautious of the strings they suggest you deny:
<denyStrings>
<add string="--" />
<add string=";" />
<add string="/*" />
<add string="#" />
Make sure you never pass an email address as the value of a query string parameter, otherwise you'll reject a legitimate request. You can allow the # symbol if needed.
I would also highly suggest you take a look at HackMyCF, which will show you many other security concerns if they exist.
SQL Injection exploits databases by stuffing malicious sql commands into a query where they're not expected. Tricking the query into do something different than what it was designed to do, like performing a DROP or DELETE instead of a SELECT.
Queries that use raw client parameters like this, are vulnerable:
WHERE policy_funct_id = #url.dept_id#
Instead, always wrap client supplied parameters in cfqueryparam. It prevents them from being executed as a command. I don't know your column data types, so modify the cfsqltype as needed.
WHERE policy_funct_id = <cfqueryparam value="#url.dept_id#" cfsqltype="cf_sql_integer">
All of the dynamic table names are another (potential) vulnerability, like:
-- potential sql-injection risk
SELECT * FROM #db.root#
If #db.root# is user supplied, it's a sql-i risk. Unfortunately, cfqueryparam cannot be used on table names. Those must be manually (and carefully) validated.
Few other suggestions, unrelated to sql injection:
All the nested (select * from...) statements decrease readability. Instead, use a single level JOIN.
When using JOIN's, best to specify the source table (or table alias) for all columns. That avoids ambiguity and increases readability for yourself and anyone else reviewing the code. No need to guess which columns comes from which table.
Example
-- psuedo example
SELECT root.ColumnA
, root.ColumnB
, dept.ColumnC
, subcat.ColumnC
, etc...
FROM #db.root# root
INNER JOIN #db.content# content ON root.policy_root_id = content.content_id
INNER JOIN #db.dept# AS dept ON ON content.dept_id = dept.policy_funct_id
INNER JOIN #db.subcat# subcat ON subcat.dept_id = dept.policy_funct_id
WHERE dept.policy_funct_id = <cfqueryparam value="#url.dept_id#" cfsqltype="cf_sql_integer">
AND content.is_newest = 1
Related
I have this statement that is generated by Geoserver
SELECT
shape AS shape
FROM
(
SELECT
c.chantier_id id,
sdo_geom.sdo_buffer(c.shape, m.diminfo, 1) shape,
c.datedebut datedebut,
c.datefin datefin,
o.nom operation,
c.brouillon brouillon,
e.code etat,
u.utilisateur_id utilisateur,
u.groupe_id groupe
FROM
user_sdo_geom_metadata m, lyv_chantier c
JOIN lyv_utilisateur u ON c.createur_id = u.utilisateur_id
JOIN lyv_etat e ON c.etat_id = e.etat_id
JOIN lyv_operation o ON c.operation = o.id
WHERE
m.table_name = 'LYV_CHANTIER'
AND m.column_name = 'SHAPE'
) vtable
WHERE
( brouillon = 0
AND ( etat != 'archive'
OR etat IS NULL )
AND sdo_filter(shape, mdsys.sdo_geometry(2003, 4326, NULL, mdsys.sdo_elem_info_array(1, 1003, 1), mdsys.sdo_ordinate_array(
2.23365783691406, 48.665657043457, 2.23365783691406, 48.9341354370117, 2.76649475097656, 48.9341354370117, 2.76649475097656, 48.665657043457, 2.23365783691406, 48.665657043457)), 'mask=anyinteract querytype=WINDOW') = 'TRUE' );
On my local instance (dockerized if that can explain anything) it works fine, but on another instance I get an error :
ORA-13226: interface not supported without a spatial index
I guess that the SDO_FILTER is applied to the result of SDO_BUFFER which is therefore not indexed.
But why is it working on my local instance ?!
Is there some kind of weird configuration shenanigan that could explain the different behavior maybe ?
EDIT : The idea behind this is to get around a bug in Geoserver with Oracle databases where it renders only the first point of MultiPoint geometries, but works fine with MutltiPolygon.
I am using a SQL view as layer in Geoserver (hence the subselect I guess).
First, you need to do some debugging here.
Connect to each instance, on the same user as your Geoserver's datasource, and run the sql. From the same connections (in each instance) you must also verify that the user's metadata view (user_sdo_geom_metadata) have an entry for the table and the table has a spatial index - whose owner is the same user as the one you connect.
Also, your query ( select ... from 'vtable') has a column 'shape' which is a buffer of the column lyv_chantier.shape. The sdo_filter, in this sql, expects a spatial index on the vtable.shape - which cannot exist. You should try to use a different alias (e.g. buf_shape) and sdo_filter(buf_shape,...) - to see if the sql fails in both instances, as it should.
I'm in a bit of a hurry right now, so my instructions are summarized. If you want, do this debugging and post the results. We then can go into details.
EDIT: Judging from your efforts, I'd say that the simplest approach is: 1) add a second geometry column to lyv_chantier (e.g. buf_shp). 2) update lyv_chantier set buf_shp = sdo_geom.sdo_buffer(shape,...). 3) insert into user_sdo_geom_metadata the values (lyv_chantier, buf_shp, ...). 4) create a spatial index on column buf_shp. You may need to consider a trigger to update buf_shp whenever shape changes...
This is a very practical approach but you don't provide any info about your case (what is the oracle version, how many rows does the table have, how is it used, why do you want to use sdo_buffer, etc), so that's my recommendation for now.
Also, since you are, most likely, using an sql view as layer in Geoserver (you don't say anything about that, either), you could also consider using pure GS functionality to achieve your goal.
At the end, without describing your goal, it's difficult to provide anything more tailor-made.
I have recently implemented SQL rowversion to prevent concurrency issues in my system. I use rowversion in where clause when updating single rows in the tables. So far I have tested and seems like a good solution. Now I'm looking for an easy way to implement this feature in my system. Here is SP that runs when user wants to update the record:
CREATE PROCEDURE [dbo].[UpdateBuilding]
#Status BIT = NULL,
#Name VARCHAR(50) = NULL,
#Code CHAR(2) = NULL,
#OriginalRowVersion ROWVERSION
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN
UPDATE dbo.Building
SET Status = #Status,
Name = #Name,
Code = #Code,
ActionDt = CURRENT_TIMESTAMP
WHERE RowVersion = #OriginalRowVersion
IF ##ROWCOUNT = 0
BEGIN
RAISERROR('Buildingwith code %s was modified or deleted by another user.', 16, 1, #Code);
END;
END;
If I want to execute SP above I would need to pass required parameters. This is how I call SP in SQL Management Studio:
EXEC UpdateBuilding
#Status = 1,
#Name = "Rockefeller Center",
#Code = 436,
#OriginalRowVersion = 0x0000000000006955;
Now I started looking how to implement this in my system where I use ColdFusion to communicate with Datatbase. Here is example on how this procedure will be executed with CF 2016:
<cfstoredproc procedure="UpdateBuilding" datasource="#dsn#">
<cfprocparam dbvarname="#Status" value="#trim(arguments.status)#" cfsqltype="cf_sql_bit" />
<cfprocparam dbvarname="#Code" value="#trim(arguments.code)#" cfsqltype="cf_sql_char" maxlength="2" null="#!len(trim(arguments.code))#" />
<cfprocparam dbvarname="#Name" value="#trim(arguments.name)#" cfsqltype="cf_sql_varchar" maxlength="50" null="#!len(trim(arguments.name))#" />
<cfprocresult name="Result"/>
</cfstoredproc>
You can see that all values are passed with arguments that user submits in the form. However, updating based on PK value (Code column in my case) was pretty simple. Now I have binary value and that makes everything more complicated. First I use JSON to send the data to client side. Sending rowversion in JSON object would require converting that value to binary and then converting back when user submits the form. I'm wondering if there is better way to achieve this? Ideally I would not even send rowversion value to the user side. I woul keep that on the back end and once user submits the form pull row version value based on PK then call stored procedure. If anyone knows good way to handle this kind of situations please let me know. I have not used rowversion before and this is new to me.
I use a similar approach where I have a column named version of type int. I pass that to the client in any read operation. If the client updates a record, it must send back the version of the record being updating, and the update will increment the version number. However, my approach sets a ColdFusion lock instead of a DB lock. Here's some simplified logic:
function updateRecord (
required numeric recordID,
required struct updateData,
required numeric version) {
lock name="#arguments.recordID#" type="exclusive" timeout="1" throwontimeout=true {
qRecord = queryExecute() // get the record
if (qRecord.recordCount != 1) {
throw();
}
if (qRecord.version != arguments.version) {
throw();
}
// do the update using arguments.updateData
}
}
One issue with this is that other nodes in a cluster would not be aware of the named lock. You would have to come up with another way of locking that section of code to other requests across the cluster. There are ways to do it, as described in this thread:
https://dev.lucee.org/t/distributed-lock-management/1004
And if this is an issue, I'm sure there are other solutions available.
I am trying to avoid a sql injection. This topic has been dealt with in Java (How to prevent query injection on Google Big Query) and Php.
How is this accomplished in App Scripts? I did not find how to add a parameter to a SQL statement. Here is what I had hoped to do:
var sql = 'SELECT [row],etext,ftext FROM [hcd.hdctext] WHERE (REGEXP_MATCH(etext, esearch = ?) AND REGEXP_MATCH(ftext, fsearch = ?));';
var queryResults;
var resource = {
query: sql,
timeoutMs: 1000,
esearch='r"[^a-zA-z]comfortable"',
fsearch='r"[a-z,A-z]confortable"'
};
queryResults = BigQuery.Jobs.query(resource,projectNumber);
And then have esearch and fsearch filled in with the values (which could be set elsewhere).
That does not work, according to the doc.
Any suggestions on how to get a parameter in an SQL query? (I could not find a setString function...)
Thanks!
Unfortunately, BigQuery doesn't support this type of parameter substitution. It is on our list of features to consider, and I'll bump the priority since it seems like this is a common request.
The only suggestion that I can make in the mean time is that if you are building query strings by hand, you will need to make sure you escape them carefully (which is a non-trivial operation).
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.