Journey of achieving great software ( Part 2) — Web application security guidelines

Marwan Darwish
9 min readJan 17, 2021

Security constraints

Meeting cybersecurity consideration is a must in the modern era of software development, your application will be subject for cyber attacks from every possible attackers, from bug bounty hunters, script kiddies and ideological attackers or even cyber mobs who are looking for a way to gain financial benefits attacking your apps.

But in our company, it’s a little bet more, Cyber security have their upper hand, they have the go/no-go veto, they even can shut down your app with no regret, if they find a potential threat, the type of threats that is consider show-stopper is not a RCE (Remote code execution) vulnerability or any major issue, this puts extra constraints on your security design and implementation.

Building security layer

For our security we have a plan to have two main layers where we apply all our security remedies and fixes, web server and our API gateway, in addition to all other type of security configurations (Passwords, Hash keys, encryption keys … etc )

Web server will control all ingoing traffic from web client to our servers, and control every possible security remedy from user client perspective, but if for some reason, some one cracked this, it will go to the main security player in our eco system, our API gateway.

In our architecture, we enforced the access to every microservices or module via API gateway, for technical reasons which worth to discuss later, but this mades it the perfect candidate to be our one-stop-shop for applying security remedies, you can apply XSS filter with a simple line of code or a configuration.

You can leverage the power of rate limiting or monetizing of API calling while keeping your original code clean and crisp.

The API gateway we used was Zuul which was created by Netflix, a very powerful yet extremely simple API gateway based on Java/Spring boot, Zuul support basically two major features: URI based routing to encapsulate your microservices either by simple routing rules config or programatic, And also support out of the box filters, basic filters that Zuul support includes, Pre-processing filters, Post processing filters, Error filters (As if it’s try catch on API level), and Processing filter.

easily you can apply any number of pre filter on your request, to put any needed actions or validation or header editing, and off-course the most important: security validations.

User access model

In modern applications, usually you are using federated identity management, using your enterprise AD, or federated login with famous providers, like google, Facebook, linkedIn …. etc, in rare cases you may need to have your custom identity management, which leads you either to have application related LDAP or Database based authentication, the first solution will be extremely complex and unwise from operation and HW resources perspective if it’s nothing beyond authentication, So we pick using databases + Caching user authentication module.

Here is it, the very basic user authentication depends on the following:

  • Registeration: Store user ID + User PWD in Database
  • Authentication: Validate request with User ID and PWD

Pretty simple? Very well, but this solution is not secure and can be easily compromised by Dictionary attack , in a dictionary attack, User is using common english words for guessing user password, till one fires correctly with him, this is a very easy attack if you have sticked to the above registration method, dictionary attack will not take that much long if english alphabet is 26 and normal user will use 8 characters then the number of needed trials is maximum of 26 to the power of 8 which is 2e11 combinations, using normal english words will reduce the list down to less than quarter million trial, even with the very huge combination count, smart attaching systems are using combinations of dictionary entities to successfully meet the target in much less time, add one extra numeric digit will raise the number significantly to 36 to the power of 8, while adding one extra special character will raise it to be almost impossible for normal dictionary attacks.

One more possible attack i that if you were subject to a SQL injection attack your full application user credentials will be compromised. Moreover, this is very easy compromised by MITM attack (Man in the middle). in Man in the middle, your ISP provider and your local network admin, and any switch or router you are connecting two can expose the URI called including the data submitted, so passing PWD as plain text is not a wise approach, better to hash or encrypt the PWD before sending, using strong Hashing or encryption technique will be a wise option. Hence; from frontend: encrypt the pwd and send it to the backend using decryption private key to compare the PWD with the DB stored PWD.

Sounds better but still one issue you will face, this didn’t resolve the issue of SQL injection, also still you are subject to insider attack or even regulation evaluation by storing critical users information, moreover: if the shared secret is in the wrong hand, you are still subject to the same vulnerabilities above.

The solution is use hashing instead of encryption, the main difference is that hashing is one way encryption algorithm, while encryption is two way encryption, that can be later decrypted.

