I have a script on my webserver that initiates a HTTPS connection with a secured payment solution. The payment solution allows the user to store its credit cards credentials, so the script's integrity is mandatory.
The script is called from web and from a web browser launch by an iPhone application.
It takes several POST values in entry:
user ID
value
currency
... etc
to generate the initial request with the payment solution.
My goal is to secure, as much as possible, the POST values sent to the script to avoid attacks, essentialy because anyone could see the POST variables it takes in entry with a simple Firebug.
The script is reached via HTTPS protocol, and this is what I came up with in order to secure the content data :
if (!empty($_POST)) {
$uid = $_POST['uid'];
$nonce = $_POST['nonce'];
$request_timestamp = $_POST['time'];
//other useful values ...
}
/*
* Test 1 : is the nonce correct ? (Example of possible hash)
*/
if (strcmp(md5($uid . $request_timestamp . 'mygrE4TpassPhraZ'), $nonce) !== 0) {
echo 'Bad nonce';
exit;
}
/*
* Test 2 : is the timestamp value acceptable ? 10 seconds maximum here.
*/
if (time() - $request_timestamp > 10) {
echo 'Request too old';
exit;
}
/*
* Test 3 : is this request is the first valid one that I receive with those credentials for this user ?
*/
if (strcmp(User::getOnGoingNonce($uid), $nonce) === 0) {
echo 'Request already registered';
exit;
}
//direct database access
User::setOnGoingNonce($uid,$nonce);
/*
* Finally, chat granted with the payment solution ...
*/
Do you think this is secure enough ? Do you have cleaner solutions ?
Inputs would be greatly appreciated, thank you in advance.
It is good developer practice to sanitize and filter every user input. There is a special PHP function for that purpose, available to make a programmer's life easier. It is called filter_var().
If you work with arrays you can use the filter_var_array().
See here for details.
So, the practical solution to your code should be something like this:
$uid = filter_var($_POST['uid'], FILTER_SANITIZE_NUMBER_INT);
$nonce = filter_var($_POST['nonce'], FILTER_SANITIZE_STRING);
$request_timestamp = filter_var($_POST['time'], FILTER_SANITIZE_STRING);
I assume, that 'uid' is a integer, other variables are strings. But you can choose whatever filter you need. See here for type of filters.
When sanitizing the user input you ensure, that your script will not allow SQL injection attacks, for example, or XSS attacks.
Related
Help! I'm writing some code to update a mySQL database using similar to the code below:-
$.post('http://myURL.com/vote.php?personID=' + personID + '&eventID=123');
The vote.php code takes the querystring values and inserts a record into a database with those values in it.
This kind of code is working fine, but I've realised the problem is that people could just type something like:
http://myURL.com/vote.php?personID=5&eventID=123
into their address bar and essentially spam the app...
Is there a straightforward way I can ensure this doesn't happen? I'm reasonably new to these technologies so not aware of how everything works or fits together, but I'm learning fast so any pointers would be super useful.
It is not a good idea to use GET parameters for data that goes to a database. Generally, you want to use POST parameters which are not visible in the URL. So instead of :
$.post('http://myURL.com/vote.php?personID=' + personID + '&eventID=123');
You would do it like this :
$.post('http://myURL.com/vote.php', { "personID" : personID, "eventID" : 123 });
And in your PHP script, you would access your data with the $_POST array like this :
$personID = $_POST['personID'];
$eventID = $_POST['eventID'];
However, don't forget to properly filter input before saving to the database to prevent bad things like SQL Injection.
This is not a silver bullet : spam will still be possible because any HTTP client will be able to send a post request to your site. Another thing you can look at is Security Tokens to make it even less vulnerable to spam. Or implement a system that limits the number of request/minute/user... but I'm getting too far from the original question.
Correct syntax of $.post is
$.post(url,data_to_send,callback_function)
By using this method your user will never be able to damage your site.Use like
$.post('http://myURL.com/vote.php',{"personID":personID,"eventID":123);
Whether you're using POST or GET, you could always consider signing important fields in your page by using hash_hmac. This prevents people from changing its value undetected by adding a signature that no one else can guess.
This also makes CSRF more difficult, though not impossible due to fixation techniques. It's just yet another technique that can be put in place to make it more difficult for "fiddlers".
The following function adds a salt and signature to a given person id to form a secured string.
define('MY_SECRET', 'an unguessable piece of random text');
function getSecurePersonId($personId)
{
$rnd = uniqid("$personId-", true);
$sig = hash_hmac('sha1', $rnd, MY_SECRET);
return "$rnd-$sig";
}
You would pass the output of getSecuredPersonId() to JavaScript to pass as data in the $.post() or $.get(); posting would be recommended btw.
When the form is submitted your person id would end up in either $_GET['personID'] or $_POST['personID'] depending on the request method. To validate the given value, you run it through this function:
function validateSecurePersonId($securePersonId)
{
if (3 != count($parts = explode('-', $securePersonId))) {
return false;
}
// reconstruct the signed part
$rnd = "{$parts[0]}-{$parts[1]}";
// calculate signature
$sig = hash_hmac('sha1', $rnd, MY_SECRET);
// and verify against given signature
return $sig === $parts[2] ? $parts[0] : false;
}
If the value is properly signed, it will return the original person id that you started out with. In case of failure it would return false.
Small test:
$securePersonId = getSecurePersonId(123);
var_dump($securePersonId);
if (false === validateSecurePersonId($securePersonId)) {
// someone messed with the data
} else {
// all okay
}
I have a somewhat hack-ish question and I'm intrigued as to how I would do the following (if even possible):
Basically, to give a bit of context, I had an account on a site a few years ago and had a username and password for the portal to log in and see all my information/transcript/etc. I haven't connected since I stopped using it a couple years ago by I wanted to view the information that I submitted. Problem is, I can no longer remember the password (to a certain degree). And if I go the 'forgot password' route, it's linked to a really old hotmail address which was deactivated a while back.
I'm aware that this will involve some sort of password crack and I don't want to talk about ways to screw people and gain access to their accounts but it's mine and I'm curious if this is possible.
Thing is, I have the username and I have the majority of the password, all except the final 2 numbers. I can't remember them. I know I added 2 digits at the end because I was forced to (between 10 and 99).
So say my username was 'johnsmith' and my password was 'eatdog##', is there a way to create a form and loop it over and over until the password is guessed correctly? I'm aware they might have some sort of protection against the amount of tries per 'whatever amount of time'.
Thanks.
Considering you only need to iterate over < 100 different possibilities, this should be crackable.
View the HTML source of the page that contains the login form and see which page the form submits to. Lets assume it is action.php. You will see something like this in the HTML source:
<form id="login" action="action.php" method="post">
Use cURL to make a POST request to action.php with your username and password as POST parameters (including anything else the form is posting). Do this in a loop with the password changing at each iteration.
Your code should look something like this (in PHP)
$username = "johnsmith";
$pass_base = "eatdog";
$url = "the url the form submits to";
$failed = ""; //the response returned by the server when login fails
for ($i=10; $i < 100; $i++)
{
$password = $pass_base . $i;
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POST,true);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
//set the POST parameters
$data = curl_exec($ch);
curl_close($ch);
if ($data != $failed) //analyze the returned data
{
echo $password; //this is your password
break;
}
}
The above code is a PHP script. It WILL NOT run as is. I've only provided the meat of the script. You might have to do some basic initialization - hopefully you're somewhat familiar with PHP.
You can run it from your localhost (install WAMP). I'd estimate it shouldn't take more than 5 min to run through all the passwords.
This would only work if the login process isn't specifically designed to stop brute force attacks. If it locks you out for x min after y unsuccessful logins, you'd have to sleep the PHP script after every y-1 attempts for sometime so as not to trigger the lockout.
If it starts asking for captcha, the above script won't work.
If they didn't add mysql_real_escape_string then you can force your way in by entering your username and for your password enter a blank space followed by
" OR 1=1
The double quotes will set the password slot equal to nothing. The or will force the mysql query to check the second statement should password not return the proper value, it won't.
And thus 1 always equals 1 and you will be allowed to log-in.
You'd think most websites would use the simple function so it might not work but it's worth one login attempt.
If you were the owner of the site and you wanted to do something about this, a really rough way to defend against this would be something like (using PHP):
$count = file_get_contents('/some/writable/dir/'$_POST['username']);
if (!$count) {
$count = 0;
}
if ($count > 5) {
print "Naughty!"; // or add a CAPTCHA or something
exit;
}
$success = checkLogin($_POST['username'], $_POST['password']);
if ($success) {
// set cookies, send them away with header('location:blah.php'); exit
} else {
$count ++;
file_put_contents('/some/writable/dir/'$_POST['username'], $count);
}
And set a cron job to delete all the files in /some/writable/dir/ every five minutes or so.
Like I said, it's properly rough, but it should give you an idea of how to start adding some armour to your site.
I'm looking for the safest way to not allow my web form, which uses PHP and MySQL, to be posted to from off site. I've done some searching and found that most people suggest setting a hidden field in the form and a session variable with a md5() hash value and check for it on form submission. But that doesn't seem very secure because the md5() hash value can be seen in the source of the form in the hidden value.
Here is my idea to not allow off site form submissions. It's a bit more resource intense with the database calls but looks to be more secure because the code hash is never sent to the client side.
Please take a look at it and see if you can poke any holes in this security measure to prevent off site form posts.
// First time form loads
if (!$_POST) {
session_start();
$code_options = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z','1','2','3','4','5','6','7','8','9');
for ($i=1; $i<=20; $i++) {
$code .= array_rand(array_flip($code_options), 1);
}
// Insert new record
$con = connect_to_db(); // connects to db
$sql = "INSERT INTO security_table (form_code) values('$code')";
$result = run_query($sql, $con); // runs the query
$_SESSION['formcode'] = $code;
$_SESSION['formid'] = mysql_insert_id();
}
// If form was posted to
if ($_POST) {
session_start();
$con = connect_to_db();
$form_code = mysql_real_escape_string($_SESSION['formcode']);
$form_id = mysql_real_escape_string($_SESSION['formid']);
$sql = "SELECT form_code FROM security_table WHERE form_code = '$form_code' AND form_id = '$form_id '";
$result = run_query($sql, $con);
if (mysql_num_rows($result) > 0) {
// Process the form
// If form processes successfully
$_SESSION['formcode'] = "";
$_SESSION['formid'] = "";
}else{
// Error
}
}
Instead of doing this in your application, you could control access to your site though your web server configuration. Nice separation of concerns if you can do it this way -- the server deals with requests and the application only deals with the logic.
Assuming you're using Apache and you have read/write access to your apache http.conf or a local .htaccess, you can add a rule like this:
<Limit GET POST>
order deny,allow
deny from all
allow from 199.166.210.
allow from .golden.net
allow from proxy.aol.com
allow from fish.wiretap.net
</Limit>
So, deny everyone, except for the few IP or network addresses you chose to allow.
See the Apache docs for the nitty gritty details.
But that doesn't seem very secure because the md5() hash value can be seen in the source of the form in the hidden value.
It doesn't matter. If you encrypt it well enough, the token will be greek to anyone, and therefore impossible to recreate unless you know the key mechanism in the php script. Since you regenerate the token after each post, seeing what it looks like won't help any bad guy.
Your way of securing your form is basically "if you have the session, you're fine". So if the spamming machine has visited your page once, the security layer has been passed. The reason for why you have a client side token is that the spammer has to provide something that only is attainable in your actual form.
There are lots of discussions about secure forms at stackoverflow, have a look at Good Form Security - no CAPTCHA, for example.
Here's a good article explaining different methods of securing a form. I've used these methods with good results.
http://www.campaignmonitor.com/blog/post/3817/stopping-spambots-with-two-simple-captcha-alternatives
We have some problems with users performing a specific action twice, we have a mechanism to ensure that users can't do it but somehow it still happens. Here is how our current mechanism works:
Client side: The button will be disabled after 1 click.
Server side: We have a key hash in the URL which will be checked against the key stored in SESSIONS, once it matches, the key is deleted.
Database side: Once the action is performed, there is a field to be flagged indicating the user has completed the action.
However, with all these measures, still there are users able to perform the action twice, are there any more safer methods?
Here is the partial code for the database side:
$db->beginTransaction();
// Get the user's datas
$user = $db->queryRow("SELECT flag FROM users WHERE userid = {$auth->getProperty('auth_user_id)}");
if ($user['flag'] != 0) {
$db->rollback();
// Return with error
return false;
}
// Proceed with performing the action
// --- Action Here ---
// Double checking process, the user data is retrieved again
$user = $db->queryRow("SELECT flag FROM users WHERE userid = {$auth->getProperty('auth_user_id)}");
if ($user['flag'] != 0) {
$db->rollback();
// Return with error
return false;
}
// --- The final inserting query ---
// Update the flag
$db->query("UPDATE users SET flag = 1 WHERE userid = {$auth->getProperty('auth_user_id)}");
$db->commit();
return true;
It is good to see that you have taken all measures to defeat the bad guys. Speaking in terms of bad guys:
Client side: This can easily be bypassed by simply disabling javascript. Good to have anyways but again not against bad guys.
Server side: This is important, however make sure that you generate a different hash/key with each submission. Here is a good tutorial at nettutes on how to submit forms in a secure fashion.
Database side: Not sure but I suspect, there might be SQL injection problem. See more info about the SQL Injection and how to possibly fix that.
Finally:
I would recommend to you to check out the:
OWASP PHP Project
The OWASP PHP Project's goal (OWASP PHP Project Roadmap) is to enable developers, systems administrators and application architects to build and deploy secure applications built using the PHP programming language.
Well the JS method and Hash method may be cheated by some notorious guy, but 3rd method seems to be very good in order to protect the redundancy. There must be some programming flaw to get passed this.
Why don't u just check the flag field on the page where you are inserting the values rather than where user performing the action (if you are doing it now)
Pseudocode follows:
<?
$act_id; // contains id of action to be executed
$h = uniqid('');
// this locks action (if it is unlocked) and marks it as being performed by me.
UPDATE actions SET executor = $h WHERE act_id = $act_id AND executor = '';
SELECT * FROM actions WHERE executor = $h;
//
// If above query resulted in some action execute it here
//
// if you want to allow for executing this exact action in the future mark it as not executed
UPDATE actions SET executor = '' WHERE act_id = $act_id;
Important things:
First query should be update claiming
the action for me if it is yet
unclaimed.
Second should be query
grabbing action to execute but only
if it was claimed by me.
I'm a beginner in PHP.
What I'm trying to do is stop Post Data coming from another webpage.
The problem I am having is let's say someone copies my form and pastes it in their website. I want to be able to stop that Post Data from running the script on my email form.
How can I do this? Let me know if I'm not being clear enough.
My PHP Contact form runs on one page with conditional statements. i.e. if data checks out, submit.
"accepted answer" has security holes. Instead, you should use more secure methods. A simple example:
Step 1: Disable framing of the page (.php), where the form is generated, in the top add:
header('X-Frame-Options: Deny');
Step 2: (important part ! ): In order to avoid XSS and 3rd party exploits, you should create a expirable validation.
For example:
ASP.NET builtin forms use dynamic input csrf (example value: gtlkjh29f9ewduh024cfvefb )
WordPress builtin forms use dynamic input nonce (example value: 340297658942346 )
So, if you are on a custom platform, which doesn't have built-in temporary token validation methods, then implement your approach. A simple concept:
<?php
$secret_key = 'fjd3vkuw#KURefg'; //change this
$encrypted_value = Cryptor::encrypt( time(), $_SERVER['REMOTE_ADDR'] . $secret_key);
?>
<form>
...
...
<input value="<?php echo $encrypted_value;?>" name="temp_random" type="hidden" />
</form>
(Cryptor code is here )
on submission, check:
if(!empty($_POST)){
// If REFERRER is empty, or it's NOT YOUR HOST, then STOP it
if( !isset($_SERVER['HTTP_REFERRER']) || parse_url($_SERVER['HTTP_REFERRER'])['host'] != $_SERVER['HTTP_HOST'] ){
exit("Not allowed - Unknown host request! ");
}
// Now, check if valid
if ( Cryptor::decrypt( $_POST['temp_random'], $_SERVER['REMOTE_ADDR'] . $secret_key) < time() - 60* 15 ) {
exit("Not allowed - invalid attempt! ");
}
...........................................
... Now, you can execute your code here ...
...........................................
}
You're trying to prevent CSRF - Cross-Site Request Forgery. Jeff himself has a blog article about this.
True XSRF Prevention requires three parts:
Hidden Input Fields, to prevent someone from just snatching the form and embedding it
Timechecking within an epsilon of the form being generated, otherwise someone can generate a valid form once and use the token (depending on impementation/how it's stored)
Cookies: this is to prevent a malicious server from pretending it's a client, and performing a man-in-the-middle attack
$_SERVER['HTTP_Referrer'] would be nice but it isn't reliable. You could use a hidden form field that MD5's something and then you check it on the other side.
In the form:
<?
$password = "mypass"; //change to something only you know
$hash = md5($password . $_SERVER['REMOTE_ADDR']);
echo "<input type=\"hidden\" name=\"iphash\" value=\"$hash\"/>";
?>
When you are checking:
$password = "mypass"; //same as above
if ($_POST['iphash'] == md5($password . $_SERVER['REMOTE_ADDR'])) {
//fine
}
else {
//error
}
If you're looking for a quick-and-dirty approach, you can check the REFERER header.
If you really want to make sure that the form was fetched from your site though, you should generate a token each time the form is loaded and attach it to a session. A simple way to do this would be something like:
$_SESSION['formToken'] = sha1(microtime());
Then your form can have a hidden input:
<input type="hidden" name="token" value='<?=$_SESSION['formToken'];?>' />
and you can check that when deciding whether to process your form data.
Every user do signup and then obtain a login id.
Following is algorithm to prevent CSRF: -
1) $login_id = user login id (converted to a numeric id using mysql)
2) $a_secret_key = $_SERVER['UNIQUE_ID'];
3) $remote_addr = $_SERVER['REMOTE_ADDR'];
4) Request Date and Time -> A unique reference key -> $refkey
5) $_SESSION['secretkey'] = $_SERVER['UNIQUE_ID'];
Combine aforesaid 1 to 4 to create a json file, when transferring data to another page.
Then
echo "<input type=\"hidden\" name=\"refkey\" value=\"$refkey\"/>";
At receiver's end:-
Receiver page should check if
1) any json file with $refkey exists at server?
2) If $refkey exists, then check $login_id, $a_secret_key and $remote_addr exists and are correct.
There's a typo in the highest score answer. It should be $_SERVER['HTTP_REFERER'] instead of $_SERVER['HTTP_REFERRER'].