Those of you who don't know, I recently made a game with the vanilla stack, i.e pure HTML, CSS and JavaScript. You can give the game a go at oddneven.netlify.app. After launching this game I came to know about some vulnerabilities of a web app made with pure HTML, CSS and JavaScript.
A. People can change the code: The bug which mostly annoyed me after releasing the game is that using the really powerful Chrome Dev Tools, people can access and change my code really easily. What people were using this for is:
a. Making the score variable to increment by a huge number for a correct answer, so that they can claim to get a ridiculously high score and flex on the others. In my game, this is more prominent as there is a global leaderboard.
This can also be done by the debugger tools provided by Google Chrome which can insert a breakpoint in the code at a certain line(specifically where the score is being incremented) and can change the variable from the console simply.
b. This one is not really related to changing the code, but, as people could see my JavaScript so it was easy for them to get the backend URL and send a request via any REST client like postman or insomnia and set their high score even without playing the game.
The solutions I came up with can't completely ensure that your code will be completely secure, but it can throw off such "hackers":
a. Minifying the code: Minifying the JavaScript code will make it to appear on one line. But Chrome has a tool to beautify the minified JavaScript:
So, Chrome Dev Tools apparently allow you to pretty-print the minified code, which defeats the purpose of the minifying for discouraging "hackers". But you should always minify your code for the production of big apps for faster loading time.
b. Obfuscating the code: One of the biggest mistakes I made after the first beta release of my code is to switch to a module-based approach for coding this game(a post on module based javascript on the web by me is under process😁) and I found it a bit difficult and not worth it to obfuscate my code without breaking it in some way or other. That thing aside, let's see how an obscurification looks like:
Normal code:
let a = 10
let b = 20
c = a+b
console.log(`${a},${b},${c}`)
obfuscated code:
const _0x53bb=['constructor','^([^\x20]+(\x20+[^\x20]+)+)+ [^\x20]}','test','return\x20/\x22\x20+\x20this\x20+\x20\x22/','compile','log'];(function(_0x4d6512,_0x53bbe2){const _0x2b6b66=function(_0x1bdf38){while(--_0x1bdf38){_0x4d6512['push'](_0x4d6512['shift']());}};const _0x138d55=function(){const _0x40964d={'data':{'key':'cookie','value':'timeout'},'setCookie':function(_0x35726b,_0x1009c9,_0xd473e1,_0x1f728d){_0x1f728d=_0x1f728d||{};let _0x4290f7=_0x1009c9+'='+_0xd473e1;let _0x254c28=0x0;for(let _0x68403e=0x0,_0x1b5f7e=_0x35726b['length'];_0x68403e<_0x1b5f7e;_0x68403e++){const _0x587822=_0x35726b[_0x68403e];_0x4290f7+=';\x20'+_0x587822;const _0x47187f=_0x35726b[_0x587822];_0x35726b['push'](_0x47187f);_0x1b5f7e=_0x35726b['length'];if(_0x47187f!==!![]){_0x4290f7+='='+_0x47187f;}}_0x1f728d['cookie']=_0x4290f7;},'removeCookie':function(){return'dev';},'getCookie':function(_0x5dbc77,_0x25358c){_0x5dbc77=_0x5dbc77||function(_0x3b38b8){return _0x3b38b8;};const _0x1a3408=_0x5dbc77(new RegExp('(?:^|;\x20)'+_0x25358c['replace'](/([.$?*|{}()[]\/+^])/g,'$1')+'=([^;]*)'));const _0x1acab6=function(_0x2d5f8a,_0x52a994){_0x2d5f8a(++_0x52a994);};_0x1acab6(_0x2b6b66,_0x53bbe2);return _0x1a3408?decodeURIComponent(_0x1a3408[0x1]):undefined;}};const _0x5b82c4=function(){const _0x1937bb=new RegExp('\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}');return _0x1937bb['test'](_0x40964d['removeCookie']['toString']());};_0x40964d['updateCookie']=_0x5b82c4;let _0x41990b='';const _0x2bffd2=_0x40964d['updateCookie']();if(!_0x2bffd2){_0x40964d['setCookie'](['*'],'counter',0x1);}else if(_0x2bffd2){_0x41990b=_0x40964d['getCookie'](null,'counter');}else{_0x40964d['removeCookie']();}};_0x138d55();}(_0x53bb,0x1a9));const _0x2b6b=function(_0x4d6512,_0x53bbe2){_0x4d6512=_0x4d6512-0x0;let _0x2b6b66=_0x53bb[_0x4d6512];return _0x2b6b66;};const _0x40964d=function(){let _0x4290f7=!![];return function(_0x254c28,_0x68403e){const _0x1b5f7e=_0x4290f7?function(){if(_0x68403e){const _0x587822=_0x68403e['apply'](_0x254c28,arguments);_0x68403e=null;return _0x587822;}}:function(){};_0x4290f7=![];return _0x1b5f7e;};}();const _0x1bdf38=_0x40964d(this,function(){const _0x47187f=function(){const _0x5dbc77=_0x47187f[_0x2b6b('0x1')](_0x2b6b('0x4'))()[_0x2b6b('0x5')](_0x2b6b('0x2'));return!_0x5dbc77[_0x2b6b('0x3')](_0x1bdf38);};return _0x47187f();});_0x1bdf38();let a=0xa;let b=0x14;c=a+b;console[_0x2b6b('0x0')](a+','+b+','+c);
This might look gibberish, but if you minutely check it, trained eyes can still find the original code and change it. But obfuscation can be done to protect your Javascript code from others. If you want to know more about obfuscating code you can use obfuscator.io
c. Another solution to this client-side vulnerability will be to use some backend to process and store the score, which will:
Make a ton of request to the backend which is not acceptable for any production level app.
Will make the game unusable offline, which I don't want to happen.
d. To prevent the high score from being a ridiculous amount sent by the client using the exploits, I set up a barrier for the high score so that any score above that will be rejected by the system.
B. People can get your API and randomly request your backend to store new things: In this app, I relied on a small express based backend to store the scores from people. So, once they know the URL of the API they can use an API client to send the result to the server and store it. For larger apps having an API key in the client-side can cause data leak from your database.
The potential solution will be to keep a secret as HTTP only cookie and ask for it on each POST request so that it can validate if the connection is from an actual browser or not. Or otherwise, you can have a look at the user-agent header which often contain the details of the browsers being used.
Top comments (0)