I am creating a random user ID, but I would like to check if the ID already has been used (very unlikely but the chances are there), but somehow this doesn't work. When I look in the database, there is no random character string in the account_id field. Do I call the functions in a wrong way?
function genRandomString() {
$length = 40;
$characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
for ($p = 0; $p < $length; $p++) {
$string .= $characters[mt_rand(0, strlen($characters))];
}
return $string;
}
function createID() {
$cl_id = 'h_u_'.genRandomString();
}
createID();
$sql_query="SELECT * FROM accounts WHERE account_id = :cl_id";
$statement = $conn->prepare($sql_query);
$statement->bindParam(':cl_id', $cl_id, PDO::PARAM_STR);
if ($statement->execute() && $row = $statement->fetch())
{
createID();
}
$conn->exec("INSERT INTO accounts SET
account_id='$cl_id' ,
name='$_POST[name]' ,
email='$_POST[email]' ");
$cl_id is a local variable in createID() function , you need to return your value to your global code ...
function createID() {
return $cl_id = 'h_u_'.genRandomString();
}
you need to check $id in the main code
$id = createID();
$sql_query="SELECT * FROM accounts WHERE account_id = '".$cl_id."'";
$statement = $conn->prepare($sql_query);
1 . You missed to return $c_id in createID(). Change it to:
function createID() {
return 'h_u_'.genRandomString();
}
$cl_id = createID();
2 . You could use good old uniqid() instead of your custom genRandomString().
This would lead to something simpler like:
function createID() {
return 'h_u_'.uniqid();
}
$cl_id = createID();
3 . You'll have to change the if in the database related code to a loop (have a look at my example below)
4 . Your insert query uses unverified $_POST vars. This is highly prone to SQL Injections. If your Database library supports server side prepared statements you should use them and you can feel secure because data is being kept separate from the query syntax. If you are using PHP with MySQL this is the case.
If you are not using server side prepared statements you should escape any $_POST data used in the query by using mysql_real_escape_string() or something like this. In the following example I'm assuming that you are using PHP with MySQL and thatswhy I use a prepared statement.
Taking all this in account may result in a finished script like this:
$sql_query="SELECT * FROM accounts WHERE account_id = :cl_id";
$statement = $conn->prepare($sql_query);
$maxtries = 3; // how many tries to generate a unique id?
for($i = 0; $i < $maxtries; $i++) {
$cl_id = uniqid(); // create a 'unique' id
$statement->bindParam(':cl_id', $cl_id, PDO::PARAM_STR);
if (!$statement->execute()) {
die('db error');
}
$row = $statement->fetch();
if($row) {
continue;
}
break;
}
// if a unique id couldn't get generated even
// after maxtries, then pigs can fly too :)
if($i === $maxtries) {
die('maximum number of tries reached. pigs can fly!');
}
// You should use a prepared statement for the insert to prevent from
// SQL injections as you pass $_POST vars to the query. You should further
// consider to validate email address and the name!
$name = $_POST['name'];
$email = $_POST['email'];
$insert_query = '
INSERT INTO accounts SET
account_id = :account_id,
name = :name,
email = :email';
$insert_statement = $conn->prepare($insert_query);
$insert_statement->bindParam(':account_id', $cl_id, PDO::PARAM_STR);
$insert_statement->bindParam(':name', $name, PDO::PARAM_STR);
$insert_statement->bindParam(':account_id', $email, PDO::PARAM_STR);
if (!$insert_statement->execute()) {
die('db error');
}
Related
I'm trying to create a unique key to save information in the database, but for some reason the $randStr variable has nothing at the end. After submitting, I get only a new id and email, nothing appears in the keystring. What's wrong?
Here's my sql table:
create table users (
id int(11) not null PRIMARY KEY AUTO_INCREMENT,
keystring varchar(110) not null,
email varchar(220) not null
);
php code:
<?php
include_once 'includes/dbh.php';
function checkKeys($conn, $randStr) {
$sqlcheck = "SELECT keystring FROM users";
$result = mysqli_query($conn, $sqlcheck);
while ($row = mysqli_fetch_assoc($result)) {
if ($row['keystring'] == $randStr) {
$keyExists = true;
break;
} else {
$keyExists = false;
}
}
return $keyExists;
}
function generateKey($conn) {
$keyLength = 8;
$str = "0123456789abcdefghijklmnopqrstuvwxyz";
$randStr = substr(str_shuffle($str), 0, $keyLength);
$checkKey = checkKeys($conn, $randStr);
while($checkKey == true) {
$randStr = substr(str_shuffle($str), 0, $keyLength);
$checkKey = checkKeys($conn, $randStr);
}
return $randStr;
}
$recipient = $_POST['emailFor'];
$sender = $_POST['senderEmail'];
$message = $_POST['message'];
$sql = "INSERT INTO users (keystring, email) VALUES('$randStr', '$sender');";
mysqli_query($conn, $sql);
You are not calling generateKey function. Call this function and store return value to $randStr before inserting in database.
Note: please see SQL injection prevention for same.
$randStr = generateKey($conn);
// Your insert statement
While the accepted answer is correct as far as it goes, there is much here that can be improved. Reading all the existing keys and checking them one by one is intensive, and will get slower as the number of keys increases. It also opens a window for a race condition, where two requests are competing fro resources.
Applying a unique index to the keystring column allows us to perform an atomic 'test and set' with MySQL checking the key and inserting it in one operation.
We can also switch to prepared queries to eliminate the risk of SQL injection.
So, apply a UNIQUE index to the keystring column. This modification to the table only needs to be done once.
ALTER TABLE `users` ADD UNIQUE INDEX `keystring_UNIQUE` (`keystring` ASC) VISIBLE;
Then the new code looks like this:
function generateKey() {
$keyLength = 8;
$str = "0123456789abcdefghijklmnopqrstuvwxyz";
$randStr = substr(str_shuffle($str), 0, $keyLength);
return $randStr;
}
$recipient = $_POST['emailFor'];
$sender = $_POST['senderEmail'];
$message = $_POST['message'];
// This will be required somewhere
$conn = mysqli_connect("xxxx", "xxxx", "xxxx", "xxxx");
// Prepare the query
$stmt = mysqli_prepare($conn, "INSERT INTO users (keystring, email) VALUES(?, ?);");
// If the key generated already exists the loop will try again.
// Limit the collisions before we give up
$collisionLimit = 10;
do {
$randstr = generateKey();
mysqli_stmt_bind_param($stmt, 'ss', $randstr, $sender);
$result = mysqli_stmt_execute($stmt);
// check the result. If it's TRUE all's well and we continue.
// If we have an error throw an Exc eption if it's not a duplicate key error
if ($result === false) {
if (mysqli_stmt_errno($stmt) != 1062) {
throw new Exception(mysqli_stmt_error($stmt));
}
// If we have a collision, check the limit and throw an Exception if necessary
if (--$collisionLimit) {
throw new Exception("Unable to insert key - too many collisions");
}
}
} while ($result===false);
I have a simple problem whitch I can't solve, because I am starting with OOP and in the same time with MySQLi.
I need these function universal for everything and I need SET statement dynamically changed.
This is my update function these not working
public function updateUser($user, $pass, $dbSet) {
if($this->getUser($user, $pass) != NULL) {
$sql = $this->connection->prepare("UPDATE users SET ? WHERE user = ?");
$sql->bind_param('ss', $dbSet, $user);
$sql->execute();
$sql->close();
return true;
} else {
return false;
}
}
Variable $dbSet contains different values. For example:
$dbSet = "last_activity = ".$last_activity;
Or complex
$dbSet = "name = ".$newName.", surname = ".$newSurname.", email = ".$newEmail;
But when I change it for one SET statement, it works...
...
$sql = $this->connection->prepare("UPDATE users SET last_activity = ? WHERE user = ?");
...
Ok, so I am having a lot of trouble with Prepared statements. I've done hours of research and still can't seem to fully understand everything...
I really feel like I need to understand Prepared statements because I was just about to release a few new free APIs on my website (which require API Key to execute API) but I recently realized how insecure everything is.... I can simply use SQL injection to bypass API Key check, e.g. 'OR'1'='1
Here is how I validate API Key:
$apikey = $_GET['key'];
$sql = "SELECT * FROM `table` WHERE `key` = '$apikey'";
$query = mysqli_query($con, $sql);
if($query)
{
$fetchrow = mysqli_fetch_row($query);
if(isset($fetchrow[0]))
{
echo "API Key is valid!";
}
else
{
echo "API KEY is invalid";
}
}
And like mentioned above this can easily be bypassed by executing my API like this
http://website.com/api.php?key='OR'1'='1
This really scared me at first, but then I did some research and learned a good way to prevent any form of SQL injection is to use prepared statement, so I did a lot of research and it just seems quite complicated to me :/
So I guess my question is, how can I take my above code, and make it function the same way using prepared statements?
Probably everything you need:
class Database {
private static $mysqli;
Connect to the DB:
public static function connect(){
if (isset(self::$mysqli)){
return self::$mysqli;
}
self::$mysqli = new mysqli("DB_HOST", "DB_USER", "DB_PASS", "DB_NAME");
if (mysqli_connect_errno()) {
/*Log error here, return 500 code (db connection error) or something... Details in $mysqli->error*/
}
self::$mysqli->query("SET NAMES utf8");
return self::$mysqli;
}
Execute statement and get results:
public static function execute($stmt){
$stmt->execute();
if ($mysqli->error) {
/*Log it or throw 500 code (sql error)*/
}
return self::getResults($stmt);
}
Bind results to the pure array:
private static function getResults($stmt){
$stmt->store_result();
$meta = $stmt->result_metadata();
if (is_object($meta)){
$variables = array();
$data = array();
while($field = $meta->fetch_field()) {
$variables[] = &$data[$field->name];
}
call_user_func_array(array($stmt, "bind_result"), $variables);
$i = 0;
while($stmt->fetch()) {
$array[$i] = array();
foreach($data as $k=>$v)
$array[$i][$k] = $v;
$i++;
}
$stmt->close();
return $array;
} else {
return $meta;
}
}
Class end :)
}
Example of usage:
public function getSomething($something, $somethingOther){
$mysqli = Database::connect();
$stmt = $mysqli->prepare("SELECT * FROM table WHERE something = ? AND somethingOther = ?");
$stmt->bind_param("si", $something, $somethingOther); // s means string, i means number
$resultsArray = Database::execute($stmt);
$someData = $resultsArray[0]["someColumn"];
}
Resolving your problem:
public function isKeyValid($key){
$mysqli = Database::connect();
$stmt = $mysqli->prepare("SELECT * FROM table WHERE key = ? LIMIT 1");
$stmt->bind_param("s", $key);
$results = Database::execute($stmt);
return count($results > 0);
}
PHP automatically closes DB connection so no worries about it.
$sql = "SELECT * FROM `table` WHERE `key` = ?";
if(stmt = $mysqli->prepare($sql)) {
$stmt->bind_param("i", $apikey);
$stmt->execute();
$stmt->bind_result($res);
$stmt->fetch();
$stmt->close();
}
See more - http://php.net/manual/en/mysqli.prepare.php
So I'm having an odd problem... We use parameterized queries to prevent SQL Injection in our code but I'm having trouble with some of that behavior and while I've found an ugly way around it, my work around kind of defeats the purpose of the parameterization.
Suppose I'm making this query:
$db = new csmysqli('database',$host,$user,$pass);
$value = x;
$stmt = "INSERT INTO table SET value='%s'";
$result = $db->prepare($stmt, $value);
echo $result;
Now here's the problem... if x is a string, or an int we get this for result:
INSERT INTO table SET value='123';
No problem... however, if x is null:
INSERT INTO table SET value='NULL'; <--- the single quotes there cause a problem.... Ok so I try this to get around it:
$value = "'x'"; // Notice the quotes now go around x
$stmt = "INSERT INTO table SET value=%s";
$result = $db->prepare($stmt, $value);
echo $result;
And we get this if x is an int or string:
INSERT INTO table SET value=\'x\';
And the null now works:
INSERT INTO table SET value=NULL;
So the question is:
How can I get both normal data and NULL data to correctly populate with parameterization ?
EDIT:
I should have mentioned I'm using a special mysqli_helper script:
class csmysqli extends mysqli
{
public function __construct($dbname = '', $host,$user,$pass)
{
parent::__construct($host, $user, $pass, $dbname);
}
public function query($query)
{
$numParams = func_num_args();
$params = func_get_args();
//merge in parameters only if needed
if ($numParams > 1) {
for ($i = 1; $i < $numParams; $i++) {
$params[$i] = parent::real_escape_string($params[$i]);
}
$query = call_user_func_array('sprintf', $params);
}
return parent::query($query, MYSQLI_STORE_RESULT);
}
public function prepare($query)
{
$numParams = func_num_args();
$params = func_get_args();
//merge in parameters only if needed
if ($numParams > 1) {
for ($i = 1; $i < $numParams; $i++) {
$params[$i] = parent::real_escape_string($params[$i]);
}
$query = call_user_func_array('sprintf', $params);
}
return $query;
}
}
With native mysqli parameterization there is no problem to get both normal data and NULL data to correctly populate.
With your home-brewed parameterization you have to check the parameter type and act accordingly.
I am using a function in php for all select queries so that i can dynamically retrieve data from my database ..... I just wanted to know that is my code secure and efficient or if their is a better way to do this, if so please point me to the right direction...thanks
class mysql {
private $conn;
function __construct(){
$this->conn= new mysqli(DB_SERVER, DB_USER, DB_PASSWORD, DB_NAME);
if( mysqli_connect_errno() )
{
trigger_error('Error connecting to host. '.$this->connections[$connection_id]->error, E_USER_ERROR);
}
}
function extracting_data($table, $fields,$condition,$order,$limit){
$query="SELECT ".$fields."
FROM ".$table."
WHERE id =".$this->sql_quote($condition)."
ORDER BY ".$order."
LIMIT ".$limit." ";
//echo $query;
if($stmt = $this->conn->prepare($query)) {
$stmt->execute();
$row = array_pad(array(), $stmt->field_count, '');
$params = array();
foreach($row as $k=>$v) {
$params[] = &$row[$k];
}
call_user_func_array(array($stmt,'bind_result'),$params);
$result = array();
while($stmt->fetch()) {
foreach ($row as $b=>$elem) {
$vals[$b]=$row[$b];
}
$result[]=$vals;
}
$stmt->close();
return $result;
}
}
function sql_quote( $value )
{
if( get_magic_quotes_gpc() )
{
$value = stripslashes( $value );
}
//check if this function exists
if( function_exists( "mysql_real_escape_string" ) )
{
$value = mysql_real_escape_string( $value );
}
//for PHP version < 4.3.0 use addslashes
else
{
$value = addslashes( $value );
}
return $value;
}
}
Now to call the function I am using ::>
$connection=New mysql();
$extract=$connection->extracting_data("tablename","id,name,points","$_GET['id']","date desc","0,10");
The function returns a multi-dimensional array in $result and stores it in $extract ,depending on the data I want to extract..
Any improvements or other suggestions would be appreciated ...
Instead of binding the results and having to do loads of looping, you could simply use mysqli::query() and mysqli_result::fetch_all().
if($stmt = $this->conn->query($query)) {
$result = $stmt->fetch_all(MYSQLI_ASSOC);
$stmt->close();
return $result;
}
You would be better off binding your input variables rather than building up an SQL string containing them, but that may not be feasible using your current approach.
Edit
Sorry, I was an idiot and didn't notice that fetch_all() is only in PHP >= 5.3. You can still do this though which is simpler:
if($stmt = $this->conn->query($query)) {
$result = array();
while ($row = $stmt->fetch_assoc()) {
$result[] = $row;
}
$stmt->close();
return $result;
}
You should watch where the parameters for your function come from. If they come from an unreliable source, then it's very insecure.
If someone passes something like 1 ; DROP TABLE tablename ; SELECT * FROM dual WHERE 1 in the $condition parameter, you'll get the Little Bobby Tables scenario.
Your query will look like the following:
SELECT id, name, points
FROM tablename
WHERE id
ORDER BY
DATE DESC
LIMIT 0, 10
The id here will be casted to BOOLEAN, and the query will select all ids except 0 and NULL.
Is it really what you want?
You probably want to change your $condition to 'id = $id' or something like that.
Do you really need this level of abstraction: generating queries from uknown tables with unknown fields but with predefined SELECT / FROM / ORDER BY / LIMIT stucture?