Hello I need help finding a way to protect from sql injection on my current project, Im making bash tutorial site but ive run into a problem. I put most my content in database and depending on what link the user clicks it will pull different data onto the page.
This is how im doing it
apt-get <br>
And on bash_cmds.php
<?php
require_once("connections/connect.php");
$dbcon = new connection();
$bash = $_REQUEST['id'];
$query2 = "SELECT * FROM bash_cmds WHERE id = $bash ";
$results = $dbcon->dbconnect()->query($query2);
if($results){
while($row = $results->fetch(PDO::FETCH_ASSOC)){
$bash_cmd = $row['bash_command'];
$how = $row['how_to'];
}
} else { return false; }
?>
<?php echo $bash_cmd ?>
<br />
<table>
<tr><td><?php echo $how ?> </td></tr>
</table>
However this leaves me vulnerable to sql injection, I ran sqlmap and was able to pull all databases and tables. Can someone please help I would appreciate it a lot the infomation would be invaluable.
There are a couple of ways to do this. I believe the best way is to use some database abstraction layer (there's a good one built into PHP called PDO) and use its prepared statements API. You can read more about PDO here, and you can see the particular function which binds a value to a ? placeholder here.
Alternatively, you could use the mysqli_real_escape_string API function, which should escape any SQL inside your $bash variable.
Of course, in this particular case, simply ensuring the ID is an integer with (int) or intval() would be good enough, but the danger of using this approach in general is that it's easy to forget to do this one time, which is all it takes for your application to be vulnerable. If you use something like PDO, it's more "safe by default," one might say - it's more difficult to accidentally write vulnerable code.
You could bind the values to a prepared statement.
But for something simple as a numeric variable a cast to an integer would be good enough:
$bash = (int) $_REQUEST['id'];
Using this, only a number would get stored into $bash. Even if someone enters ?id=--%20DROP%20TABLE%20xy;, as this will get casted to 1;
I've found one of the easiest ways to protect against injection is to use prepared statements.
You can do this in PHP via PDO, as CmdrMoozy suggested.
Prepared statements are more secure because the placeholders ? can only represent values, and not variables (ie: will never be interpreted as a table name, server variable, column name, etc. It {currently} can't even represent a list of values). This immediately makes any modification to the logic of the query immutable, leaving only possible unwanted values as injection possibilities (looking for an id of 'notanid'), which in most cases isn't a concern (they'd just get a blank/wrong/error page, their fault for trying to hack your site).
Addendum:
These restrictions are what is in place when the prepared statements are done on the server. When prepared statements are simulated by a library instead of actually being server side the same may not be true, but often many of these are emulated.
Related
i have a list of linked thumbnails. Each thumbnail has a link with one variable.
<a href="index.php?id=1"><img src="thumb1.jpg">
<a href="index.php?id=2"><img src="thumb2.jpg">
etc...
now, i've updated site to use url rewriting. Idea is that i have links like this
<a href="gallery/?id=1"><img src="thumb1.jpg">
<a href="gallery/?id=2"><img src="thumb2.jpg">
or something simillar.
On the landing page, i use $id to execute MySQL query and show all pictures from gallery with that id.
$pictures = mysql_query("SELECT * FROM t_gallery where id=$id",$db);
Can it be done, and main thing, how can i prevent that passing id poses a security threat?
Cheers,
Aleks
The url-rewriting part does not itself really introduce any new security-threats, the issue is the usage of mysql_* functions (which are deprecated) and not escaping the $id request-variable.
If you are scared of SQL-Injections (as one should be), either escape the $id variable before using it in the query or rather use prepared statements (and then switch to either mysqli or PDO, which you should do in any case cause of mysql_* being deprecated!).
Always validate and escape anything that you are about to use in a database query.
Please be aware of SQL injection vulnerabilities and make sure you sanitize any user input or consider using prepared statements.
For a particular implementation I leave this encapsulated piece of code
function ControllerAction($id=null) {
// I like to formalize on entry; this is supposed to be an absolute integer and nothing else
$id = abs((int)$id);
$q = 'SELECT * FROM t_gallery where id = ?'; // placeholder
// use PDO, it's safe and very comfortable
$pdo = get_pdo_connection($whatever_you_need);
// prepare it because it has placeholders, and because there is external input comming in
$stmt = $pdo->prepare($q);
// execute it
$stmt->execute(array($id)); // read the reference documentation to understand this clearly
// now the stmt object holds the results
return $stmt->fetchObject();// whatever you like here, I like to make a DAO
}
In this example that simple incomming parameter has already been prepared for use but I still use prepared statements for consistency (my own code convention)
Alert: This uses some form of half-assed MVC/MVP/MVVM (I'm not sure anymore) type architecture
you could actually make your href URLs like this if you are using Url Rewriting to further implement your SEO links
protocol://host/controller/action/getParam1/getParam2
// usage
http://yourhost/browsethumbs/aNumber
I'm creating a basic blog and I'm using the following code.
It's collecting the id (always a number) from the url and before I use, I wondered if anyone could check the security of the code and let me know if its ok?
I really don't want any injections, etc, and I want to keep it as much secured as possible.
<?php
if(is_numeric($_GET['id']) && $_GET['id'] > 0){
include("connectionfile.php");
$ia = intval($_GET['id']);
$ib = mysql_real_escape_string($ia);
$ic = strip_tags($ib);
$qProfile = "SELECT * FROM #### WHERE id='$ic' ";
$rsProfile = mysql_query($qProfile);
$row = mysql_fetch_array($rsProfile);
extract($row);
$title = trim($title);
$post = trim($post);
$date = trim($date);
mysql_close();
}else{
echo 'hack error here';
}
?>
$ia = intval($_GET['id']);
$ib = mysql_real_escape_string($ia);
$ic = strip_tags($ib);
strip_tags is useless, because it is only relevant in an HTML context. Any one of the other two methods would be sufficient to prevent SQL injection. Generally, just use the appropriate escaping mechanism for the language you're dealing with. In this case you're dealing with SQL, so mysql_real_escape_string alone is fine. See The Great Escapism (Or: What You Need To Know To Work With Text Within Text) for a step-by-step approach to escaping.
Better yet, learn PDO with prepared statements instead of the deprecated mysql_ functions, which solves the issue of SQL injection much better.
Don't use mysql_ functions. They are deprecated. Use mysqli or
PDO.
Use parameterized queries
Don't use "extract" as it pollutes the local scope. There are rare cases where it's safe, usually internal to an ORM, where it's
within the object. This is dangerous otherwise as all forms of
nasty variable names could be introduced, especially with successful
SQL injection.
Do exception handling so that database errors do not break the page entirely, and in the case of a bad query somehow forced via SQL Injection, nothing is displayed to indicate that the query was broken.
Even after you do all the above, still make sure you use htmlentities() or otherwise validate the data is what you expect before you display.
This code is a mess ;-)
if statement can be simplified "if (($id = (int)$_GET['id']) > 0) {"
if you acknowledge my 1. point, then $ia, $ib and $ic can be deleted
don't trim() database data! data should be trimed before INSERT into database.
read what #FilmJ has answered you
Will mysql_real_rescape_string() be enough to protect me from hackers and SQL attacks? Asking because I heard that these don't help against all attack vectors? Looking for the advice of experts.
EDIT: Also, what about LIKE SQL attacks?
#Charles is extremely correct!
You put yourself at risk for multiple types of known SQL attacks, including, as you mentioned
SQL injection: Yes! Mysql_Escape_String probably STILL keeps you susceptible to SQL injections, depending on where you use PHP variables in your queries.
Consider this:
$sql = "SELECT number FROM PhoneNumbers " .
"WHERE " . mysql_real_escape_string($field) . " = " . mysql_real_escape_string($value);
Can that be securely and accurately escaped that way? NO! Why? because a hacker could very well still do this:
Repeat after me:
mysql_real_escape_string() is only meant to escape variable data, NOT table names, column names, and especially not LIMIT fields.
LIKE exploits: LIKE "$data%" where $data could be "%" which would return ALL records ... which can very well be a security exploit... just imagine a Lookup by last four digits of a credit card... OOPs! Now the hackers can potentially receive every credit card number in your system! (BTW: Storing full credit cards is hardly ever recommended!)
Charset Exploits: No matter what the haters say, Internet Explorer is still, in 2011, vulnerable to Character Set Exploits, and that's if you have designed your HTML page correctly, with the equivalent of <meta name="charset" value="UTF-8"/>! These attacks are VERY nasty as they give the hacker as much control as straight SQL injections: e.g. full.
Here's some example code to demonstrate all of this:
// Contains class DBConfig; database information.
require_once('../.dbcreds');
$dblink = mysql_connect(DBConfig::$host, DBConfig::$user, DBConfig::$pass);
mysql_select_db(DBConfig::$db);
//print_r($argv);
$sql = sprintf("SELECT url FROM GrabbedURLs WHERE %s LIKE '%s%%' LIMIT %s",
mysql_real_escape_string($argv[1]),
mysql_real_escape_string($argv[2]),
mysql_real_escape_string($argv[3]));
echo "SQL: $sql\n";
$qq = mysql_query($sql);
while (($data = mysql_fetch_array($qq)))
{
print_r($data);
}
Here's the results of this code when various inputs are passed:
$ php sql_exploits.php url http://www.reddit.com id
SQL generated: SELECT url FROM GrabbedURLs
WHERE url LIKE 'http://www.reddit.com%'
ORDER BY id;
Returns: Just URLs beginning w/ "http://www.reddit.com"
$ php sql_exploits.php url % id
SQL generated: SELECT url FROM GrabbedURLs
WHERE url LIKE '%%'
ORDER BY id;
Results: Returns every result Not what you programmed, ergo an exploit --
$ php sql_exploits.php 1=1
'http://www.reddit.com' id Results:
Returns every column and every result.
Then there are the REALLLY nasty LIMIT exploits:
$ php sql_exploits.php url
> 'http://www.reddit.com'
> "UNION SELECT name FROM CachedDomains"
Generated SQL: SELECT url FROM GrabbedURLs
WHERE url LIKE 'http://reddit.com%'
LIMIT 1
UNION
SELECT name FROM CachedDomains;
Returns: An entirely unexpected, potentially (probably) unauthorized query
from another, completely different table.
Whether you understand the SQL in the attacks or not is irrevelant. What this has demonstrated is that mysql_real_escape_string() is easily circumvented by even the most immature of hackers. That is because it is a REACTIVE defense mechism. It only fixes very limited and KNOWN exploits in the Database.
All escaping will NEVER be sufficient to secure databases. In fact, you can explicitly REACT to every KNOWN exploit and in the future, your code will most likely become vulnerable to attacks discovered in the future.
The proper, and only (really) , defense is a PROACTIVE one: Use Prepared Statements. Prepared statements are designed with special care so that ONLY valid and PROGRAMMED SQL is executed. This means that, when done correctly, the odds of unexpected SQL being able to be executed are drammatically reduced.
Theoretically, prepared statements that are implemented perfectly would be impervious to ALL attacks, known and unknown, as they are a SERVER SIDE technique, handled by the DATABASE SERVERS THEMSELVES and the libraries that interface with the programming language. Therefore, you're ALWAYS guaranteed to be protected against EVERY KNOWN HACK, at the bare minimum.
And it's less code:
$pdo = new PDO($dsn);
$column = 'url';
$value = 'http://www.stackoverflow.com/';
$limit = 1;
$validColumns = array('url', 'last_fetched');
// Make sure to validate whether $column is a valid search parameter.
// Default to 'id' if it's an invalid column.
if (!in_array($column, $validColumns) { $column = 'id'; }
$statement = $pdo->prepare('SELECT url FROM GrabbedURLs ' .
'WHERE ' . $column . '=? ' .
'LIMIT ' . intval($limit));
$statement->execute(array($value));
while (($data = $statement->fetch())) { }
Now that wasn't so hard was it? And it's forty-seven percent less code (195 chars (PDO) vs 375 chars (mysql_). That's what I call, "full of win".
EDIT: To address all the controversy this answer stirred up, allow me to reiterate what I have already said:
Using prepared statements allows one to harness the protective measures of
the SQL server itself, and therefore
you are protected from things that the
SQL server people know about. Because
of this extra level of protection, you
are far safer than by just using
escaping, no matter how thorough.
No!
Important update: After testing possible exploit code provided by Col. Shrapnel and reviewing MySQL versions 5.0.22, 5.0.45, 5.0.77, and 5.1.48, it seems that the GBK character set and possibly others combined with a MySQL version lower than 5.0.77 may leave your code vulnerable if you only use SET NAMES instead of using the specific mysql_set_charset/mysqli_set_charset functions. Because those were only added in PHP 5.2.x, the combination of old PHP and old MySQL can yield a potential SQL injection vulnerability, even if you thought you were safe and did everything correctly, by-the-book.
Without setting the character set in combination with mysql_real_escape_string, you may find yourself vulnerable to a specific character set exploit possible with older MySQL versions. More info on previous research.
If possible, use mysql_set_charset. SET NAMES ... is not enough to protect against this specific exploit if you are using an effected version of MySQL (prior to 5.0.22 5.0.77).
Yes. If you will not forget to:
Escape string data with mysql_real_rescape_string()
Cast numbers to numbers explicitly (ie: $id = (int)$_GET['id'];)
then you're protected.
I personally prefer prepared statements:
<?php
$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?");
if ($stmt->execute(array($_GET['name']))) {
while ($row = $stmt->fetch()) {
print_r($row);
}
}
?>
It would be pretty easy to overlook one or another specific variable that has been missed when using one of the *escape_string() functions, but if all your queries are prepared statements, then they are all fine, and use of interpolated variables will stand out like a sore thumb.
But this is far from sufficient to ensure you're not vulnerable to remote exploits: if you're passing around an &admin=1 with GET or POST requests to signify that someone is an admin, every one of your users could easily upgrade their privileges with two or three seconds of effort. Note that this problem isn't always this obvious :) but this is an easy way to explain the consequences of trusting user-supplied input too much.
You should look into using prepared statements/parameterized queries instead. The idea is that you give the database a query with placeholders. You then give the database your data, and tell it which placeholder to replace with said data, and the database makes sure that it's valid and doesn't allow it to overrun the placeholder (i.e. it can't end a current query and then add its own - a common attack).
<?php
$id = intval($_GET['id']);
$sql = mysql_query("SELECT username FROM users WHERE id = $id");
$row = mysql_fetch_assoc($sql);
$user = htmlspecialchars($row['username']);
?>
<h1>User:<?php echo $user ?></h1>
Can you see any threats in the above code? Do I have to use htmlspecialchars on everything I output? And should i use is_numeric or intval to check so that the get is numeric?
I'm just building a minimal site. I'm just wondering if the above code is vulnerable to sql injection, xss?
Generally speaking mysql_real_escape_string() is preferred but since it's a number, intval() is OK. So yes, it looks OK from a security perspective.
One thing though, on many platforms, ints are limited to 32 bits so if you want to deal in numbers larger than ~2.1 billion then it won't work. Well, it won't work how you expect anyway.
These sorts of security precautions apply to any form of user input including cookies (something many people forget).
I would strongly recommend using PDO and prepared statements. While your statement above looks safe, you're going to have problems as soon as you do more complex queries.
Instead of puzzling over whether a particular query is safe, learn about prepared statements and you won't have to worry. Here is your example, re-written with PDO:
# Make a database connection
$db = new PDO('mysql:dbname=your_db;host=your_db_server', 'username',
'password');
# The placeholder (:id) will be replaced with the actual value
$sql = 'SELECT username FROM users WHERE id=:id';
# Prepare the statement
$stmt = $db->prepare($sql);
# Now replace the placeholder (:id) with the actual value. This
# is called "binding" the value. Note that you don't have to
# convert it or escape it when you do it this way.
$stmt->bindValue(':id', $id);
# Run the query
$stmt->execute();
# Get the results
$row = $stmt->fetch();
# Clean up
$stmt->closeCursor();
# Do your stuff
$user = htmlspecialchars($row['username']);
I've added a lot of comments; it's not as much code as it looks like. When you use bindValue, you never have to worry about SQL injection.
Well,
You are casting the received id to an int ; so no possible SQL injection here.
And the rest of the DB query is "hard-coded", so no problem there either.
If id was a string in DB, you'd have to use mysql_real_escape_string, but for an integer, intval is the right tool :-)
About the output, you are escaping data too (and, as you are outputting HTML, htmlspecialchars is OK) ; so no HTML/JS injection.
So, this short portion of code looks OK to me :-)
As a sidenote, if you are starting developping a new website, it is the moment or never to take a look at either mysqli (instead of mysql), and/or PDO ;-)
It would allow you to use functionnalities provided by recent versions of MySQL, like prepared statements, for instance -- which are a good way to protect yourself from SQL injection !
So I'm a slightly seasoned php developer and have been 'doin the damn thing' since 2007; however, I am still relatively n00bish when it comes to securing my applications. In the way that I don't really know everything I know I could and should.
I have picked up Securing PHP Web Applications and am reading my way through it testing things out along the way. I have some questions for the general SO group that relate to database querying (mainly under mysql):
When creating apps that put data to a database is mysql_real_escape_string and general checking (is_numeric etc) on input data enough? What about other types of attacks different from sql injection.
Could someone explain stored procedures and prepared statements with a bit more info than - you make them and make calls to them. I would like to know how they work, what validation goes on behind the scenes.
I work in a php4 bound environment and php5 is not an option for the time being. Has anyone else been in this position before, what did you do to secure your applications while all the cool kids are using that sweet new mysqli interface?
What are some general good practices people have found to be advantageous, emphasis on creating an infrastructure capable of withstanding upgrades and possible migrations (like moving php4 to php5).
Note: have had a search around couldn't find anything similar to this that hit the php-mysql security.
Javier's answer which has the owasp link is a good start.
There are a few more things you can do more:
Regarding SQL injection attacks, you can write a function that will remove common SQL statements from the input like " DROP " or "DELETE * WHERE", like this:
*$sqlarray = array( " DROP ","or 1=1","union select","SELECT * FROM","select host","create table","FROM users","users WHERE");*
Then write the function that will check your input against this array. Make sure any of the stuff inside the $sqlarray won't be common input from your users. (Don't forget to use strtolower on this, thanks lou).
I'm not sure if memcache works with PHP 4 but you can put in place some spam protection with memcache by only allowing a certain remote IP access to the process.php page X amount of times in Y time period.
Privileges is important. If you only need insert privileges (say, order processing), then you should log into the database on the order process page with a user that only has insert and maybe select privileges. This means that even if a SQL injection got through, they could only perform INSERT / SELECT queries and not delete or restructuring.
Put important php processing files in a directory such as /include. Then disallow all IPs access to that /include directory.
Put a salted MD5 with the user's agent + remoteip + your salt in the user's session, and make it verify on every page load that the correct MD5 is in their cookie.
Disallow certain headers (http://www.owasp.org/index.php/Testing_for_HTTP_Methods_and_XST) . Disallow PUT(If you dont need file uploads)/TRACE/CONNECT/DELETE headers.
My recommendations:
ditch mysqli in favor of PDO (with mysql driver)
use PDO paremeterized prepared statements
You can then do something like:
$pdo_obj = new PDO( 'mysql:server=localhost; dbname=mydatabase',
$dbusername, $dbpassword );
$sql = 'SELECT column FROM table WHERE condition=:condition';
$params = array( ':condition' => 1 );
$statement = $pdo_obj->prepare( $sql,
array( PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY ) );
$statement->execute( $params );
$result = $statement->fetchAll( PDO::FETCH_ASSOC );
PROs:
No more manual escaping since PDO does it all for you!
It's relatively easy to switch database backends all of a sudden.
CONs:
i cannot think of any.
I don't usually work with PHP so I can't provide advice specifically targeted to your requirements, but I suggest that you take a look at the OWASP page, particularly the top 10 vulnerabilities report: http://www.owasp.org/index.php/Top_10_2007
In that page, for each vulnerability you get a list of the things you can do to avoid the problem in different platforms (.Net, Java, PHP, etc.)
Regarding the prepared statements, they work by letting the database engine know how many parameters and of what types to expect during a particular query, using this information the engine can understand what characters are part of the actual parameter and not something that should be parsed as SQL like an ' (apostrophe) as part of the data instead of a ' as a string delimiter. Sorry I can not provide more info targeted at PHP, but hope this helps.
AFAIK, PHP/mySQL doesn't usually have parameterized queries.
Using sprintf() with mysql_real_escape_string() should work pretty well. If you use appropriate format strings for sprintf() (e.g. "%d" for integers) you should be pretty safe.
I may be wrong, but shouldn't it be enough to use mysql_real_escape_string on user provided data?
unless when they are numbers, in which case you should make sure they are in fact numbers instead by using for example ctype_digit or is_numeric or sprintf (using %d or %u to force input into a number).
Also, having a serarate mysql user for your php scripts that can only SELECT, INSERT, UPDATE and DELETE is probably a good idea...
Example from php.net
Example #3 A "Best Practice" query
Using mysql_real_escape_string() around each variable prevents SQL Injection. This example demonstrates the "best practice" method for querying a database, independent of the Magic Quotes setting.
The query will now execute correctly, and SQL Injection attacks will not work.
<?php
if (isset($_POST['product_name']) && isset($_POST['product_description']) && isset($_POST['user_id'])) {
// Connect
$link = mysql_connect('mysql_host', 'mysql_user', 'mysql_password');
if(!is_resource($link)) {
echo "Failed to connect to the server\n";
// ... log the error properly
} else {
// Reverse magic_quotes_gpc/magic_quotes_sybase effects on those vars if ON.
if(get_magic_quotes_gpc()) {
$product_name = stripslashes($_POST['product_name']);
$product_description = stripslashes($_POST['product_description']);
} else {
$product_name = $_POST['product_name'];
$product_description = $_POST['product_description'];
}
// Make a safe query
$query = sprintf("INSERT INTO products (`name`, `description`, `user_id`) VALUES ('%s', '%s', %d)",
mysql_real_escape_string($product_name, $link),
mysql_real_escape_string($product_description, $link),
$_POST['user_id']);
mysql_query($query, $link);
if (mysql_affected_rows($link) > 0) {
echo "Product inserted\n";
}
}
} else {
echo "Fill the form properly\n";
}
Use stored procedures for any activity that involves wrinting to the DB, and use bind parameters for all selects.