I'm currently working on a php/PostgreSQL/JQuery project, and I'm an absolute beginner in all of these domains. In Php, to connect to my database, best way I found was to include a php
script with user and password in all php scripts that need it, like this.
include('../scripts/pgconnect.php');
With pgconnect.php :
$conn_string = "dbname=mydb user=myuser password=mypass";
$db = pg_connect($conn_string);
if(!$db){
die("Connection à la base impossible -> " . pg_errormessage($db));}
I'm sure all of you security expert will laugh at this, so could you tell me what is the best way to keep user and pass out from prying eyes ?
I found on stack overflow an advice to use env variables, but I don't find it really secure.
PHP is a server-side language. That means that when the requested page is sent back to the client all PHP code is parsed and "removed". As long you're the only one being able to look into your files there is no fear.
Whether you store it in (env) variables or not won't make a single difference.
There's absolutely nothing wrong with this code ;)
Edit:
When you execute an SQL query however, you need to be careful. Often, you use user input (an URL or POST data) to set certain values in a query. For example:
$sql = 'SELECT * FROM `table` WHERE `id`=' . $_GET['id'];
The $_GET['id'] variable is set in the URL (index.php?id=4).
If they change the value 4 to a bit of SQL query, they can pretty much do everything with your database. This is called SQL injection. It's truly the biggest threat of web applications using a database.
There are multiple fixes.
Sanitize the input (make sure that the input doesn't contain SQL syntax)
Prepare statements
Now, I'm not familiar with PostgreSQL, but apparently the PHP module has the ability to send prepared statements. This allows you to send the query with the unknown values as question marks and send the values afterwards.
$sql = 'SELECT * FROM `table` WHERE `id`=?';
// send prepared statement
$value = $_GET['id'];
// send the value
This way the database can tell that the value is no query.
Like I said, I'm not familiar with PostgreSQL, but I'm sure there are some tutorials that will guide you all the way through!
Another edit:
Because I'm a nice guy, I found how to do it! You need to use the function pg_prepare() and pg_execute(). Like this:
// This is a name to allow the database to identify the prepared statement
$queryname = 'my_query';
// Setting up our query with "empty" values
$sql = "SELECT * FROM `table` WHERE `column`='$1' AND `column`='$2'";
// Setting our values to send afterwards
$values = array(
$_GET['first_value'], // The first value that will replace $1
$_GET['second_value'] // The second value that will replace $2
);
$result = pg_prepare($connection, $queryname, $sql); // Send the query to the database
$result = pg_execute($connection, $queryname, array($value)); // Send the values
Last edit (I swear):
If you decide to put your configuration variables in an extern file, lets say configuration.php, make sure that the file ends with the .php extension. If you use a different extension, people might be able to find it and see it in plain text. If the PHP extension is used, they won't be able to see anything because like I said, the PHP is parsed and removed.
I use environment variable to store db authentication information: that is, the host/user/password for the db are in SetEnv commands in the Apache configuration, which appear in $_SERVER.
Why do this?
Development and production environments need different values for these, so having the application read some sort of environment configuration is necessary.
The application code is in a source code repository, which is easily browseable... embedding authentication secrets into it would make those just as widely available.
Environment variables aren't the only solution: for example, including a file that sets up a configuration object of some sort will work too. But most people seem to evolve some system for having the configuration settings (and just the settings) in a single changeable point, separate from the code that uses those settings, which changes on a completely different schedule.
Related
I've been reading through different secure ways to connect to databases and wanted to run this idea past you before I tie myself in knots trying it with my actual databases if it has no security benefits or simplification of switching between databases.
I was thinking that it would ensure the db_* variables are killed off after the function is used and the connection has been made to the relevant database:
<?php
function dbconn($db_hostname='localhost',
$db_username='',
$db_password='',
$db_database='database1'
$db_object='connection') {
if($db_username=='' && $db_password=='') {
switch ($db_database) {
case 'database1':
$db_username='user1';
$db_password='pass1';
break;
case 'database2':
$db_username='user2';
$db_password='pass2';
break;
default:
echo "No database defined to connect to";
break;
}
}
else if($db_password=='') {
switch ($db_username) {
case 'root':
$db_password='rootpass';
break;
case 'user':
$db_password='userpass';
break;
default:
echo "No password known for this user";
break;
}
}
$db_object= new mysqli($db_hostname, $db_username, $db_password, $db_database);
if ($db_object->connect_error) die($db_object->connect_error);
}
?>
It's unclear to me what you're trying to do, from the code you've provided. However, I can answer your question: "What is a secure way to connect to a database?"
With PHP working as a CGI-like language, the primary concern with connecting securely, is that your configuration file could be exposed. There are two possible solutions to this:
1. A PHP file as your configuration
This is probably the most common approach, and especially used often in redistributable software. By making your "configuration file" a PHP file that simply sets variables and is included in other pages, the PHP interpreter will parse this file, rather than returning its contents. The location of the file doesn't really matter - as long as it's somewhere where PHP files can be executed. In a typical application, this is anywhere in the document root except for the uploads directory.
An example configuration file (db_config.php):
<?php
$db_host = "localhost";
$db_user = "username";
$db_pass = "password";
$db_database = "database_name";
You would simply require('db_config.php'); in your initialization/header/etc. code.
You should only use the configuration file for configuring the connection, not for making it. This way, it makes it easy to change your connection code later on, without having to modify your configuration file on every server that your application runs on.
2. A serialized configuration file
Alternatively, you could use something like JSON or YAML to create a configuration file. The main advantage is that you can use the same configuration file for any auxiliary scripts or applications that need to connect to the same database, but aren't written in PHP.
However, you should never place this file in your document root. As far as your webserver is concerned, a JSON or YAML file is a 'viewable' file, and it will happily show it to anybody who has the URL.
The correct way to use a JSON/YAML/etc. file as your configuration file, is to make sure that it's placed outside of the document root, so that the webserver cannot possibly serve it up to a user. Trying to give it an 'unguessable' name is not sufficient.
You could also use something like .htaccess, but this is not recommended as it will make changing to other webservers harder, and a webserver misconfiguration could expose your database connection details. Only use this as an absolute last resort.
Other considerations
There are three other main points that I want to address here.
Multiple database credentials
In your original code, it looks like you're trying to add a feature where you can select the database you want to select to. In reality, this is almost never what you want. Every server/installation should only have its own database credentials.
If you follow the suggestions I gave for storing your configuration data, then it will be very easy to have a separate configuration file on each system, without having to ever change it. If you're using Git or another version control system that lets you ignore files, you can safely (and should) make it ignore the configuration file. You'll simply have a different configuration file on each server.
Overall security
Of course, you should make sure that your overall security is in proper working order. If you have a LFI vulnerability or somebody can upload a shell to your server, then no amount of putting files outside a document root is going to protect your database credentials. OWASP is a decent resource on general (web) application security.
PDO or mysqli_?
Judging from your snippet of code, you're using mysqli_. While this can be a valid choice if you use parameterization / prepared queries correctly, I would not recommend using it. PDO is a database-independent SQL library that focuses more heavily on parameterized queries. It's included by default in more or less every recent PHP installation, and as a bonus it'll let you switch between different SQL servers.
Using parameterized ("prepared") statements is absolutely critical - it is the only reliable way to prevent SQL injections, which seems to be the most important issue you'll want to protect yourself against here. A goood introductory guide can be found here.
I'll reference parts of the question to simplify what I'm referencing, I'm a non-traditional PHP developer so ask before you edit because past edits have destroyed questions and wasted the time of people trying to answer. If I don't understand the why of what you're telling me I can't understand the code even if it works.
The First Half
Part 1.1: My main question is simply this: how do I make the variable assigned to mysqli_connect() non-global yet clean reliably accessible?
Part 1.2: Adding some complexity to the issue: I access the same file for both AJAX and non-AJAX requests and the "dirty" part is the constant need to check if a non-global variable isset().
My structure for traditional non-AJAX requests looks like the following...
Part 1.3: Currently I have mysqli_connect() assigned to a global variable which I read all the time is apparently pure evil. As far as I can tell the whole global versus (what is non-global, local?) bit is in basic terms the ability to expose only PART of your server code so others can use PHP with a custom API you've built (if not then try to explain it in a single sentence with WHY a human would do that). I have no plans to create any APIs in this fashion though still would like to better understand this topic within the limited scope as in only what applies directly to this question to serve as a basis for the future.
Here is a slimmed down version of my database connection file; I've added commentary to clarify what is going on. I'm not looking to make any super-wild changes though I am obviously looking to improve my approach...
<?php
//This file included by the main PHP file.
if ($_SERVER['HTTP_HOST']!='localhost' && substr($_SERVER['HTTP_HOST'],0,7)!='192.168')
{// LIVE domain
$p0 = explode('www.',$_SERVER['HTTP_HOST'],2);
$domain = $p0[1];
}
else
{// Local or network access
$p0 = explode('www.',$_SERVER['REQUEST_URI'],2);
$p1 = explode('/',$p0[1],2);
$domain = $p1[0];
}
if (!isset($_SESSION['user_status']) || $_SESSION['user_status']<8)
{//Common VERY limited SQL syntax access only.
$GLOBALS['connection'] = mysqli_connect($vars['db_host'],$vars['db_user'],$vars['db_pass'],$vars['db_db']);
}
else
{//Admin SQL syntax access
$GLOBALS['connection'] = mysqli_connect($vars['db_host'],$vars['db_user_admin'],$vars['db_pass_admin'],$vars['db_db']);
}
if ($GLOBALS['connection'])
{
if (!isset($_SESSION)) {include('sessions.php');}
$_SESSION['database'] = 1;
$dbs = true;//structure leftover from 'mysql' to 'mysqli' API switch.
if ($dbs)
{//Timezone, etc...
$query1 = "SET time_zone = '+00:00';";
$result1 = mysqli_query($GLOBALS['connection'],$query1);
if ($result1) {}
else {mysqli_error_report($query1,mysqli_error($GLOBALS['connection']),__FUNCTION__);}
}
}
?>
So currently queries tend to look like this...
$result1 = mysqli_query($GLOBALS['connection'],$query1);
The Second Half
Part 2.1 After a bit of reading it seems that the intended approach for handling mysqli_connect() is to treat the variable in a procedural fashion. I'd much rather assign it once and not have to reconnect to the database seven times if there are seven SQL queries for a single page request (I currently connect once and can see it clearly with MySQL query log enabled). I've often encountered issues where the connection already exists (the dirty aspect of this) which creates conflicts. Even when I utilize classes I have to manually pass them as parameters to functions that are apparently too "deep" via PHP includes to be simply seen globally. So I'm looking to make sure I can have a standard include() of some kind that works cleanly around the site regardless of whether it's an AJAX request or not.
Part 2.2 So in another fashion if we're supposed to use mysqli_connect() in a procedural fashion what keeps the non-global variable from forcing PHP to reconnect to the database for say, all seven MySQL queries for a single page request?
Part 2.3 Simply put what is the best thing to assign mysqli_connect() to (variable, class, object...and what is it's scope)?
Part 2.4 Simply put what is the best way to include the file where mysqli_connect() is located (for the entire site) for more miscellaneous situations such as AJAX requests?
I'll be happy to clarify/edit as needed.
how do I make the variable assigned to mysqli_connect() non-global yet clean reliably accessible?
this one is simple.
If your application architecture is using OOP all the way, then just don't bother - by the time you need this variable, it will be always ready already surely you'll need no raw mysqli object at all.
If you are writing in the old good plain procedural PHP - just make it global.
I access the same file for both AJAX and non-AJAX requests
this one just have no relation to db problem at all.
Learn to use templates (or better MVC separation) and you will find that you can use exactly the same code to serve any request.
the ability to expose only PART of your server code so others can use PHP
This one is wrong.
the reasons are different.
Simply put what is the best thing to assign mysqli_connect() to
In fact, you are concerning of the most trifle things. While paying no attention to real ones.
And your real problem is use of prepared statements. If you are planning to use mysqli_query with raw SQL queries exactly the same way you used to be with old mysql_query - then there is not a single reason to move on - just keep with old mysql_query. As simple as that.
I need to pass special symbols through the URL for my MySQL query. For example, I need to have a URL that is something like:
www.example.com/index.php?q=AND name LIKE '%hi%'
When I tried it at first, I got a 406 error. I looked it up and apparently I have to use urlencode() and urldecode(). I put those in, and the 406 error went away, but then I got a MySQL error:
mysql_fetch_array(): supplied argument is not a valid MySQL result resource
Usually when I get these it means the query isn't written properly. So I echoed MySQL query, and everything looked fine. I even removed the urldecode() and hard-coded into a variable what I wanted to be passed to the page, and the MySQL error went away. However, both queries from using urldecode() and not using that are EXACTLY the same, so I'm kind of confused.
I went onto the php.net documentation page for urldecode(), and there was a warning that said something like using _GET and urldecode() together can result in unexpected things, and that _GET already functions as a decoder (or at least that's how I interpreted the wording), so I removed urldecode() but still left in the _GET, and that resulted in the text not being decoded, so I guess I didn't interpret the documentation correctly.
Is urldecode() not compatible with MySQL queries? I'm fairly certain it's an issue with the encode/decode, since I already tested my code with hard-coded info that bypassed the encode/decode, and it worked fine. Maybe the urldecode() is somehow turning the characters into special ones that look the same but are internally different so MySQL can't read them?
Don't do this. It's wrong. Anyone can of course just end the query using a ; and start a new one, deleting everything or reading out users and passwords.
One easy and much better way to do this is to use www.example.com/index.php?q=hi as your URL, then on the server (let's assume PHP)
$queryString = mysql_real_escape_string($_GET['q']);
$query = "AND name LIKE '%$queryString%'";
// Then replace $query for whatever you were using $_GET['q'] for before.
// Feel free to rename my variables what you like!
This way, the user can't mess things up, and the URL looks cleaner. the mysql_real_escape_string function makes the string safe to use in the query by escaping things. Read http://php.net/manual/en/function.mysql-real-escape-string.php for more on that.
If you still aren't convinced this is useful, consider what happens if someone publishes a link that will drop your tables, and then Google crawls it once a week. Your data will be removed any time that Google happens to swing by.
Once you are happy with this technique, read up on mod_rewrite (if using Apache) to clean the URL up even more, mysqli and how it is an improved version of the mysql functions, and finally PHP Data Objects (PDO) which helps to clean the PHP up even more.
Rich has the right general idea, but even better than quoting is to use database parameters. It essentially places a "variable token" into your SQL query, and then passes the variable separately. The query ends up looking something like this:
SELECT ID, VALUE1, VALUE2
FROM MY_TABLE
WHERE VALUE3 = :param
And then in your code, you add a value to substitute in for :param, and then send that to the database alongside your SQL. (On some DB libraries, you'd use a ? instead of a :parametername.) It works better than quoting for three reasons:
First, you can keep the query string constant instead of having to rebuild it every time, which improves performance in your server.
Second, if you send the same constant query to the database multiple times with multiple different parameters, the database engine can cache the query plan, which improves performance on the DB.
Third, when you get used to writing your queries in parameterized style, it becomes natural, especially if you use a query function that takes a parameter list as an argument, so it's hard to get wrong. By contrast, it's easy to forget to quote something, and then you've accidentally opened a security hole in your program.
Exactly how parameterization works depends on the database and the DB access library you're using, but every SQL database supports them. You should look at how it's done for the system you're using. It really is the best way to work with SQL, especially when you have input coming from untrusted sources such as the Internet.
At some point after calling mysql_query() and before calling mysql_fetch_array() you should check that the query didn't return false, and print mysql_error() if it did. e.g.:
$result = mysql_query($query);
if (!$result) {
die(htmlenitites(mysql_error())." when running <pre>".htmlenitites($query)."</pre>");
}
But:
You shouldn't be using php's mysql functions, you should use PDO, and you should use prepared / parameterized queries.
You shuoldn't be letting SQL be passed from the client unless you really trust anything the client might type - e.g. this is for a back-end admin interface, and even then, it's probably bad practice unless you really need it (like you are writing phpMySQLAdmin)
I am pretty new at php, quick note this is an android application that i am calling this script, I am not having the users make up the script lol. There is a series of checkboxs and when they check off certain ones it appends a script to the string builder. i am trying to run a query based on the value of a variable that is being passed in. Usually i do something like this,
mssql_query("UPDATE Userdata SET BrowseScript = '".$_REQUEST['sqlscript']."'
WHERE Userdata.Username = '".$_REQUEST['Username']."'");
and where it says .$_REQUEST[''] I can grab values that i pass in.
But this time the .$_REQUEST[''] is the whole script, so i want something like this
mssql_query($_REQUEST['sqlscript']);
and thats it i want it to run the query thats in that value, The query is correct, but it just will not return any value, I think i may have some type of syntax error or something, like i said i am new to php. Thanks for any help. Also I am not posting the whole code because everything is running ok, i just cant get that query to run. so all i need assistance with is the mssql_query part thanks again.
First of all there is huge security flaw in what you are doing. You should always sanitalize and escape the variables you use in your queries for example using mysql_real_escape_string or prepared statements.
Since you are importing whole script for your query, it could be that quotes are not escaped. You need to put these functions like this before your variables:
mysql_real_escape_string($_REQUEST['your_var']);
Using $_REQUEST in itself instead of proper $_GET or $_POST is vulnerable.
Tell your script to output the contents of $_REQUEST['sqlscript']
echo $REQUEST['sqlscript'];
Check that the output is correct. You might well find that there are some parsing errors, or just that your script is incomplete. Check for quotes (") and make sure the query is valid.
Also, you should never run a script from a request. Imagine if someone browsed to your site and typed
youtsite.com?sqlscript='drop * from tables';
Your entire database would be wiped.
For all that's holy, do not do this!
You do not just use request parameters and put them in SQL queries. You at the very least use mysql_real_escape_string (or whatever the MSSQL equivalent is, never used it). And you most certainly don't accept whole queries in the request and execute them sight unseen.
You're opening huge gaping security holes. Especially if you're new, stop that now before it becomes a habit!
example.com/foo.php?sqlscript=DROP TABLE users
Minimum required lecture: http://www.php.net/manual/en/security.database.sql-injection.php
I have a simple feedback form PHP script that I would like to enhance by adding the $_SERVER[HTTP_USER_AGENT] data to the row in the database that I'm saving.
I keep getting parse errors when I try a simple insert, passing '$_SERVER[HTTP_USER_AGENT]' as a typical string. Should I bundle it in some way, so that the characters used in that Server variable are not triggering such errors?
(The INSERT query runs fine without that field, btw.)
Thanks.
My bet is that there is a ' in the user agent strings that are causing the parser error.
The User-Agent string returned to PHP is under control of the local browser, which means that you need to treat it no differently from regular user input. A malicious user or a user who has been infected by a virus/trojan/worm could change the user agent string to cause an SQL injection attack. At the very least, you need to escape it (with mysql_real_escape_string() for example. My bet is that once you do this, your parser errors should also go away. Better yet, try to move to using prepared statements if your system allows this.
Does
mysql_query("
INSERT INTO
db_table
VALUES (
...
'" . mysql_real_escape_string($_SERVER['HTTP_USER_AGENT']) . "'
...
)");
not work? Can you show us your whole query? What are the exact error-messages?
Without an actual error message it's hard to say what particular problem you encounter with.
But to solve all possible issues,
First of all, you must read an official manual page to make yourself understand PHP strings syntax: http://php.net/types.string
Then, you have to understand Mysql proper syntax. I've explained it already here
Finally, you have to put everything together
like this:
$agent = mysql_real_escape_string($_SERVER['HTTP_USER_AGENT']);
$sql = "INSERT INTO `table` set useragent = '$agent'";
$res = mysql_query($sql) or trigger_error(mysql_query.$sql);
Running your queries this way you'll never have any problem. Or comprehensive error message at least.