User password will be encrypted using a shared key, with predefined hashing key and stored in the database the hash instead of storing the real password, while on login: you compare the PWD hash to the PWD hash in the users table, even if SQL injection happens or someone have access by mistake to this table he will not get “MUCH”.

  • Registeration: Post User email + Hash_Pwd (Plain text PWD , Hashing key)
  • Authentication: Validatie(User email + Hash_Pwd (Plain text PWD , Hashing key)

One issue is still there, the hash is still representing the user, analyzing the table can show you if PWDs are repeated, so if for some reason my user and your user share same PWD hash, this means inevitably that my password and yours are identical which is another vulnerability that needs to be addressed, more over , resending your hash will be equally the same like sending the original PWD. Moreover, many dictionary attacks are now upgraded to use hash dictionary attack which is — as it’s name suggest — store the hash of every possible english combination — and use it in the attack.

From the above what is needed is sending different password for every session and storing different PWD that the PWD will never be the same in the hash table even if the passwords are identical.

One good technique is to hash the password and append timestamp to it (but this is still splittable and manipulatable, so it’s a good step to re-encrypt it again and to be decrypted at the backend side and check the freshness of the timestamp to avoid any reuse of the encrypted value, then compare the hash to Hash.

We are now almost done, but still one issue of storing the plain Hash which is directly proportional to the PWD value, so you can store a bit of salt, the salt is any randomly generated GUID or combination of characters and numeric value, and unique per user, each password hash when stored is hashed again with the hash salt of the user, this leads to perfect uniqueness of the password even if it’s the same.

Frontend actions steps

  1. Hash the PWD
  2. Add the timestamp to the password
  3. Encrypt (timestamp+password)
  4. Post Encrypted timestamp and password to the backend

API gateway actions steps:

  1. Decrypt the PW returned from API call
  2. Extract the timestamp of the request
  3. Validate timestamp is no older than 30 seconds
  4. If timestamp validation failed, return 401 unauthorized and increment the count of failed logins
  5. get the user salt
  6. Add the salt to the #password
  7. Hash the salt_and_hsh_passowrd
  8. Match the hashed salt_and_hash_password to the active stored token in DB
  9. If matched return 200 , otherwise returen 401 unauthorized and increment failure count
  10. Check if user failed login attempts is more than 5, then return: account is blocked

Other considerations

Avoid denial of service attack, or brute force attacks, brute force attacks depends on attacker guesses the password till he successes, brute force attack can be blocked by making maximum number of failed logins attempts then block account, while this is a good technique, it’s still a vulnerability for denial of service attack, attacker can block any user account or even all user accounts by simple script, better approach is using captcha or exponential backoff, which will limit the ability of the attacker to perform such form of attacks, exponential backoffs depends on setting mean time between failed trials which doubles for each time. If you think that this is not an issue consider a initial waiting time of 1 sec, increases to 2 seconds in the second trial, then 4 , 8, 16 .. etc . in 10 trials you will need to wait 1000 seconds, another ten trials you will need to wait million seconds.

Also one more security dimension to consider the power of social engineering, recent studies indicates, that there are common passwords used between people as long as common patterns, partner name, wedding dates, or kids birth data as well as very common list like this one: https://en.wikipedia.org/wiki/List_of_the_most_common_passwords

One more very important security vulnerability comes from the fact that most of users are using same passwords among different sites, this is not a very big deal if you use one common password for famous protected sites, such as google, yahoo, facebook and twitter, but the issue when you use this password in less protected sites and passwords are leaked for any reason, then you will learn in the very hard way, when some attacker compromise the credentials for local store, it may not be a big deal, but the real night mare if just tried to use your very same credentials to access your google account or your iCloud account, as you can see, the hardest part of security is making users immune against social engineering, you cannot know if the password is shared with other apps or not, rather this needs a better user awareness of cyber security and it’s requirements.

Still we have some solutions which is better than nothing: MFA or 2FA can do just well, considering using third party authenticator or SMS OTP (One time password) is still a smart move.

Session management

One of the important aspects of secure system is the proper session management, while using sticky sessions and load balancers is still a valid solution but the system resilience and expandability will favor stateless architecture which is independent from targeted HW station.

Sending session identifier token stored in local storage or session ID from frontend and validated in the backend which keep managing users session data can be very useful.

Purposed scenario can be as follows: Once user login, generate a token like JWT token, JWT is a well know way of claims management, JWT is divided into three parts: Meta data, claims and signature, you can claim whatever you want and sign the document using signature meta information (Hashing or encryption, technique, key ..etc), hence you can keep in cache one record per session, user ID and session token, token creation date, this can be very helpful, whenever a user tries to access a resource, check the validity of session token, if it’s not expired and fine, let’em go, otherwise: block him.

Simple and crisp but have many trade-offs, I can use your very same token using session hijacking either from other application or by MITM and reuse it simply, well; here is the trick, add as much identifiers as you think you are safe enough, add user IP, MAC address, user client , OS .. etc, whenever user claims is not matching caller real data: kick him off the session.

Sounds better, but it’s better to keep session time to avoid data exploit by physical access to user PC, most cache servers like Redis, supports setting record timeout, set token timeout for your agreed session timeout (make it 10 minutes for example), and keep the record creation date, as long as the request is within half the period, do nothing, once you past half the time, extend the record TTL to extra 10 minutes, if user did no action, Redis should take care of this, if you are not using Redis or singlestore, you can still handle this on your own with a little bit more effort.

In this case: Logout is a very simple action: expire the token record in the Cache server immediately.

In the next articles we will discuss more dimensions of securing you application.

Originally published at http://demystifyprogramming.wordpress.com on January 17, 2021.

--

--