How to protect variable table names against SQL injection in PHP - php

I am building a web app which relies heavily on a database. Here is an example of the type of query I use a lot:
CREATE TABLE item$userenteredname$username
Basically each time a create a new item, there is a table which stores info for every time something is added. So I need the table names to remain the same.
I recently spent quite a while updating my code to use PDO. I understand how to prepare statements and bind values, but you can't do this with table names. Haven't been able to find a proper answer to my question, which to clarify is...
How can I sanitise user input against sql injection when I can't use prepare's or mysql_real_escape_string because the variable is in a table name?

My strategy for this use case would be to strip out non-alphanumeric characters.
$usereneteredname = preg_replace('/[^0-9a-zA-Z_]/', '', $usereneteredname);
$username = preg_replace('/[^0-9a-zA-Z_]/', '', $username);
// then "CREATE TABLE item$userenteredname$username"
This strategy is called whitelisting. That preg_replace call will replace anything that isn't 0-9a-zA-Z_.
Further considerations:
You may also wish to validate the string lengths after the output, and make sure you are operating on a string not an array.

Related

Is there a better way to combat SQL Injection?

I've watched Computerphile's video many times on this subject(for any of you who want, this is the link: https://www.youtube.com/watch?v=_jKylhJtPmI). He provides some really good advice on how to combat SQL Injection and make your app more effective. These are the key points from his video:
Don't use straight and unprotected SQL commands because this is the way hackers can perform a SQL Injection, stealing, modifying, or even deleting your data.
A good approach is to use the mysql_real_escape_string(String s) function. This basically places on the start of every dangerous character (/,", {, }, etc) a slash (/). So basically this makes the quote or slash inside the string useless.
The best thing to do is to use prepared statements. So, you basically say:
SELECT * FROM USERS WHERE username = ?
Later you go and replace the question mark with the string you want to input as the user name. This has the advantage of not confusing PHP or any other fault-tolerant language, and using this simple and (kind of, hacky) elegant solution to just say replace this with the string and tell the language that what is given is just a string and nothing more than that.
That is good and all, but this video is really outdated. It was made way back in 2013 and since then a lot of new technology has emerged. So, I tried to search the internet to find if there were any new approaches or if this is the one. But the problem was that either I couldn't find it or either I found something that was super confusing.
So, my question is: Is there a better and enhanced way to combat SQL Injection that has been introduced, or if prepared statements are still the norm and if they are vulnerable to any kind of attack?
Parameter binding is still the best solution in most examples of combining dynamic data with an SQL query.
You should understand why. It's NOT just doing a string substitution for you. You could do that yourself.
It works because it separates the dynamic value from the SQL-parsing step. The RDBMS parses the SQL syntax during prepare():
$stmt = $pdo->prepare("SELECT * FROM USERS WHERE username = ?");
After this point, the RDBMS knows that the ? must only be a single scalar value. Not anything else. Not a list of values, not a column name, not an expression, not a subquery, not a UNION to a second SELECT query, etc.
Then you send the value to be bound to that placeholder in the execute step.
$stmt->execute( [ "taraiordanov" ] );
The value is sent to the RDBMS server, and it takes its place in the query but only as a value and then the query can be executed.
This is allows you to execute the query multiple times with different values plugged in. Even though the SQL parser only needed to parse the query once. It remembers how to plug a new value into the original prepared SQL query, so you can execute() as many times as you want:
$stmt->execute( [ "hpotter" ] );
$stmt->execute( [ "hgranger" ] );
$stmt->execute( [ "rweasley" ] );
...
Are prepared statements the best? Yes, they are. It doesn't matter that the advice comes from 2013, it's still true. Actually, this feature about SQL dates back a lot further than that.
So are query parameters the foolproof way of defending against SQL injection? Yes they are, if you need to combine a variable as a value in SQL. That is, you intend for the parameter to substitute in your query where you would otherwise use a quoted string literal, a quoted date literal, or a numeric literal.
But there are other things you might need to do with queries too. Sometimes you need to build an SQL query piece by piece based on conditions in your application. Like what if you want to do a search for username but sometimes also add a term to your search for last_login date? A parameter can't add a whole new term to the search.
This isn't allowed:
$OTHER_TERMS = "and last_login > '2019-04-01'";
$stmt = $pdo->prepare("SELECT * FROM USERS WHERE username = ? ?");
$stmt->execute( [ "taraiordanov", $OTHER_TERMS ] ); // DOES NOT WORK
What if you want to allow the user to request sorting a result, and you want to let the user choose which column to sort by, and whether to sort ascending or descending?
$stmt = $pdo->prepare("SELECT * FROM USERS WHERE username = ? ORDER BY ? ?");
$stmt->execute( [ "taraiordanov", "last_login", "DESC" ] ); // DOES NOT WORK
In these cases, you must put the column names and syntax for query terms into your SQL string before prepare(). You just have to be extra careful not to let untrusted input contaminate the dynamic parts you put in the query. That is, make sure it's based on string values you have complete control over in your code, not anything from outside the app, like user input or a file or the result of calling an API.
Re comments:
The idea Martin is adding is sometimes called whitelisting. I'll write out Martin's example in a more readable manner:
switch ($_GET['order']) {
case "desc":
$sqlOrder = "DESC";
break;
default:
$sqlOrder = "ASC";
break;
}
I replaced Martin's case "asc" with default because then if the user input is anything else -- even something malicious -- the only thing that can happen is that any other input will default to SQL order ASC.
This means there are only two possible outcomes, ASC or DESC. Once your code has complete control over the possible values, and you know both values are safe, then you can interpolate the value into your SQL query.
In short: always keep in your mind an assumption that $_GET and $_POST may contain malicious content. It's easy for a client to put anything they want into the request. They are not limited by the values in your HTML form.
Write your code defensively with that assumption in mind.
Another tip: Many people think that client input in $_GET and $_POST are the only inputs you need to protect against. This is not true! Any source of input can contain problematic content. Reading a file and using that in your SQL query, or calling an API, for example.
Even data that has previously been inserted in your database safely can introduce SQL injection if you use it wrong.

Escape a pdo query, is that necessary?

My question of to day is. Do i need to escape PDO in my script?
$columns = implode(", ",$column);
$query = ''.$query.' '.$columns.' FROM '.$table.'';
$dbh_query = $dbh->prepare($query);
$dbh_query->execute();
$dbh_querys = $dbh_query->fetchAll();
return $dbh_querys;
The whole script can be found at.
https://github.com/joshuahiwat/crud/blob/master/control/query_connector.class.php
Can someone explain why do i need a escape at this time or why not.
I like to hear from you, thanks a lot!
The parts of your query that are dynamic are the table name and column names. You can't use bind functions for these parts of the query. Bind functions can be used only for the parts of the query that would otherwise be a simple value in an SQL query. Like a numeric constant, or a quoted string or quoted date literal.
To avoid SQL injection from dynamic table names or column names, you have the following choices:
Use values that are predefined in your class, or otherwise certain to be safe. Don't use external content from users or any other source.
Use escaping. Note that the function PDO::quote() doesn't do the kind of escaping you need for table names or column names.
Create a "allowlist" of known table names and the column names for the respective table, and compare the dynamic input to the allowlist. If it doesn't match the allowlist, raise an error.
First of all you need to understand that the word you are using - "escape" - is meaningless.
What you probably mean is "to make your query safe from SQL injection". But, unfortunately, there is no such magic "escaping" that will make some abstract query safe.
The traditional query building assumes that all the query parts beside data values are hard-coded, while data values are bound via placeholders, like this:
$query = 'SELECT col1, col2 FROM some_table WHERE id = ?';
$stmt = $dbh->prepare($query);
$stmt->execute([$id]);
$row = $stmt->fetch();
This kind of a query considered safe.
In your case of a dynamically constructed query, every part is potentially vulnerable.
And here it is very important to understand that a burden of sanitizing all the query parts is entirely on this function. You cannot dismiss the danger simply claiming that your data is coming from the trusted source. That's a slippery ground because people often have no idea whether their source is trusted or not.
So, if take your question as "Do I have to protect this code from SQL injection", than the answer is - YES, YOU HAVE.
In the meantime you are protecting only a small part of your query - the data values. So you still have to protect (this term is much better than "escape") all other parts.
On a side note, your code is connecting to database every time it runs a query, which is highly inefficient and makes it impossible to use some database features.

mysql sanitize row name

I'm currently writing a php framework with focus on security. I use a query builder to generate SQL-statements, so that it is not bound to MySQL. (Or SQL in general) I found certain posibilities that user could inject row names, so it has to escape them somehow. Because of how the query builder works, i sadly cannot use prepared statements. How can I fix this?
EDIT:
The system works for example like this: db::select()-from('Tablename')->that('rowname')->run(). And I'm afraid one user could do something like that($_GET['foo']) or something. I could live with that, but I thought there has to be a way to sanatize this
To escape backtick you have to double it. Here is a function from my class
private function escapeIdent($value)
{
if ($value)
{
return "`".str_replace("`","``",$value)."`";
} else {
$this->error("Empty value for identifier (?n) placeholder");
}
}
//example:
$db->query("UPDATE users SET ?u=?s", $_POST['field'], $_POST['value']);
So, it will create a syntactically correct identifier.
But it is always better to whitelist it, as there can be a field, though with correct name,to which a user have no access rights. (So, schema-based solution is still dangerous from this point of view. Imagine there is a role field with value admin for the query from my example)
I have 2 functions in my class for this purpose, both accepts an array of allowed values.
Because of how the query builder works, i sadly cannot use prepared statements. How can I fix this?
If you can't use query parameters, then change the query builder to apply escaping to its arguments before interpolating them into SQL expressions.
Lots of people correctly advocate for query parameters, but escaping is also safe IF you do it correctly and consistently.
Cf. mysqli::real_escape_string()
Re your comment, okay I see where you're going. I was confused because you said "row name" and that's not the correct terminology. You must mean column name.
Yes, you're right, there are no functions in any of the MySQL APIs to escape table or column identifiers correctly. The escaping functions are for string literals and date literals only.
The best way to protect SQL queries when untrusted input names a table or column is to use allowlisting. That is, test the argument against a list of known table names or column names, which you either code manually, or else discover it from DESCRIBE table.
See examples of allowlisting at my past answers:
escaping column name with PDO
PHP PDO + Prepare Statement
My presentation SQL Injection Myths and Fallacies
My book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming.

SQL injection with union and load_file()

My website has been attacked by SQL injection. Hacker used following in URL query string:
abc-buy.php?sid=144760&op=-3+union+all+select+1,2,3,4,5,6,7,load_file%28%22/etc/passwd%22%29
How can I avoid these kind of attacks?
Always validate untrusted input.
All input is untrusted.
How to validate the input depends on what the input is, but in this case, it's probably pretty obvious that -3+union+all+select+1,2,3,4,5,6,7,load_file%28%22/etc/passwd%22%29 is not valid input for op (whatever op is).
So in this case, it would probably be as simple as adding some code to check that the value for "op" matches one of the expected values.
if ( op != "or" and op != "and" and op != "monkeys" ) {
raise_exception("Invalid op specified! Go away you trickster!");
}
You should do this for every value you receive from users. Although it's trickier for free-form fields, like email addresses or comments, etc. But still, make sure they are valid data for the field they're matching--and escape any free-form fields before you insert them into the database. That can make the difference between:
INSERT INTO users (username,fullname) VALUES ("bob","Robert"); DROP TABLE users; SELECT 1 WHERE "x"="");
and:
INSERT INTO users(username,fullname) VALUES ("bob",Robert\"\)\; DROP TABLE users\; SELECT 1 WHERE \"x\"=\"");
The functional difference being that with the first (un-escaped) version, the DROP TABLE users; command executes, and with the second, you simply insert a new user with a really long, silly name of Robert"); DROP TABLE users; SELECT 1 WHERE "x"=".
Switch to PDO and use prepared statements with placeholders for everything.
As most of the answers says, you should escape everything you save into your database (field placeholders).
But I have recently discovered that you should escape all place holders in your query, because without it:
Placeholders for the "FROM clause" could allow hackers to access any table's data.
Placeholders for the "WHERE clause", could allow hackers to any row in the current table. That means a hacker could access your app as any user in your database when trying to log in.
use zend framework. that will by default prevent it
http://framework.zend.com/
or everything you put in the database you escape.
http://php.net/manual/en/function.mysql-real-escape-string.php

how can i insert the array of id into a single field of data base

hi how can i insert the array of service id into table into a single field like 2,3,4 in database i am confuse please help followingis my code...i am using this but it is inserted only single id..
$service=implode(",",$_POST['service']);
$sqlQuery="INSERT INTO ".DBPROMOTION." SET
promotion_service_id='".$service."',
promotion_user_id='".$user."',
promotion_discount='".$_POST['discount'].$_POST['type']."',
promotion_title='".$_POST['title']."',
promotion_start_date='".$startDate."',
promotion_end_date='".$endDate."',
promotion_code='".$_POST['code']."',
promotion_description='".$_POST['desc']."'"
You should use two database tables with relation one-to-many to link more than one element with table columnt. That would be better design.
You may insert all id's into db in form of string, but I don't think, that is what you aiming for.
Also, you should sanitize your input from $_POST before entering int to db, to prevent sql injection.
you database-model seem to be not normalized - correct that first before programming an application that will cause some realy big problems in the future.
also, you should never insert posted data directly to avoid sql-injection. use mysql_real_escape_string or, better, PDO for prepared statements.
to your problem directly: i think 'promotion_service_id' is an integer-field - '2,3,4' is a string, so (fortunately) you have no chance to insert it.

Categories