Reputation: 33
I need to pass a where
condition from my client to the webservice, which performs the SQL query on an oracle database.
const res = await db.execute(`SELECT count(ID) FROM table WHERE ${where}`);
Since the where
condition can be totally dynamic, I cannot use prepared statements, a stored procedure or parameters.
Is it sufficient, to just run the where
condition through the following check?
static isDangerousSql(sql: string): boolean {
const characters = [ '--', ';'];
for (const char of characters) {
if (sql.indexOf(char) > -1) {
return true;
}
}
return false;
}
Upvotes: 2
Views: 93
Reputation: 167972
No, it is not sufficient.
If ${where}
is:
EXISTS (
SELECT 1
FROM security_table
WHERE userid = 1234
AND password_hash = CHR(65) || CHR(66) || CHR(67) || CHR(68)
)
Then it will:
security_table
; userid
and password_hash
; and1234
and the password hash ABCD
This can be used to map your entire database structure and to check for the existence of any data. Definitely a vulnerability.
An example would be
WHERE status = 'Active' and ID in (SELECT cell_id FROM alerts WHERE alert_status = 1)
Rather than use dynamic SQL, you can list the valid filter conditions in your query and use bind parameters to populate the filter conditions:
So a slightly more complicated example would be:
SELECT count(ID)
FROM table t
WHERE ( :status IS NULL OR status = :status )
AND ( ( :alert_status IS NULL AND :other_status IS NULL )
OR EXISTS (
SELECT 1
FROM alerts a
WHERE t.id = a.cell_id
AND ( :alert_status IS NULL OR alert_status = :alert_status )
AND ( :other_status IS NULL OR other_status = :other_status )
) )
Then you have a static query and can pass in the (named) bind variables :status
, :alert_status
and :other_status
.
If you do not want to have a single static query with all the parameters then you can build the filters from fixed snippet component parts in the middle tier.
Some pseudo-code:
sql = "SELECT count(ID) FROM table";
filters = [];
bind_parameters = [];
if ( [ 'Active', 'Inactive' ].indexOf( user_input.status ) > -1 )
{
filters.push( "status = ?" )
bind_parameters.push( user_input.status );
}
if ( user_input.active_status == 0 || user_input.active_status == 1 )
{
filters.push( "id IN ( SELECT cell_id FROM alerts WHERE alert_status = ?)" );
bind_parameters.push( user_input.active_status );
}
if ( filters.length > 0 )
{
sql = sql + " WHERE " + filters.join( " AND " );
}
db.setSQL( sql );
for ( i = 0; i < bind_parameters.length; i++ )
{
db.setBindParameter( i, bind_parameters[i] );
}
const res = await db.executeQuery();
(The above pseudo-code is to indicate the concept of building the query dynamically in the middle tier from fixed snippets and does not consider implementation details such as setting the data type of the bind parameters and probably many other things - use the idea with caution and validate your implementation's security before you put it into production.)
Upvotes: 5