I'm talking about an action game with no upper score limit and no way to verify the score on the server by replaying moves etc.
What I really need is the strongest encryption possible in Flash/PHP, and a way to prevent people calling the PHP page other than through my Flash file. I have tried some simple methods in the past of making multiple calls for a single score and completing a checksum / fibonacci sequence etc, and also obfuscating the SWF with Amayeta SWF Encrypt, but they were all hacked eventually.
Thanks to StackOverflow responses I have now found some more info from Adobe - http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.html and https://github.com/mikechambers/as3corelib - which I think I can use for the encryption. Not sure this will get me around CheatEngine though.
I need to know the best solutions for both AS2 and AS3, if they are different.
The main problems seem to be things like TamperData and LiveHTTP headers, but I understand there are more advanced hacking tools as well - like CheatEngine (thanks Mark Webster)
This is a classic problem with Internet games and contests. Your Flash code works with users to decide a score for a game. But users aren't trusted, and the Flash code runs on the user's computer. You're SOL. There is nothing you can do to prevent an attacker from forging high scores:
Flash is even easier to reverse engineer than you might think it is, since the bytecodes are well documented and describe a high-level language (Actionscript) --- when you publish a Flash game, you're publishing your source code, whether you know it or not.
Attackers control the runtime memory of the Flash interpreter, so that anyone who knows how to use a programmable debugger can alter any variable (including the current score) at any time, or alter the program itself.
The simplest possible attack against your system is to run the HTTP traffic for the game through a proxy, catch the high-score save, and replay it with a higher score.
You can try to block this attack by binding each high score save to a single instance of the game, for instance by sending an encrypted token to the client at game startup, which might look like:
hex-encoding( AES(secret-key-stored-only-on-server, timestamp, user-id, random-number))
(You could also use a session cookie to the same effect).
The game code echoes this token back to the server with the high-score save. But an attacker can still just launch the game again, get a token, and then immediately paste that token into a replayed high-score save.
So next you feed not only a token or session cookie, but also a high-score-encrypting session key. This will be a 128 bit AES key, itself encrypted with a key hardcoded into the Flash game:
hex-encoding( AES(key-hardcoded-in-flash-game, random-128-bit-key))
Now before the game posts the high score, it decrypts the high-score-encrypting-session key, which it can do because you hardcoded the high-score-encrypting-session-key-decrypting-key into the Flash binary. You encrypt the high score with this decrypted key, along with the SHA1 hash of the high score:
hex-encoding( AES(random-128-bit-key-from-above, high-score, SHA1(high-score)))
The PHP code on the server checks the token to make sure the request came from a valid game instance, then decrypts the encrypted high score, checking to make sure the high-score matches the SHA1 of the high-score (if you skip this step, decryption will simply produce random, likely very high, high scores).
So now the attacker decompiles your Flash code and quickly finds the AES code, which sticks out like a sore thumb, although even if it didn't it'd be tracked down in 15 minutes with a memory search and a tracer ("I know my score for this game is 666, so let's find 666 in memory, then catch any operation that touches that value --- oh look, the high score encryption code!"). With the session key, the attacker doesn't even have to run the Flash code; she grabs a game launch token and a session key and can send back an arbitrary high score.
You're now at the point where most developers just give up --- give or take a couple months of messing with attackers by:
Scrambling the AES keys with XOR operations
Replacing key byte arrays with functions that calculate the key
Scattering fake key encryptions and high score postings throughout the binary.
This is all mostly a waste of time. It goes without saying, SSL isn't going to help you either; SSL can't protect you when one of the two SSL endpoints is evil.
Here are some things that can actually reduce high score fraud:
Require a login to play the game, have the login produce a session cookie, and don't allow multiple outstanding game launches on the same session, or multiple concurrent sessions for the same user.
Reject high scores from game sessions that last less than the shortest real games ever played (for a more sophisticated approach, try "quarantining" high scores for game sessions that last less than 2 standard deviations below the mean game duration). Make sure you're tracking game durations serverside.
Reject or quarantine high scores from logins that have only played the game once or twice, so that attackers have to produce a "paper trail" of reasonable looking game play for each login they create.
"Heartbeat" scores during game play, so that your server sees the score growth over the lifetime of one game play. Reject high scores that don't follow reasonable score curves (for instance, jumping from 0 to 999999).
"Snapshot" game state during game play (for instance, amount of ammunition, position in the level, etc), which you can later reconcile against recorded interim scores. You don't even have to have a way to detect anomalies in this data to start with; you just have to collect it, and then you can go back and analyze it if things look fishy.
Disable the account of any user who fails one of your security checks (for instance, by ever submitting an encrypted high score that fails validation).
Remember though that you're only deterring high score fraud here. There's nothing you can do to prevent if. If there's money on the line in your game, someone is going to defeat any system you come up with. The objective isn't to stop this attack; it's to make the attack more expensive than just getting really good at the game and beating it.
You may be asking the wrong question. You seem focused on the methods people are using to game their way up the high score list, but blocking specific methods only goes so far. I have no experience with TamperData, so I can't speak to that.
The question you should be asking is: "How can I verify that submitted scores are valid and authentic?" The specific way to do that is game-dependent. For very simple puzzle games, you might send over the score along with the specific starting state and the sequence of moves that resulted in the end state, and then re-run the game on the server side using the same moves. Confirm that the stated score is the same as the computed score and only accept the score if they match.
An easy way to do this would be to provide a cryptographic hash of your highscore value along with the score it self. For example, when posting the results via HTTP GET:
http://example.com/highscores.php?score=500&checksum=0a16df3dc0301a36a34f9065c3ff8095
When calculating this checksum, a shared secret should be used; this secret should never be transmitted over the network, but should be hard coded within both the PHP backend and the flash frontend. The checksum above was created by prepending the string "secret" to the score "500", and running it through md5sum.
Although this system will prevent a user from posting arbitrary scores, it does not prevent a "replay attack", where a user reposts a previously calculated score and hash combination. In the example above, a score of 500 would always produce the same hash string. Some of this risk can be mitigated by incorporating more information (such as a username, timestamp, or IP address) in the string which is to be hashed. Although this will not prevent the replay of data, it will insure that a set of data is only valid for a single user at a single time.
To prevent any replay attacks from occurring, some type of challenge-response system will have to be created, such as the following:
The flash game ("the client") performs an HTTP GET of http://example.com/highscores.php with no parameters. This page returns two values: a randomly generated salt value, and a cryptographic hash of that salt value combined with the shared secret. This salt value should be stored in a local database of pending queries, and should have a timestamp associated with it so that it can "expire" after perhaps one minute.
The flash game combines the salt value with the shared secret and calculates a hash to verify that this matches the one provided by the server. This step is necessary to prevent tampering with salt values by users, as it verifies that the salt value was actually generated by the server.
The flash game combines the salt value with the shared secret, high score value, and any other relevant information (nickname, ip, timestamp), and calculates a hash. It then sends this information back to the PHP backend via HTTP GET or POST, along with the salt value, high score, and other information.
The server combines the information received in the same way as on the client, and calculates a hash to verify that this matches the one provided by the client. It then also verifies that the salt value is still valid as listed in the pending query list. If both these conditions are true, it writes the high score to the high score table and returns a signed "success" message to the client. It also removes the salt value from the pending query list.
Please keep in mind that the security of any of the above techniques is compromised if the shared secret is ever accessible to the user
As an alternative, some of this back-and-forth could be avoided by forcing the client to communicate with the server over HTTPS, and insuring that the client is preconfigured to trust only certificates signed by a specific certificate authority which you alone have access to.
I like what tpqf said, but rather than disabling an account when cheating is discovered, implement a honeypot so whenever they log in, they see their hacked scores and never suspect that they have been marked as a troll. Google for "phpBB MOD Troll" and you'll see an ingenious approach.
In the accepted answer tqbf mentions that you can just do a memory search for the score variable ("My score is 666 so I look for the number 666 in memory").
There's a way around this. I have a class here: http://divillysausages.com/blog/safenumber_and_safeint
Basically, you have an object to store your score. In the setter it multiplies the value that you pass it with a random number (+ and -), and in the getter you divide the saved value by the random multiplicator to get the original back. It's simple, but helps stop memory search.
Also, check out the video from some of the guys behind the PushButton engine who talk about some of the different ways you can combat hacking: http://zaa.tv/2010/12/the-art-of-hacking-flash-games/. They were the inspiration behind the class.
Encrypting using a known (private) reversible key would be the simplest method. I'm not all up on AS so I'm not sure what sorts of encryption providers there are.
But you could include variables like game-length (encrypted, again) and a click count.
All things like this can be reverse engineered so consider throwing in a bunch of junk data to throw people off the scent.
Edit: It might be worth chucking in some PHP sessions too. Start the session when they click start game and (as the comment to this post says) log the time. When they submit the score you can check they've actually got an open game and they're not submitting a score too soon or too large.
It might be worth working out a scalar to see say what the maximum score is per second/minute of play.
Neither of these things are uncircumventable but it'll help to have some logic not in the Flash where people can see it.
In my experience, this is best approached as a social engineering problem rather than a programming problem. Rather than focusing on making it impossible to cheat, focus on making it boring by removing the incentives to cheat. For example, if the main incentive is publicly visible high scores, simply putting a delay on when high scores are shown can significantly reduce cheating by removing the positive feedback loop for cheaters.
You cannot trust any data the client returns. Validation needs to be performed on the server side. I'm not a game developer, but I do make business software. In both instances money can be involved and people will break client side obfuscation techniques.
Maybe send data back to server periodically and do some validation. Don't focus on client code, even if that is where your applicaiton lives.
I made kind of workaround... I had a gave where scores incremented ( you always get +1 score ). First, I started to count from random num (let's say 14 ) and when I display the scores, just showed the scores var minus 14. This was so if the crackers are looking for example for 20, they won't find it ( it will be 34 in the memory ). Second, since I know what the next point should be... I used adobe crypto library, to create the hash of what the next point should be. When I have to increment the scores, I check if the hash of the incremented scores is equal to the hash is should be. If the cracker have changed the points in the memory, the hashes are not equal. I perform some server-side verification and when I got different points from game and from the PHP, I know that cheating were involved. Here is snippet ot my code ( I'm using Adobe Crypto libraty MD5 class and random cryptography salt. callPhp() is my server side validation )
private function addPoint(event:Event = null):void{
trace("expectedHash: " + expectedHash + " || new hash: " + MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt) );
if(expectedHash == MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt)){
SCORES +=POINT;
callPhp();
expectedHash = MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt);
} else {
//trace("cheat engine usage");
}
}
Using this technique + SWF obfustication, I was able to stop the crackers. Also, when I'm sending the scores to the server-side, I use my own small, encryption / decryption function. Something like this (server side code not included, but you can see the algorithm and write it in PHP) :
package {
import bassta.utils.Hash;
public class ScoresEncoder {
private static var ranChars:Array;
private static var charsTable:Hash;
public function ScoresEncoder() {
}
public static function init():void{
ranChars = String("qwertyuiopasdfghjklzxcvbnm").split("")
charsTable = new Hash({
"0": "x",
"1": "f",
"2": "q",
"3": "z",
"4": "a",
"5": "o",
"6": "n",
"7": "p",
"8": "w",
"9": "y"
});
}
public static function encodeScore(_s:Number):String{
var _fin:String = "";
var scores:String = addLeadingZeros(_s);
for(var i:uint = 0; i< scores.length; i++){
//trace( scores.charAt(i) + " - > " + charsTable[ scores.charAt(i) ] );
_fin += charsTable[ scores.charAt(i) ];
}
return _fin;
}
public static function decodeScore(_s:String):String{
var _fin:String = "";
var decoded:String = _s;
for(var i:uint = 0; i< decoded.length; i++){
//trace( decoded.charAt(i) + " - > " + charsTable.getKey( decoded.charAt(i) ) );
_fin += charsTable.getKey( decoded.charAt(i) );
}
return _fin;
}
public static function encodeScoreRand(_s:Number):String{
var _fin:String = "";
_fin += generateRandomChars(10) + encodeScore(_s) + generateRandomChars(3)
return _fin;
}
public static function decodeScoreRand(_s:String):Number{
var decodedString:String = _s;
var decoded:Number;
decodedString = decodedString.substring(10,13);
decodedString = decodeScore(decodedString);
decoded = Number(decodedString);
return decoded;
}
public static function generateRandomChars(_length:Number):String{
var newRandChars:String = "";
for(var i:uint = 0; i< _length; i++){
newRandChars+= ranChars[ Math.ceil( Math.random()*ranChars.length-1 )];
}
return newRandChars;
}
private static function addLeadingZeros(_s:Number):String{
var _fin:String;
if(_s < 10 ){
_fin = "00" + _s.toString();
}
if(_s >= 10 && _s < 99 ) {
_fin = "0" + _s.toString();
}
if(_s >= 100 ) {
_fin = _s.toString();
}
return _fin;
}
}//end
}
Then I send the variable among with other fake-vars and it just get's lost among the way... It is a lot of work for just small flash game, but where prizes are involved some people just get greedy. If you need any help, write me a PM.
Cheers, Ico
There is no way to make it completely unhackable, as it is easy to decompile SWFs, and a skilled developer hacker could then trace through your code and work out how to bypass any encrypted system you might employ.
If you simply want to stop kids cheating via the use of simple tools like TamperData though, then you could generate an encryption key that you pass to the SWF at startup. Then use something like http://code.google.com/p/as3crypto/ to encrypt the high score before passing it back to the PHP code. Then decrypt it at the server end before storing it in the database.
You are talking about what is called the "client trust" problem. Because the client (in this cash, a SWF running in a browser) is doing something it's designed to do. Save a high score.
The problem is that you want to make sure that "save score" requests are coming from your flash movie, and not some arbitrary HTTP request. A possible solution for this is to encode a token generated by the server into the SWF at the time of request (using flasm) that must accompany the request to save a high score. Once the server saves that score, the token is expired and can no longer be used for requests.
The downside of this is that a user will only be able to submit one high score per load of the flash movie - you've have to force them to refresh/reload the SWF before they can play again for a new score.
I usually include "ghost data" of the game session with the highscore entry. So if I'm making a racing game, I include the replay data. You often have the replay data already for replay functionality or ghost racing functionality (playing against your last race, or playing against the ghost of dude #14 on the leaderboard).
Checking these is very manual labour, but if the goal is to verify if the top 10 entries in a contest are legit, this can be a useful addition to the arsenal of security measures others have pointed out already.
If the goal is to keep highscores list online untill the end of time without anybody having to look at them, this won't bring you much.
The way that a new popular arcade mod does it is that it sends data from the flash to php, back to flash (or reloads it), then back to php. This allows you to do anything you want to compare the data as well bypass post data/decryption hacks and the like. One way that it does this is by assigning 2 randomized values from php into the flash (which you cannot grab or see even if running a realtime flash data grabber), using a mathematical formula to add the score with the random values then checking it using the same formula to reverse it to see if the score matches it when it finally goes to the php at the end. These random values are never visible as well as it also times the transaction taking place and if it's any more than a couple seconds then it also flags it as cheating because it assumes you have stopped the send to try to figure out the randomized values or run the numbers through some type of cipher to return possible random values to compare with the score value.
This seems like a pretty good solution if you ask me, does anybody see any issues with using this method? Or possible ways around it?
It is only possible by keeping the all game logic at server-side which also stores the score internally without knowledge of the user. For economical and scientific reasons, mankind can not apply this theory to all game types excluding turn-based. For e.g. keeping physics at server-side is computationally expensive and hard to get responsive as speed of hand. Even possible, while playing chess anyone can match AI chess gameplay to opponent. Therefore, better multiplayer games should also contain on-demand creativity.
Whenever your highscore system is based on the fact that the Flash application sends unencrpyted/unsigned highscore data via the network, that can be intercepted and manipulated/replayed. The answer follows from that: encrypt (decently!) or cryptographically sign highscore data. This, at least, makes it harder for people to crack your highscore system because they'll need to extract the secret key from your SWF file. Many people will probably give up right there. On the other hand, all it takes is a singly person to extract the key and post it somewhere.
Real solutions involve more communication between the Flash application and the highscore database so that the latter can verify that a given score is somewhat realistic. This is probably complicated depending on what kind of game you've got.
It's not really possible to achieve what you want. The internals of the Flash app are always partially accessible, especially when you know how to use things like CheatEngine, meaning no matter how secure your website and browser<->server communications are, it is still going to be relatively simple to overcome.
I think the simplest way would be to make calls to a function like RegisterScore(score) each time the game registers a score to be added and then encode it, package it and send it to a php script as a string. The php script would know how to decode it properly. This would stop any calls straight to the php script as any tries to force a score would result in a decompression error.
It might be a good idea to communicate with backend via AMFPHP. It should discourage at least the lazy ones from trying to push the results via browser console.
Related
So I'm working on a Chrome extension for someone else. I don't want to give away specific details about the project, so for I'll use an equivalent example: let's assume it's an extension to run on an image/forum board. Imagine I have variables such as userPoints, isBanned etc. The later being fairly self-explanatory, while the former corresponding to points the user acquires as they perform certain actions, hence unlocking additional features etc
Let's imagine I have code like:
if(accountType !== "banned"){
if(userPoints > 10000) accountType = "gold";
else if(userPoints > 5000) accountType = "silver";
else if(userPoints > 2500) accountType = "bronze";
else if(userPoints <= 0) accountType = "banned";
else accountType = "standard";
}else{
alert("Sorry, you're banned");
stopExtension();
}
Obviously though, it becomes trivial for someone with the knowledge to just browse to the extensions background page and paste chrome.storage.local.set({'userPoints': 99999999}) in the console, hence giving them full access to all the site. And, with the Internet, someone can of course share this 'hack' on Twitter/YouTube/forums or whatever, then suddenly, since all they'd need to do is copy and paste a simple one-liner, you can have 1000s of people, even with no programming experience, all using a compromised version of your extension.
And I realise I could use a database on an external site, but realistically, it would be possible that I would be wanting to get/update these variables such as userPoints 200+ times per hour, if the user was browsing the extentions target site the entire time. So the main issues I have with using an external db are:
efficiency: realistically, I don't want every user to be querying the
db 200+ times per hour
ease-of-getting-started: I want the user to just download the
extension and go. I certainly don't want them to have to sign up. I
realise I could create a non-expiring cookie with for the user's ID
which would be used to access their data in the db, but I don't want
to do that, since users can e.g. clear all cookies etc
by default, I want all features to be disabled (i.e. effectively
being considered like a 'banned' user) - if, for some reason, the
connection with the db on my site fails, then the user wouldn't be
able to use the extension, which I wouldn't want (and just speaking
from experience of my parents being with Internet providers whose
connection could drop 10 times per hour, for some people, failed
connections could be a real issue) - in contrast, accessing data from
the local storage will have like a 99.999% success rate I'd assume,
so, for non-critical extensions like what I'm creating, that's more
than good enough
Still, at least from what I've found searching, I've not found any Chrome storage method that doesn't also allow the user to edit the values too. I would have thought there would be a storage method (or at least option with chrome.storage.local.set(...) to specify that the value could only be accessed from within the extension's context pages, but I've not found that option, at least.
Currently I'm thinking of encrypting the value to increment by, then obfuscating the code using a tool like obfuscator.io. With that, I can make a simple, like 30 character js file such as this
userPoints = userPoints + 1000;
become about 80,000...still, among all the junk, if you have the patience to scroll through the nonsense, it's still possible to find what you're looking for:
...[loads of code](_0x241f5c);}}}});_0x5eacdc(),***u=u+parseInt(decrypt('\u2300\u6340'))***;function _0x34ff36(_0x17398d)[loads more code]...
[note that, since it's an extension and the js files will be stored on the user's pc, things like file size/loading times of getting the js files from a server are irrelevant]
Hence meaning a user wouldn't be able to do something like chrome.storage.local.set({'userPoints': 99999999}), they'd instead have to set it to the encrypted version of a number - say, something like chrome.storage.local.set({'userPoints': "✀ເ찀삌ሀ"}) - this is better, but obviously, by no means secure.
So anyway, back to the original question: is there a way to store persistent values for a Chrome extension without the user being able to edit them?
Thanks
I have made the html5 game, that users sometime may try to alter to get better highscores.
How can I count in the begin(and runtime) of game the hash of whole JavaScript game ( made with Construct2) so that I can compare if there was any changes runtime done by user.
Game is running inside iframe if it matters anything and sends highscrores after the game.
As said in my comment, hashing your source it's not an efficient anti-hack.
The strongest way to prevent cheating is to run your engine server-side, and validate each action with a predictive client/authoritative server strategy.
Predictive client
The client engine behave as no server is involved, sending data (Eg.) only when score changes along with some additional information (Eg. gametime, position, player state etc etc).
The client receive a unique token stored server side on each game session to identify it.
Authoritative server
The server, once received the score and the additional information, can run a validation against those data, eg:
Is the player position valid ? (Eg: inline with enemy to shoot)
The state of the player is valid ? (Eg: can it shoot down the enemy with a single bullet ?)
The score vs gametime is suitable ? (Eg: can the player has a score of 1 Billion at 2s from start ?
etc etc
Once validation on server side happened, you can reconciliate the game state or invalidate the game by token once finished.
I've got a short question. I want to develop a little script on our intranet.
The requirements are:
a button, which count +1 if you click on it
just logged in users can count
all other users should see the counter
I'm a beginner in JS and i only know the localStorage function, but i want to save the counter on the server, so that everybody see's the same status.
Thats what I got, but its just the localStorage, so every computer has their own status of the counter.
if (localStorage.getItem("counter")!=null) {
counter = Number(localStorage.getItem("counter"));
document.getElementById("counterValue").innerHTML = counter;
}
Do you know what I mean? Thanks for the help and sorry for my bad english :)
You need to keep the 'counter' on the server. You should keep it in some kind of persistent storage (not memory, since the memory can be re-set):
simplest is file system (file)
DB (e.g., MySql)
cloud (e.g., AWS S3)
then to get it from storage and present to all users. If you need the value to be presented for all Live, then you'll have to use some solution for that like SignalR.
I've created a whiteboard web app where a multitude of registered users, login and start drawing on the html5 canvas. I devised the 'multiplayer' aspect of the game through python websockets and the login is made with php, currently the canvas page has a 'session_start();' so I can be able to add a feature to see who's currently using the application and I feel it may come in hand for the 'turn based' aspect as well.
Now I'm trying to prevent users from being able to draw on the canvas at the same time, if possible i'd like every user to have a fair turn at drawing on the canvas. I'm not sure how I'd accomplish this but I feel that Javascript will most definitely be the logic behind this.
Any advise or suggestions as to how I'd go about achieving this feature?
EDIT
Ok since nobody has any answers or suggestion I'll try and provide to you what I've attempted so far, I think it may be on the right direction even though it doesn't work:
var player = document.getElementById("inputnameid").value;
var currentPlayer = player; // player class
//array of player objs.
var array1 = [player]; // list that OnCurrentPlayerEndTurn cycles through to choose user
// call this at the start of the app
function OnStartTurn()
{
currentPlayer.OnBeginTurn();
var inputs=document.getElementById('inputty');
for(i=0;i<inputs.length;i++){
inputs[i].disabled=false;
}
//This function will activate the GUI so the user can now act
}
// call this function when setTimeout is 10 seconds
function OnCurrentPlayerEndTurn()
{
setTimeout('OnCurrentPlayerEndTurn();', 10*1000);
change currentPlayer variable to the next player in line
OnStartTurn(); // will cause the next player to begin his turn
}
Your question seems to be focused on the front-end code, which, while important, is not the critical part of this problem. As you've noted, the core of a turn-based game is the round-robin passing of active players. You probably want this to be done server-side: it's much easier to coordinate the various players from there.
You'll maintain a list of the players in a given game on the server. Before the game starts each client will register with the server and an identifying user id is stored there. Then in each round, the server allows each player a turn. The turn order is of course up to the specifics of the game, but the general idea is the same whether turn order is fixed or fluid.
As each player's turn comes around, the server sends a ticket to that player's client. This is essentially a one-time pad (OTP) concept: generate a random token that is hard to guess (so don't use just an increasing integer, but instead some cheap hash function or the like). The client then sends this ticket along with the request for the move they would like to make, and the server validates that the ticket corresponds to the currently active player before taking any action.
Depending on the rules and requirements of the game, the server can then immediately invalidate the ticket (e.g., chess), can wait until an 'end of turn' move, or can invalidate the ticket after some amount of time. The server then generates a new ticket for the next player to go.
The client-side code follows naturally from this architecture. All inputs can be disabled by default, and only enabled when the client holds a valid ticket. If the ticket is designed to time out, you probably want a method of querying the server to determine if it is still valid. If the user is always responsible for ending their own turn (explicitly or implicitly) you can get away without that.
I'm implementing a like feature for a site I'm working on. A user doesn't have to be logged in and they can either like or dislike a particular page. At the end of it all I'd like to be able to populate a list of articles or stories with the most likes.
I have a very simple method that currently uses an onclick javascript function to update a database via ajax and a php function.
This is working ok. What I'd like to do is prevent a user from spamming the button.
At first I thought of maybe getting the IP address, storing that in the database and then running a check. Is there a better way?
Technically there isn't a bomb proof way to do so. You could get pretty close by allowing one vote per ip-useragent combination. You'd have to implement this on the server side.
PHP Example
$concienceKey = md5($_SERVER['REMOTE_ADDR'] . $_SERVER['USER_AGENT']);
$query = "SELECT COUNT(*) FROM clickConcience WHERE key = `" . $concienceKey . "`";
//run your query
//.....and get the $count;
//
//already voted!
if($count > 0){
echo 'already voted';
return false;
}
//remember entry
$insert = "INSERT INTO clickConcience (key, datetime) VALUES (`" . $concienceKey . "`, NOW())";
//run your query
//.....and insert
//
return true;
straight forward answer, you won't be able to do it.
If I really want to SPAM your "like" button, I will find a way to do so, especially if you're not forcing me to be signed in (I used to write pretty good bots and were pretty efficient spamming big link submission sites).
Javascript will only stop mediocre spammers or sock puppet account holders. As a spammer I can circumvent your Javascript pretty easily, either by programming a time-based robot to like your post, or by sending requests directly to your server (I will not even load your site).
What you need to do, if you really want to prevent spammers from spamming this feature efficiently (efficiency is the keyword here because spammers can still spam your feature, but their likes won't count) is to log every IP that likes a post along with its geographical information (it's not always 100% accurate, but it's a good start) and then run a process in the background that checks for suspicious origins and penalize such likes (either by assigning them less value, or just subtracting them from the total count).
For example if your main audience is people living in the United States, but one post gets a bunch of likes from Mexico, Salvador, India, Australia, Russia, then it's more than likely that there's a spammer behind a proxy or a network similar to TOR and he/she can change his/her IP address at his/her will.
After a few hundred thousand records, you'll have a good base to start blacklisting IP addresses. I usually use R programming language to get statistical information about my databases.
But then again, a good spammer could use a list of IP addresses of compromised computers coming from your audience's country or geographical location, and use those IPs to abuse the feature. Those bots are harder to spot, but you can analyze previous posts and come up with useful metrics as "Likes/comment ratio".
If one post has a huge number of likes, but low number of comments, then it's very probable that someone spammed it, but then again I can program my bot to like AND post a comment so the numbers look natural.
I'm not sure what kind of project you're working on, but if it's something similar to link submission, do not rank (whatever your users are liking) by the number of likes.
The number of likes should only be a factor, you can take a look at how HackerNews or Reddit rank the posts (those projects are open source), but it's a combination between multiple factors.
Just hide the button after it has been clicked for the first time.
It does even makes more sense, when using an AJAX handler for sending the click...
Use cookies. Lets say you have a button where the user can like article 123456789
<button id="like" articleID="123456789">Like</button>
script :
function setLike(articleID) {
document.cookie=articleID+'=y';
}
function hasLiked(articleID) {
var cookies=document.cookie.split(';');
for (var i=0;i<cookies.length;i++) {
var cookie=cookies[i].split('=');
if (cookie[0]==articleID) return true;
}
return false;
}
var button=document.getElementById('like');
button.onclick=function() {
var articleID=this.getAttribute('articleID');
if (!hasLiked(articleID)) {
//register the like in your system
//...
//
setLike(articleID);
} else {
alert('You cant like or dislike an article twice');
}
}
Of course the user can delete all his or hers cookies - but a user can also like the same page / article from 100 different computers. The above prevents the most common scenario : People repetetively clicking like or dislike a lot of times from the same computer in a short distant of time.