I'm toying with the idea of creating automatic electronic certificates. It's pretty easy to create custom certificates using the fpdf PHP class. The way I have it set up is that given a URL
http://www.example.com/makepdf.php?name=myname&class=classname
you get a PDF certificate with the student name and the class they took taken from the $_GET variable. Of course, this means that anyone could manipulate the URL to very easily create a custom certificate. (They could do this in Photoshop anyway, but the idea is to make manipulating the certificate not totally trivial.) Once a class is over, I want to send a mail merge to everyone with a unique URL for their certificate.
How would you approach this problem? Should I just create a set of random numbers and associate these with the student/workshop pairs in a database? Are there standard ways of approaching this problem?
Couple solutions stand out:
Store the names & classes in a database, and reference them with a numeric ID instead of passing the data in the request
Keep the information in the request, but add a secure hash that will prevent tampering with the data
The hash mechanism would be something like this:
When generating the link for the certificate, you have $name and $class. You'll create a third GET variable that is a hash of $name, $class, and a secret string that only your program knows. Something like this:
$salt = "this is my secret";
$hash = md5($name . $class . $salt);
$url = "http://www.mysite.com/certificate.php?name=" . urlencode($name) . "&class=" . urlencode($class) . "&hash=" . $hash;
Now when a user hits your certificate generation page, you must verify the hash:
$salt = "this is my secret";
$expected = md5($_GET['name'] . $_GET['class'] . $salt);
if ($expected != $_GET['hash']) {
die("You are not authorized");
} else {
// User is OK; generate the certificate
}
Yes, if you want to limit your inputs to a fixed pool, then creating a database full of random keys is the way I would go.
If you want a quicker and dirtier way to do it, just generate the keys into a text file, use a script to pull the file apart to send them to the recipients, and have your PHP certificate generator read from a copy of the file on the server.
Assuming you are generating these URLs yourself on the server, you could join all your parameter values together into a string:
hash_string = "myname:classname";
Then append a final parameter that's a hash of that string along with some secret seed:
query_string .= "&h=" . md5("my_secret_key:" . hash_string)
Then, when you get the query back, just check to make sure that the hash matches:
hash_string = params['name'] . ':' . params['class'];
if (params['h'] == md5("my_secret_key:" . hash_string)) ...
I don't really know PHP syntax, but you get the idea.
Your best bet would be to have a list of students/classes (some kind of database) and only allow generation of allowed certificates. That way you don't need to obfuscate the name of the student or class, because only valid certificates can be generated.
If that's too much to ask - you could generate a MD5 hash based on the combination and some salt, then add that hash to the URL. That way the salt would need to be know to forge a URL.
http://www.example.com/makepdf.php?name=Tim&class=PHP&hash=c2c455ce438112b44499561131321126
Then the generation script just does this:
$hash = md5($_GET['name'] . $_GET['class'] . $salt);
if($hash != $_GET['hash']){
//invalid request
}
Of course you'll need to generate the URL's with the same salt.
Should I just create a set of random numbers and associate these with the student/workshop pairs in a database?
Yes.
Related
My old PHP app has a default admin user and md5 encrypted password created by the SQL that creates the database: insert into users values ( 1, 'admin', MD5('changeMe'), 2 );
Is there a simple way to include a default user and encrypted password using PHP's passowrd_hash function on creating the tables? I ask because I understand that password_hash is a native PHP function and I assume it won't be understood in SQL.
The solution to my problem came in three parts. My OP sought a simple way to create a hashed password for the admin user for insertion in the MySQL database on the installation of the application, using the native PHP password_hash() function.
(1) Based on a suggestion by #Nick and #Tadman, I decided to incorporate setting the hash in an installer script that would set not only the hash but other defined site/application variables.
Rather than inserting user values when the database table is created, it was deferred until immediately after, with the admin user entering their credentials in the form that inserts the hash and writes other definitions to a file:
$userpass = $_POST['userpass'];
echo password_hash($userpass, PASSWORD_BCRYPT);
(2) The second part of my problem was replacing all instances of md5()`` withpassword_hash()` and I achieved that by using a neat PHP script I found online to recursively search and replace the occurrences on the server.
Having replaced the md5() occurrences, I needed to change the hash comparison method and again by searching the relevant files I was able to replace instances of:
if ($p != $theUser->pwd ) {
return( false ); }
with:
if(password_verify($p, $theUser->pwd)) {
// Success!
}
else {
// Invalid credentials
echo "Uh oh!";
}
(3) The third step in resolving the problem was discovering that adding $1$ to the opening of the md5 hash could make it readable by password_hash(); so I just needed to make a couple of adjustments in the installed database to the admin user's old password.
Thanks to those who helped shine the light so I could find my way. I'm off now to invent the wheel and sliced bread.
you can do something like this in php:
$hash = password_hash('changeMe');
//echo $hash;
then use this hash in the Database.
I need to append an user id to a public url, I'm not a security expert and I need to know the best way to secure it.
I thougt that cypher the id and the append it to the url, and decypher it on the function called by the url, but I need to be the only one thant can decypher it.
Any suggestions?
If you want a one-time-only or limited-time-only unique ID to give to the user (I'm guessing this is maybe in some kind of email link or something?) then you could potentially create a GUID or UUID and associate that with the user (via a database table perhaps), and have a field to mark when it's been used or expired.
A GUID/UUID is near-enough guaranteed to be unique and isn't easy to randomly guess.
The best way to do that is by implementing a SSO mecanism (Single Sign On).
The user will enter his password on the first app and use a secure link to access the second app.
The first app generates a public key encrypted with a private key known by the two apps.
The second app generates the public key to and if it matches the key appended to the URL then its safe to log the user.
Here an example :
The first app will create a public key :
$privateKey = `123azerty456`;
$publicKey = hash('sha512', 'user_id' . $privateKey);
$url = 'second_app.com/example.php?user_id=foo&key='.$publicKey;
The script on the second app will compute the key on its own based on the user_id passed as an argument and using the same private key :
$privateKey = `123azerty456`;
$checkPublicKey = hash('sha512', $_GET['user_id'] . $privateKey);
if($checkPublicKey == $_GET['key']) {
echo 'OK';
} else {
echo 'UNAUTHORIZED';
}
Suppose a website xyz.com is showing ads from my ad network example.com using JavaScript code:
<script type='text/javascript' src='http://example.com/click.php?id=12345678'></script>
Which shows the ad as:
click.php
<a href="http://example.com/process.php?var1=var1&var2=var2">
<img src="http://example.com/ads/banner.png"/></a>
When the link is clicked it is taken to process.php where I add and subtract balance using some MySQL queries and then redirect to ad's URL.
process.php
$ua = $_SERVER['HTTP_USER_AGENT'];
$ip = $_SERVER['REMOTE_ADDR'];
//invalid click
if($_SERVER['HTTP_REFERER']==null || $_SERVER['HTTP_USER_AGENT']==null) {
header("location: http://example.com");
exit;
}
I want to add to an Unique Session at click.php and retrieve it at process.php to prevent invalid clicks. How do I do that?
Update:
The answer below solves half of the issue but the users are still able to send fake clicks using iframe and img tag as below:
<img src="http://example.com/click.php?id=12345678" height="1px" width="1px"/>
These clicks are still being counted as the request are served by both the pages click.php and process.php
What's the solution for this?
I have got a solution to the problem and it works perfectly:
EDIT:
I have found a solution:
To set the variables using sessions at click.php and sent it to process.php using a random number
click.php
$_SESSION["ip"]=$ip;
$_SESSION["ua"]=$ua;
$rand="".rand(1,9)."".rand(0,9)."".rand(0,9)."".rand(0,9)."".rand(0,9)."".rand(0,9)."";
$_SESSION["hash"]=$rand;
<a href="http://example.com/process.php?hash=$rand">
<img src="http://example.com/ads/banner.png"/></a>
and getting the values from the session at process.php
process.php
$hash=$_GET["hash"];
$ua=$_SESSION["ua"];
$ip=$_SESSION["ip"];
$rand=$_SESSION["hash"];
// Invalid Redirection Protection
if(($hash!=$rand) || ($ip!=$_SERVER['REMOTE_ADDR']) || ($ua!=$_SERVER['HTTP_USER_AGENT'])) {
header("location: http://example.com");
session_destroy();
exit;
}
If I have understood your question, your goal is to ensure that any requests arriving at http://example.com/process.php were from links created by http://example.com/click.php
(note that this only means that anyone trying to subvert your system needs to fetch http://example.com/click.php and extract the relevant data before fetching http://example.com/process.php. It raises the bar a little but it is a long way from being foolproof).
PHP already has a very good sessions mechanism. It would be easy to adapt to propogation via a url embedded in the script output (since you can't rely on cookies being available). However as it depends on writing to storage, its not very scalable.
I would go with a token with a finite number of predictable good states (and a much larger number of bad states). That means using some sort of encryption. While a symmetric cipher would give the easiest model to understand it's more tricky to implement than a hash based model.
With the hash model you would hash the values you are already sending with a secret salt and include the hash in the request. Then at the receiving end, repeat the exercise and compared the generated hash with the sent hash.
To prevent duplicate submissions you'd need to use some other identifier in the request vars - a large random number, the client IP address, the time....
define('SALT','4387trog83754kla');
function mk_protected_url($url)
{
$parts=parse_url($url);
$args=parse_str($parts['query']);
$args['timenow']=time();
$args['rand']=rand(1000,30000);
sort($args);
$q=http_build_query($args);
$args['hash']=sha1(SALT . $q);
$q=http_build_query($args);
return $parts['scheme'] . '://'
.$parts['host'] . '/'
.$parts['path'] . '?' . $q;
}
function chk_protected_url($url)
{
$parts=parse_url($url);
$args=parse_str($parts['query']);
$hash=$args['hash'];
unset($args['hash'];
// you might also want to validate other values in the query such as the age
$q=http_build_query($args);
$check=sha1(SALT . $q);
return ($hash === $check)
}
I have read about users being able to manipulate website cookie and use it to exploits security loopholes. I did a search and came across an idea posted online. Here is the code below that is, after the username and password of the user are authenticated;
$Separator = '--';
$uniqueID = 'jhlhgjh12u0#345';
$Data = $userID.' '.md5('65748');
$expire=time()+60*24;
setcookie('verify-user', $Data.$Separator.md5($Data.$uniqueID), $expire);
The code above will set the cookie using a uniqueID, the userID, a MD5 hash numbers and a separator. The uniqueID, md5 hash numbers and separator are set by the developer. The idea is that a user won't be able to manipulate the cookie because the don't know the UniqueID, and the md5 hash numbers. The code below is used to test each cookie if they are manipulated or not
if ($_COOKIE) {
$Separator="--";
$uniqueID = 'jhlhgjh12u0#345';
$Cut = explode($Separator, $_COOKIE['verify-user']);
if (md5($Cut[0].$uniqueID) === $Cut[1]) {
$_COOKIE['verify-user'] = $Cut[0];
} else {
echo "fake cookie";
}
}
else {
echo "fake cookie";
}
I am wondering if this method is security tight or if there are loopholes too. criticism and corrections are welcomed
This is known as message signing. You hash the message together with a secret and attach that "signature" to the message itself. This allows the recipient to verify that the creator/signer of the message is in possession of the secret, without revealing the secret itself.
The problem with your particular implementation is that
the secret is too small
the hashing algorithm is unsuitable for the task
the cookies never change and never expire; if a cookie is stolen there's no recourse
You should use a longer secret, the longer the better. You should also use a hashing algorithm that is suited for the task, namely something like HMAC (hash-based message authentication). E.g.:
hash_hmac('sha512', $data, $secret)
You can see an implementation of a similar thing, including expiration of values, here.
The most important thing though: think thrice about whether a signed plain text message is the best way to go here in the first place. Perhaps you want a session-like system, in which an entirely meaningless random string is used as an id for data that is stored on the server. This completely eliminates the problem of users manipulating the cookie.
I'm trying to make a url shortener for a client. I've got a decent algorithm going right now, the only problem is that if the client was to shorten the same URL for a different promotion it would create the same code.
What can I do to prevent that?
$hash = sha1($this->data[$this->alias]['us_url']);
$this->data[$this->alias]['shortid'] = base_convert(hexdec($hash), 10, 32);
I'd like to be able to create multiple shortcodes for the same url to track it differently.
Same procedure as when creating password hashes: Use some salt.
$hash = sha1($randomly_generated_salt . $my_url);
Thus, same input strings will create different hashes.
The salt should have a decent length to provide enough entropy.
(Although you wouldn't use sha1 to hash passwords!)
Add the possibility to give a custom url. In pseudocode:
if(isset($customUrl) && isUniqueLabel($customUrl)) {
createCustomUrl()
} else {
createUsualUrl()
}
Given the custom URL the client has the possibility to define the url manually (as is.gd and many other services are doing) and this will increase the possibility of customization.