Is anything wrong with naming images uploaded by users (for example avatars) like that:
/user_id-username.jpg
Example:
/1-feont.jpg
All images has unique name (becouse user_id is primary key), but what about security? Has that any bad influence? If has, what conventions should I do instead?
Make sure that the username is appropriately sanitized before using it as part of the filename. User id should be system generated so that should not cause any problems.
The name that you give the images is purely conventional. I do not think it is a security issue for revealing the usernames of your users. (If it is, then you better check your CMS right away!) However, if your website is not completely secure a hacker can make use of SQL injection to access your user data.
But the idea is really far-fetched. You can go ahead with using usernames. :-)
IMO, just name the images as user-user_id.jpg (Here, "user" being a normal string followed by the integer - user_id)
Generally it is fine but some users may not like their name to be displayed and the user ID being a primary key could have vulnerability if your site isn't completely secure or if a PHP vulnerability is found in the future that is currently unknown.
For this sort of thing I tend to use the following code
$createName = date('YmdHis');
$fileType = '.jpg';
$imgName = $createName.$fileType;
This should give a string like 20110702155513.jpg - this is the full date and time the image was named and is unique.
If you really want to be safe then you can write a call back function that if there was a failure due to the file name not being unique (generally because there were 2 requests in the same second - unlikely but possible), then you can use a fall back adding the user ID in the middle of the string or use the fall back as your primary naming method, so for example
if($imgName == 'inuse'){
$createName1 = date('Ym');
$createName2 = date('dHis');
$fileType = '.jpg';
$imgName = $createName1.$userId.$createName2.$fileType;
}
This allows the user ID to be hidden but entirely unique.
*Edit - * another option is to use the existing format and create an MD5 hash, the code would be something like
$user_id = 'user_id';
$username = 'username';
$fileType = '.jpg';
$fileName = md5($user_id).'-'.md5($username).$fileType;
I hope this helps
Using the date as Ryan suggests fails when you have users that upload at the same time
Using the username fails when users may change their username. "Fail" is a bit hard here, but you have to move the files around which is something that's not needed when using another solution
Using the id solves all concurrency problems if the ID is an autogenerated auto-increment ID from your database. Opposed to Kerrek SB, I don't see a problem that this makes a connection between a unique identifier and an image on your file system. I mean it's unique and you can use it.
Using the ID also makes it easy for your users to find/link their image if you constantly use the ID publicly. If you on the other hand have only the user names in URLs - like /profile/$username, then I'd use the username in the image file name, too - to be consistent.
About security: If you sanitize the username, all is fine. Just make sure that it's unique, so people can't overwrite each other's names (which means that you need to use the same sanitation rules for usernames in database as you use for usernames in image file names).
Having the username as image name also makes it "easier" to find an image if you manually need to find one, but that probably happens not often enough to be of worth for you.
To summarize, I'd go with the ID.
there is no huge risk to naming files like the way you use in your first example but to make it more secure why don't you use something better like
function Naming($username,$imagename)
{
$uniq = time() ;
$name = $uniq.'-'.$username.'-'.$imagename ;
return $name ;
}
i think its better to avoid using users ID's
Unique user_id is enough, you don't need "username".
Problem isn't here, but in count of files (in one folder).
Split them by 1000 in folder (or by 100): take first, second and third symbols of ID string and put in separate dirs:
ID = 10524.jpg
filename = 1/0/5/10524.jpg
If it's hard to write algorithm, you can try this function:
function getStorePath($filename, $base_dir)
{
//file should have extension
$ext_pos = strrpos($filename, '.');
if ($ext_pos===false) return false;
//extension will be sanitized (filtered actually)
$ext = preg_replace("|\W+|", "", substr($filename, $ext_pos+1));
if (empty($ext)) return false;
if (in_array($ext, array('php', 'shtml', 'cgi', 'inc', 'module', 'sh', 'sql', 'class'))) return false;
//filename will be filtered
$filename = preg_replace("|\W+|", "", substr($filename, 0, $ext_pos));
if (empty($filename)) $filename = mt_rand(100000, 999999).round(microtime(true)*1000000);
//let's create path to the file.
//we will take first 3 symbols of filename as names of folders
$d = realpath($base_dir).'/';
//first symbol
$d .= $filename[0].'/';
if (!file_exists($d))
{
$md = mkdir($d, 0755);
if ($md===false && !file_exists($d)) return false;
}
//second symbol
if (isset($filename[1]))
{
$d .= $filename[1].'/';
if (!file_exists($d))
{
$md = mkdir($d, 0755);
if ($md===false && !file_exists($d)) return false;
}
}
//and third symbol
if (isset($filename[2]))
{
$d .= $filename[2].'/';
if (!file_exists($d))
{
$md = mkdir($d, 0755);
if ($md===false && !file_exists($d)) return false;
}
}
if (!file_exists($d.$filename.'.'.$ext)) return $d.$filename.'.'.$ext;
else return false;
}
I typically name my images a random string, then store it in the database attached to the uploaders id. You can also store other data about the image this way.
$filename = uniqid('', true) . '.jpg';
Image Table
id | user_id | filename | etc
Just my own two cents.
If you are gonna add more avatars for users to choose from, you're better off using a function like uniqid() instead of the user of. Else, you can do that all you like.
I use an MD5 hash of the userid and time() though. Just a matter of preference.
Related
I am using PHP header redirect to redirect users from account/?id=$id to user/$username
Successfully it takes for instance account/?id=1 to user/samuel as long as the user exists. Now, this page user/samuel is quite empty ( 404 ).
How do I make it return the user data in the user/samuel using something like isset($_GET[]) manual? in addition of course to adding MYSQL query to retrieve data for the user which has username extracted from the URL, and get their data from database table. and I will be placing all the code in user/index.php
As long as I could make account/?id=$id get the $id from URL ( parameter ) and do other db stuff I think it is also possible to get $username from the URL.. even though user/?username could do it but I don't want to include an ? in the URL..
Any thoughts?
This is a pretty broad topic, what you need to do is parse the url - IE break it into parts and then match the url to a set of actions. This is commonly known as routing.
A naive implementation would be a:
$parts = explode($_SERVER['REQUEST_URI'], '/');
if ( $parts[-2] === 'user' && $parts[-1] ) {
$stmt = $pdo->prepare("SELECT * FROM 'users' WHERE username = ? OR id = ?");
$result = $stmt->execute(array($parts[-1], array($parts[-1]));
// ... do something if the user is found or not.
}
But this would fail if the url contains query parameters (?foo=bar) or a hash (#foo).
Instead you can use parse_url to make sure you only use the path.
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$parts = null;
// Match the url with a regular expression.
preg_match(/^\/user\/(\w*)$/, $path, $parts);
if ( count($parts) == 2 ) {
$stmt = $pdo->prepare("SELECT * FROM 'users' WHERE username = ? OR id = ?");
$result = $stmt->execute(array($parts[-1], array($parts[-1]));
// ... do something if the user is found or not.
}
But in the end you might want consider using a micro framework such as Silex, Slim or using the Routing component from Symfony2 so that you can concentrate on building your application rather than reinventing the wheel.
It might be better if you use Url Rewriting (aka friendly urls)
You can see this link which answers this same question, although your case is a little bit different.
Apache friendly urls
Since you can't convert $id to $username (both are different values) I would recommend to change the link to 'user/ID' instead of 'user/USERNAME'.
I'm writing a component for Joomla 2.5 that generates a graph using the PHP GD Library depending on the users input.
What name should I give the image file to avoid conflict when multiple users are using the component.
In my opinion you have two way.
The first is save the image with prefix like this
scotttiger-img12321323.jpg
where scotttiger is the username and 12321323 is the user input.
Another way is to use different folder for every user
scotttiger/1231321.jpg
But if You don't have the user logged in and doesn't know the username, do a simple check with isfile (http://it1.php.net/is_file) function and set incremental name. eg:
$wrote = false;
$prog = 0;
$filename="img-{$user_input}-{$prog}.jpg";
while(isfile($filename)) {
$prog++;
$filename="img-{$user_input}-{$prog}.jpg";
}
write_file($filename);
So you have the first time img-input-1.jpg the second once img-input-2.jpg and so on.
With this solution You doesn't have duplicates or conflict.
You can improve performance with this solution:
$wrote = false;
$prog = md5(time());
$filename="img-{$user_input}-{$prog}.jpg";
while(isfile($filename)) {
$prog = md5(time());
$filename="img-{$user_input}-{$prog}.jpg";
}
write_file($filename);
you can generate random name with help of e.g. date and php hash function:
http://www.php.net/manual/en/function.hash.php
So the name will be unique.
Or, depends on used feature in your component, you can somehow add the user name to the image name. It depends of the function of the component and on my factors (if there will be only one image per user, or per some other feature, etc.)
Jan
I'm trying to figure out a folder structure for storing user content such as images, which will work for a massive amount of users. I was going to go with something like... 000/000/001 (user id 1) ....999 max subfolder per folder.
But I would like to hide the total number of users, easily seen by starting at 1. Should I start at some random number like 349203480? I'm baffled as to how to work out the folders based on ID this way. Is there some better/easier way?
Use GUID's:
http://php.net/manual/en/function.uniqid.php
As per the PHP manual:
<?php
/* A uniqid, like: 4b3403665fea6 */
printf("uniqid(): %s\r\n", uniqid());
/* We can also prefix the uniqid, this the same as
* doing:
*
* $uniqid = $prefix . uniqid();
* $uniqid = uniqid($prefix);
*/
printf("uniqid('php_'): %s\r\n", uniqid('php_'));
/* We can also activate the more_entropy parameter, which is
* required on some systems, like Cygwin. This makes uniqid()
* produce a value like: 4b340550242239.64159797
*/
printf("uniqid('', true): %s\r\n", uniqid('', true));
?>
Create unique folders, create unique sub-folders. No one, including yourself, will ever know how many you have ... unless you are doing a count of folders / sub-folders on the FileSystem or are maintaining a reference of actual users to GUID's
Well, the simplest and easiest approach would be what you suggested. Pre-start counting users with a random number.
Another way would be to append a random number to your user id.
$userid = get_user_id();
$foldername = strval($userid) . strval(rand(1, 999));
//$foldername = 11, 231, ...
But of course, the above method has the overhead of checking if the folder already exists.
A third way would be use a simple encryption (such as rot13) on the username/id and append folder count from there on.
$username = get_user_name(); //Use the user id if you wish.
$next_folder_count = $last_folder_count + 1;
$foldername = str_rot13($username) . strval($next_folder_count);
//$foldername = hxcvf1, hxcvf2, ...
Again, you'll have to store the count of folders, or obtain it on the fly from the directory. The advantage of the last method would we that just by knowing the foldername, you can find out which user it belongs to.
I plan to create visitor unique ID and named as log file, as existing now I use the IP visitor as log file name i.e. logs/127.0.0.1.php but I think this is not enough because some visitor using share an IP address for PC's.
The visitor log file itself as setting place of configuration of visitors itself, so I plan to add another unique ID to identify each different visitor so let's say the log file:
logs/127.0.0.0.1-t3451dq.php, -t3451dq as unique ID
so as long as visitor browsing on my website the unique log file as setting configuration for each user (because I use plain text)
Currently I use:
<?
$filename = "./logs/".$_SERVER['REMOTE_ADDR'].".php" ; //out put logs/127.0.0.1.php
$data stripcslashes($data);
// each Visitor configuration here...
// bla...bla...
/* Writing file configurations */
$buat = fopen($filename, "w+");
fwrite($buat, "$data");
fclose($buat);
?>
so I need $filename add the $unique ID as name of their log file. Any ideas how to do that?
Try uniqid.
You can store this unique ID in the users session or in a cookie.
Example (not tested)
session_start();
if(!isset($_SESSION['uniqueID']))
{
$_SESSION['uniqueID'] = uniqid();
}
$filename = "./logs/".$_SESSION['uniqueID'].$_SERVER['REMOTE_ADDR'].".php" ;
Using a session will mean that if the same user closes their browser (or the session expires) they will get a new ID, which may or may not be what you want.
If you want a more persistent tracker then you may be better using cookies, and store the ID in the cookie (create a new ID if no cookie exists).
if(!isset($_COOKIE['uniqueID']))
{
$expire=time()+60*60*24*30;//however long you want
setcookie('uniqueID', uniqid(), $expire);
}
$filename = "./logs/".$_COOKIE['uniqueID'].$_SERVER['REMOTE_ADDR'].".php" ;
If you cannot use cookies/session then you may need to pass around the ID in your URL query string e.g. mypage.php?id=35dfgdfg3434
Create something out of his IP and the first time he enters the page. That should be unique.
You have two simple options : uniqid or as you're creating a file tempnam
Tempnam example :
function log($string, $userIP = null, $filename = null){
// Check if filename exists
if(!file_exists(LOG_PATH.$filename)){
$filename = tempname(LOG_PATH, $userIP.' - ');
if(!$filename){
return false;
}
}
// write log into file
$file = file_put_contents($filename, $string);
if($file === false || $file != strlen($string)){
return false;
}
return $filename
}
using log files for this type of use is unnecessary, it's alot easier to just shunt this type of data to a database. If it's just temporary data then use Cookies and/or Sessions
Below is a page that handles a login script and I am wondering if I have put it any security holes. I have been reading articles on protecting from injections and others and wanted to make sure that my code is secure.
It is submitted via ajax and returns JSON based on the login being correct or not.
<?php
ob_start();
session_start();
include ("config.inc.php");
include ("jsonEncode.php");
// ausername and apassword sent from form
$ausername = '';
$apassword = '';
$ausername = mysql_real_escape_string(stripslashes($_GET['username']));
$apassword = mysql_real_escape_string(stripslashes($_GET['password']));
$sql = "SELECT * FROM admin WHERE ausername='$ausername' AND apassword='$apassword' LIMIT 1";
$result = mysql_query($sql) or die(mysql_error());
$data = mysql_fetch_array($result);
$count = mysql_num_rows($result);
if($count==1){
$_SESSION['ausername'] = $ausername;
$_SESSION['apassword'] = $apassword;
$_SESSION['admin_id'] = $data['a_id'];
$a_id = $data['a_id'];
$_SESSION['LastLogin'] = $data['last_login'];
$query = "UPDATE admin SET last_login = Now() WHERE `a_id`= $a_id";
mysql_query($query);
//echo $query;
$_SESSION['aloggedin'] = "1234";
// valid
$var = array('avalid' => 1, 'ausername' => $ausername, 'apassword' => $apassword);
print php_json_encode($var);
}else{
// invalid
$var = array('avalid' => 0, 'ausername' => $ausername, 'apassword' => $apassword);
print php_json_encode($var);
}
?>
You might want to use the POST method rather than GET with the login form, otherwise their password will appear in the URL and URLs aren't very secure (they might get bookmarked or sent to another server as a referral URL, for example).
You don't need to strip the slashes. Unless you are also stripping slashes when these columns are populated, you've actually introduced a security hole -- if for whatever reason you don't have a unique constraint on the username field, and/or you have slashes in the in the stored username or password fields, and their passwords differed only by a slash, you could get one user logged in as another.
You should be using bound parameters to put user data into your SQL, not string concatenation.
Also, you should probably be storing password hashes in your database - not the original plaintext passwords.
Finally, not a security issue, but setting $ausername and $apassword to '' immediately before giving them new values is entirely pointless.
Also, don't store the password in the session. Php session data is stored in the OS tmp/temp directory by default so the data could be viewed by others. Normally, I'll just keep the username in the session and query the database when needed. That avoids problems when a user's information is changed, but the session isn't updated.
(I'm an MSSQL bod, so don't know if any of these points are irrelevant to MySQL)
This isn't really to do with security, just general observations in case helpful:
Don't use SELECT * - list the columns you want back - looks like you only need a_id & last_login. You might add a Blob in that table with their photograph in the future, or personal notes etc. - it will kill performance in all the places where you did SELECT * in the past and didn't need the picture.
I wouldn't do LIMIT 1 - I'd quite like to know if there are DUPs at this point, and raise an error.
I would put the last_login column in another table linked 1:1 with your User / password table. Its a frequent-change item, and if you decide to introduce an Audit table on the user/Password table (i.e. store the old values whenever it changes) having a frequently changing "info" column mucks that up a bit.
Personally I would want to keep the column naming convention and the SESSION / variable one the same.
admin_id / a_id, LastLogin / last_login
Personally I wouldn't store password in the session unless you need it later on. I would store something to indicate the "permissions" the user has, and then use that to decide if they can view PageX or PageY etc.
All good answers above.
Only one thing I want to add that hasn't been mentioned... I tend to fetch the account password and do a PHP comparison rather than putting the password in the query and looking if the row exists.