This question already has answers here:
Can PHP PDO Statements accept the table or column name as parameter?
(8 answers)
Closed 2 months ago.
I've used the mysqli_stmt_bind_param function several times. However, if I separate variables that I'm trying to protect against SQL injection I run into errors.
Here's some code sample:
function insertRow( $db, $mysqli, $new_table, $Partner, $Merchant, $ips, $score, $category, $overall, $protocol )
{
$statement = $mysqli->prepare("INSERT INTO " .$new_table . " VALUES (?,?,?,?,?,?,?);");
mysqli_stmt_bind_param( $statment, 'sssisss', $Partner, $Merchant, $ips, $score, $category, $overall, $protocol );
$statement->execute();
}
Is it possible to somehow replace the .$new_table. concatenation with another question mark statement, make another bind parameter statement, or add onto the existing one to protect against SQL injection?
Like this or some form of this:
function insertRow( $db, $mysqli, $new_table, $Partner, $Merchant, $ips, $score, $category, $overall, $protocol )
{
$statement = $mysqli->prepare("INSERT INTO (?) VALUES (?,?,?,?,?,?,?);");
mysqli_stmt_bind_param( $statment, 'ssssisss', $new_table, $Partner, $Merchant, $ips, $score, $category, $overall, $protocol );
$statement->execute();
}
Short answer to your question is "no".
In the strictest sense, at the database level, prepared statements only allow parameters to be bound for "values" bits of the SQL statement.
One way of thinking of this is "things that can be substituted at runtime execution of the statement without altering its meaning". The table name(s) is not one of those runtime values, as it determines the validity of the SQL statement itself (ie, what column names are valid) and changing it at execution time would potentially alter whether the SQL statement was valid.
At a slightly higher level, even in database interfaces that emulate prepared statement parameter substitution rather than actually send prepared statements to the database, such as PDO, which could conceivably allow you to use a placeholder anywhere (since the placeholder gets replaced before being sent to the database in those systems), the value of the table placeholder would be a string, and enclosed as such within the SQL sent to the database, so SELECT * FROM ? with mytable as the param would actually end up sending SELECT * FROM 'mytable' to the database, which is invalid SQL.
Your best bet is just to continue with
SELECT * FROM {$mytable}
but you absolutely should have a white-list of tables that you check against first if that $mytable is coming from user input.
The same rule applies when trying to create a "database".
You cannot use a prepared statement to bind a database.
I.e.:
CREATE DATABASE IF NOT EXISTS ?
will not work. Use a safelist instead.
Related
I need to use this condition in my select statement:
WHERE YEAR(date) = YEAR(CURDATE())
but if I do, like this:
$query = db_select('table', 't');
$query->fields('t');
$query->condition('YEAR\(date\)', 'YEAR(CURDATE())', '=');
Drupal won't have it (even if I do not escape those parenthesis - it simply ignores them) because I get an error:
Column not found: 1054 Unknown column 'YEARdate' in 'where clause':
How to overcome this error?
Hmm.. just like this, it seems:
$query->where('YEAR(date) = YEAR(CURDATE())');
The where allows for the arbitrary SQL:
The where() method allows for the addition of arbitrary SQL as a conditional fragment. $snippet may contain any legal SQL fragment, and if it has variable content it must be added using a named placeholder. The $args array is an array of placeholders and values that will be substituted into the snippet. It is up to the developer to ensure that the snippet is valid SQL. No database-specific modifications are made to the snippet.
Hm, you could also use db_query, it allow you to write SQL queries "without Drupal".
I mean, you'll be able to add custom WHERE statements or any SQL-proper functions, like custom functions ;)
Eg.
$result = db_query('SELECT title FROM {node} WHERE type = "%s" AND title LIKE "%%%s%%"', 'type', 'title');
Use addExpression method :
https://api.drupal.org/api/drupal/includes!database!select.inc/function/SelectQuery%3A%3AaddExpression/7.x
$query = db_select('table', 't');
$query->fields('t');
$query->addExpression('YEAR(t.date) = YEAR(CURDATE())');
$result = $query->execute()->fetchAll();
var_dump($result);
I'm attempting to query against a materialized path stored with postgres ltree type from a Grails application. Unfortunately, my query uses the "?" operator which is being captured by GORM as a parameter
sqlRestriction("materialized_path ? (SELECT ARRAY(SELECT CAST(CAST(subpath(?,0,generate_series) AS text) ||'.*{1}' AS lquery) FROM generate_series(1,nlevel(CAST(? AS lquery)))))"
,[vertex.materializedPath,vertex.materializedPath])
Where that first question mark should be escaped and the error being thrown is
org.postgresql.util.PSQLException: No value specified for parameter 4.
at org.postgresql.core.v3.SimpleParameterList.checkAllParametersSet(SimpleParameterList.java:246)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:272)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:430)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:356)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:168)
at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:116)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:70)
Found it myself with a little experimentation. It just takes a double-question mark. So,
"materialized_path ? (SELECT ARRAY(...
becomes
"materialized_path ?? (SELECT ARRAY(
I am calling a stored procedure from my Groovy code. The stored proc looks like this
SELECT * FROM blahblahblah
SELECT * FROM suchAndsuch
So basically, two SELECT statements and therefore two ResultSets.
sql.eachRow("dbo.testing 'param1'"){ rs ->
println rs
}
This works fine for a single ResultSet. How can I get the second one (or an arbitrary number of ResultSets for that matter).
You would need callWithAllRows() or its variant.
The return type of this method is List<List<GroovyRowResult>>.
Use this when calling a stored procedure that utilizes both output
parameters and returns multiple ResultSets.
This question is kind of old, but I will answer since I came across the same requirement recently and it maybe useful for future reference for me and others.
I'm working on a Spring application with SphinxSearch. When you run a query in sphinx, you get results, you need to run a second query to get the metadata for number of records etc...
// the query
String query = """
SELECT * FROM INDEX_NAME WHERE MATCH('SEARCHTERM')
LIMIT 0,25 OPTION MAX_MATCHES=25;
SHOW META LIKE 'total_found';
"""
// create an instance of our groovy sql (sphinx doesn't use a username or password, jdbc url is all we need)
// connection can be created from java, don't have to use groovy for it
Sql sql = Sql.newInstance('jdbc:mysql://127.0.0.1:9306/?characterEncoding=utf8&maxAllowedPacket=512000&allowMultiQueries=true','sphinx','sphinx123','com.mysql.jdbc.Driver')
// create a prepared statement so we can execute multiple resultsets
PreparedStatement ps = sql.getConnection().prepareStatement(query)
// execute the prepared statement
ps.execute()
// get the first result set and pass to GroovyResultSetExtension
GroovyResultSetExtension rs1 = new GroovyResultSetExtension(ps.getResultSet())
rs1.eachRow {
println it
}
// call getMoreResults on the prepared statement to activate the 2nd set of results
ps.getMoreResults()
// get the second result set and pass to GroovyResultSetExtension
GroovyResultSetExtension rs2 = new GroovyResultSetExtension(ps.getResultSet())
rs2.eachRow {
println it
}
Just some test code, this needs some improving on. You can loop the result sets and do whatever processing...
Comments should be self-explanatory, hope it helps others in the future!
I've a problem with escaping colons and dashes in SQL queries when I use dbFit with Fitnesse.
Such statement doesn't work:
!|Query|select to_char(my_birthday,'YYYY-MM-DD HH24:MI:SI') from family|
I need to replace colons and dashes with some other acceptable characters, ex.
!|Query|select to_char(my_birthday,'YYYY_MM_DD HH24_MI_SI') from family|
Do you know how to solve it properly without using the 2nd approach ?
Cheers,
foxrafi
I think this is what you need. From http://dbfit.github.io/dbfit/docs/reference.html
Avoiding parameter mapping
If you want to prevent DbFit from mapping parameters to bind variables
(eg to execute a stored procedure definition that contains the #
symbol in Sql Server), disable bind symbols option before running the
query.
|set option|bind symbols|false|
|execute| insert into users (name, username) values ('#hey','uuu')|
|query|select * from users| |name|username| |#hey|uuu|
Remember to re-enable the option after the query is executed. You can
use the same trick with the Execute command.
In addition to Mike's answer, you can also solve this by using bind parameters. This is useful if you have to use bind parameters at other places in the same query.
!|DatabaseEnvironment|ORACLE|
|Connect|${HOSTNAME}|${USERNAME}|${PASSWORD}|
!|Query|!-select current_timestamp ts from dual-!|
|ts?|
|>>my_birthday_ts|
|set parameter|my_birthday_ts| <<my_birthday_ts|
#Set bind parameter :MI to string ':MI'
|set parameter|MI|:MI|
# and do it in the same way with :SS.
|set parameter|SS|:SS|
!|Query|!-select to_char(:my_birthday_ts, 'YYYY-MM-DD HH24'|| :MI || :SS) bds from dual-!|
|bds?|
|>>birthday_string|
Note that you have to use !- -! around your query, otherwise FitNesse will expand the concatenation operator to table cells. The main drawback of this manner is that you cannot use ordinary FitNesse variables (${varname}) in the query.
!|DatabaseEnvironment|ORACLE|
|Connect|${HOSTNAME}|${USERNAME}|${PASSWORD}|
!|Query|!-select current_timestamp ts from dual-!|
|ts?|
|>>my_birthday_ts|
!|Query|!-select to_char(:my_birthday_ts, 'YYYY-MM-DD HH24:'||'MI:'||'SS) bds from dual-!|
|bds?|
|>>birthday_string|
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.