Top 10 app security problems and how to protect yourself
You know your latest web application is inherently vulnerable to all kinds of attacks. You also know app security can never be perfect, but you can make it better tomorrow than it was yesterday.
The problem is that whether you’re using enterprise-grade (aka, expensive and complex) security tools, or have cobbled together a handful of open-source projects into a CI/CD pipeline or Git commit hooks and are hoping for the best, your toolkit can’t help you see:
- How your app could be vulnerable due to less-than-ideal programming practices, insecure dependencies, and beyond.
- Where the vulnerabilities are most likely hiding, down to single LOCs or entries in your
package.json
file. - Why you should fix certain vulnerabilities immediately and why others are lower priority.
Aikido exists to make app security relevant and efficient for developers who need to apply the right security fixes fast and get back to shipping code. Just like we do in our developer-centric security platform, we’re going to reduce the noise around common vulnerabilities and focus on three essential details:
- The TL;DR, which will teach you just enough to be afraid… and the right keywords to continue your educational search.
- A concise answer to the question, “Does this affect me?” with a clear yes or no (✅ or 🙅) and brief explanation.
- Quick tips in response to “How can I fix it?” that don’t involve expensive tools or costly refactors.
#1: SQL injection && NoSQL injection
TL;DR: This classic vulnerability is made possible by unsanitized and unvalidated user input, which allows attackers to run queries directly against your database. From there, they can extract data, modify records, or delete at will, completely negating any other app security measures you’ve put into place.
Does this affect me?
- ✅ if your app interacts with a SQL or NoSQL database at any point. Injection attacks have been around for decades, and automated attacks will immediately start to probe your endpoints with common exploits.
- 🙅 if you have no dynamic content based on database records. This could be because you’re entirely client-side, using a static site generator (SSG), or doing server-side rendering with a database but never accepting input from users.
How do I fix it? First and foremost, sanitize and validate all user input to eliminate unwanted characters or strings. Leverage open-source libraries and frameworks that allow for parameterized queries, and never concatenate user input into a database query. If you’re using Node.js, consider our open-source security engine Runtime, which autonomously projects you from SQL/NoSQL injection attacks and more.
#2: Cross-site scripting (XSS)
TL;DR: XSS is another injection attack that allows an attacker to send a malicious script to another, potentially gathering their authentication credentials or confidential data.
Does this affect me?
- ✅ if your app accept user input and outputs it elsewhere as dynamic content.
- 🙅 if you don’t accept user input at all.
How do I fix it? As with SQL/NoSQL injection attacks, you should validate user input when you include said input inside the href
attribute of anchor tags to ensure the protocol isn’t javascript
. Take care when using JavaScript methods like innerHTML
or React’s dangerouslySetInnerHTML
, which can arbitrarily execute any code embedded into the string during output. Regardless of your approach, sanitize HTML output with open-source sanitizers like DOMPurify to send only clean, non-executable HTML to your users.
#3: Server-side request forgery (SSRF)
TL;DR: SSRF attacks happen when a malicious actor abuses your app to interact with its underlying network, operating it like a proxy to jump to potentially more vulnerable or lucrative services.
Does this affect me?
- ✅ if your app interfaces with another service or API that performs a specific operation with user data—even if you’ve used allowlists to restrict traffic between only known and trusted endpoints.
- 🙅 if you’re truly static.
How do I fix it? While a regex to validate IP addresses or hostnames is an okay start, it’s usually prone to bypasses like octal encoding. Two more reliable solutions are to use an allowlist and your platform’s native URL parser to restrict input to only safe and known hosts, and disabling redirects in the fetch requests. Depending on your framework, you can also freely leverage open-source projects—like ssrf-req-filter for Node.js—to properly refuse any requests to internal hosts.
#4: Path traversal
TL;DR: This security flaw lets attackers access files and directories on your web server by reference files using ../
sequences or even absolute paths. Using sneaky tactics like double encoding, attackers can use framework-specific folder-file hierarchies or common filenames to find valuable information.
Does this affect me?
- ✅. Your app runs on a web server and includes references to the filesystem—no skirting around this one.
How do I fix it? Your first step is to remove any sensitive files, like any containing environment variables or secrets, from your web server’s root directory, and establish a process to prevent further slip-ups.
be to never store sensitive files, like those containing environment variables or secrets, in your web server’s root directory. Also, don’t store these files in any folder meant to be publicly accessible, like the /static
and /public
folders of a Next.js project. Finally, strip ../
path separators and their encoded variants from user input.
Runtime also works fantastically well for path traversal… just saying.
#5: XML eXternal Entity (XXE) injection
TL;DR: XXE attacks leverage a weakness in XML parsers that allows external entities, referenced by a document type definition (DTD), to be fetched and processed without validation or sanitization. The type and severity of the attack are limited mostly by the attacker’s skills and any OS-level security/permissions from your infrastructure provider.
Does this affect me?
- ✅ if you parse XML for any reason, including single sign-on authentication flows using SAML.
- 🙅 if you don’t have to deal with XML in your app!
How do I fix it? Disable external DTD resolving in your XML parser. You likely can’t refuse DTDs entirely, as it’s normal for some XML payloads to contain them—just don’t let your XML parser do anything with them.
#6: Deserialization
TL;DR: Attackers can send malicious data through a deserialization function built into your app (like unserialize()
from node-serialize) to execute code remotely, run a denial-of-service, or even create a reverse shell.
Does this affect me?
- ✅ if your app deserializes data directly from user interaction or through background functions/services like cookies, HTML forms, third-party APIs, caching, and more.
- 🙅 if you’re running a fully-static app with none of the above.
How do I fix it? In general, avoid deserializing user input (aka untrusted) data. If you must, only accept said data from authenticated and authorized users based on trusted signatures, certificates, and identity providers.
#7: Shell injection/command injection
TL;DR: Your app passes user input directly to the underlying shell of the OS on which your web server and app executes, allowing attackers to execute arbitrary commands or traverse the filesystem, often with sufficient privileges to extract data or pivot to another system.
Does this affect me?
- ✅ if your app interacts with the filesystem or shell directly, such as a UNIX command like
cat
. - 🙅 if you already use a framework API or method to safely pass arguments to the command you need to execute, or don’t need to interact with the filesystem/shell, such as in an SSG.
How do I fix it? Avoid accepting user input directly into commands or calling them directly. Instead, use your framework’s API/method, like child_process.execFile()
in Node.js, which lets you pass arguments in a list of strings. Even with that protection, always run your apps with the least privileges necessary for the required business logic to prevent an attacker from “escaping” the web server and accessing root
-only folders and files.
And yes, we’re back for one more friendly reminder to add Runtime to any Node.js project with one command (npm add @aikidosec/runtime || yarn install @aikidosec/runtime
) to instantly protect your app against common shell/command injection attacks.
#8: Local file inclusion (LFI)
TL;DR: LFI attacks involve tricking your app into exposing or running files on the system running your web server, which allows attackers to extract information or execute code remotely. While path traversal only allows attackers to read files, LFI attacks execute those files within your app, opening you up to a laundry list of app security vulnerabilities like remote code execution (RCE).
Does this affect me?
- ✅ if your app uses the path to a file as user input.
- 🙅 if your app doesn’t require users to supply paths to complete any action.
How do I fix it? Always sanitize user input to prevent the path traversal methods discussed above. If you must include files on the local filesystem beyond those typically found in “safe” /public
or /static
folders, use an allowlist file names and locations that your app is permitted to read and execute.
#9: Prototype pollution
TL;DR: This JavaScript-specific vulnerability lets an attacker manipulate your app’s global objects using __proto__
. The new object is then inherited across your app, potentially giving them access to confidential data or further escalating their privileges.
Does this affect me?
- ✅ if you’re using JavaScript.
- 🙅 if you’re using anything but JavaScript!
How do I fix it? Start by sanitizing keys from user input using allowlists or an open-source helper library. You can extend your protection by using Object.freeze()
to prevent changes to a prototype, or even using the --disable-proto=delete
flag offered with Node.js.
#10: Open redirects
TL;DR: In this common vector for phishing, attackers craft a custom URL like https://www.example.com/abc/def?&success=true&redirectURL=https://example.phishing.com
to trick your app into redirecting unsuspecting users to a malicious website. In addition, attackers can chain redirects together with other vulnerabilities for even more impact, leading to account takeovers and more.
Does this affect me?
- ✅ if your app redirects users to another page/view after completing an action, like sending them to
example.app/dashboard
after successful authentication. - 🙅 if you’re still living that static-generated life.
How do I fix it? First, remove parameter-based redirects from your app and replace them with fixed redirects based on an allowlist of trusted domains and paths to which you can redirect users after they take specific actions. This might slightly degrade the user experience, but it’s a meaningful compromise for better app security, not one that leaves them blaming you for the strange expenses on their credit card statement.
What’s next for your app security?
If you’re feeling overwhelmed by the scope of attacks and all the work required to protect against them, know you’re not alone.
No one expects you to solve all these security problems and possible vulnerabilities yourself. SQL injection attacks alone have existed for decades, and folks are still finding CVEs in sophisticated apps, frameworks, and libraries all the time. That’s not to say you should also take these security problems with a grain of salt—if your project meets the ✅ for any of these top 10 app security problems, you should start taking action.
First, sign up for Aikido to start focusing on the genuine threats to your app security. In two minutes, and for free, you can scan repositories and get relevant details plus guided remediations for the most critical vulnerabilities based on specific architecture of your app and what features, functions, and helper libraries you’ve implemented. With Aikido, you’ll reduce scope to what matters and implement smart fixes faster, and get informed instantly of new security problems introduced in your latest commits.
Next, add Runtime, our open-source embedded security engine, to your Node.js apps. Runtime instantly and autonomously protects your apps against various injection, prototype pollution, and path traversal attacks by blocking them at the server-level, but without the cost and complexity of web application firewalls or Agent-based application security management platforms. Runtime gives you confidence your app and users are safe from these common security problems, but can also feed real-time data back to Aikido to give you visibility into current attack vectors to help you prioritize fixes.
Now you’re off on the right foot, with a clearer picture as to:
- How your app is vulnerable in more ways than you might have once thought.
- Where you should focus your time and attention to fix the most critical issues first.
- Why app security and vulnerability scanning isn’t a one-time effort, but a continuous process—one made a whole lot easier with Aikido.