SQL Injection, XSS and CSRF: Security in the Frontend and Backend
Published on October 26, 2025
SQL Injection, XSS and CSRF: Security in the Frontend and Backend
Web security isn’t a “patch” you add at the end. It’s something you must consider from day one of development. Three of the most common and dangerous attacks on web applications are SQL injection, XSS (Cross-Site Scripting), and CSRF (Cross-Site Request Forgery).
These attacks can compromise sensitive data, steal user sessions, and in extreme cases, take complete control of your application. Understanding how they work is the first step to preventing them.
In this article, I’ll explain how these attacks actually work, what vulnerable code looks like, and how modern tools and best practices prevent them. I won’t include explicit exploit code, but I will explain the mechanisms so you can protect your applications.
Part I: SQL Injection - The Autopsy
SQL injection is one of the oldest and most dangerous attacks on web applications. It occurs when an attacker can inject malicious SQL code into a database query.
How does SQL injection work?
SQL injection works by exploiting how applications build SQL queries. When an application concatenates user input directly into an SQL query without validation or sanitization, an attacker can manipulate that query.
Imagine an application that searches for users by name. If the code builds the query by concatenating user input directly, an attacker could enter something like: admin' OR '1'='1 in the search field. If the query is built as SELECT * FROM users WHERE name = 'admin' OR '1'='1', the condition '1'='1' is always true, meaning the query would return all users, not just the one being searched.
Types of SQL injection
Classic SQL injection: The attacker manipulates the query to change its logic. They can make a query that should return one result return all results, or make a query that should authenticate a user authenticate anyone.
UNION-based SQL injection: The attacker uses SQL’s UNION clause to combine results from multiple queries. This allows extracting data from other tables that shouldn’t be accessible.
Blind SQL injection: When the application doesn’t show SQL errors directly, the attacker can use “trial and error” techniques to extract information. For example, they can check whether a condition is true or false based on response time or application behavior.
Time-based SQL injection: The attacker uses functions that cause delays (like SLEEP in MySQL) to determine if a condition is true. If the application takes longer to respond, they know the condition is true.
Why is it dangerous?
SQL injection can have devastating consequences:
-
Unauthorized access: An attacker can authenticate as any user, including administrators.
-
Data leakage: They can extract sensitive information from the database: passwords, personal information, financial data.
-
Data modification: They can modify or delete critical data.
-
Code execution: In some cases, they can execute operating system commands on the server.
What vulnerable code looks like
In vulnerable code, you’d see something like building SQL queries by concatenating strings directly with user input. This is extremely dangerous because the attacker can manipulate the query structure.
The typical vulnerability occurs when developers don’t use prepared parameters or don’t validate user input before including it in SQL queries.
How modern ORMs prevent it
Modern ORMs (Object-Relational Mappers) like TypeORM, Prisma, Sequelize, and others prevent SQL injection in several ways:
Prepared Statements: ORMs use prepared statements automatically. Instead of concatenating values directly into the query, ORMs separate the query structure from the values. The database compiles the query structure first, then applies the values. This means that even if an attacker tries to inject SQL code, the database will treat it as a literal value, not as executable code.
Type validation: ORMs validate data types before executing queries. If you expect a number and receive text, the ORM will reject the query before it reaches the database.
Automatic escaping: ORMs automatically escape special characters that could be interpreted as SQL code.
Query abstraction: ORMs provide high-level methods for building queries. Instead of writing raw SQL, you use ORM methods that build safe queries automatically.
Best practices to prevent SQL injection
-
Always use ORMs or prepared statements: Never build SQL queries by concatenating strings with user input.
-
Input validation: Validate and sanitize all user input before using it in queries.
-
Principle of least privilege: Database connections should use users with the minimum privileges necessary. If an application only needs to read data, it shouldn’t have write permissions.
-
Code review: Regularly review code for vulnerable patterns. Any place where SQL is built dynamically is a point of attention.
-
Monitoring and logging: Monitor for SQL injection attempts. Suspicious patterns in queries can indicate attack attempts.
Part II: XSS and CSRF - Stealing Sessions in the Browser
While SQL injection attacks the backend, XSS and CSRF mainly attack the frontend and the interaction between the browser and the server.
XSS (Cross-Site Scripting)
XSS is an attack where an attacker injects malicious JavaScript code into a web application that then runs in other users’ browsers.
How does XSS work?
XSS works when a web application includes user input in its HTML without properly sanitizing it. If an attacker can make the application include malicious JavaScript code, that code will run in the browser of any user who visits the page.
Imagine a forum where users can post comments. If an attacker posts a comment that includes JavaScript code, and the application displays that comment without sanitizing it, the code will run in the browser of all users who see that comment.
Types of XSS
Reflected XSS: The malicious code is reflected immediately in the server response. For example, if an application displays an error message that includes unsanitized user input, an attacker could create a link that includes JavaScript code. When the victim clicks the link, the code runs.
Stored XSS: The malicious code is stored in the database and runs every time it’s displayed. For example, if an attacker posts a comment with JavaScript code, that code will run every time someone views that comment.
DOM-based XSS: The malicious code runs entirely on the client, manipulating the browser’s DOM. It doesn’t require the server to include the malicious code in its response.
Why is it dangerous?
XSS can have serious consequences:
-
Session theft: Malicious JavaScript can access session cookies and send them to the attacker. With session cookies, the attacker can impersonate the user.
-
Content manipulation: It can modify page content to deceive users, for example, changing forms to send data to the attacker.
-
Keylogging: It can record the keys the user presses, capturing passwords and other sensitive information.
-
Malicious redirects: It can redirect users to malicious sites.
What vulnerable code looks like
In vulnerable code, you’d see user input being included directly in HTML without sanitization. For example, if an application takes a user’s name from the database and displays it directly in the HTML, an attacker could register with a name that includes JavaScript code.
How to prevent XSS
Input sanitization: All user input must be sanitized before display. This means escaping special HTML characters like <, >, &, ", and '.
Content Security Policy (CSP): CSP is a security mechanism that lets you specify which content sources are allowed. You can use CSP to prevent inline JavaScript execution and restrict where JavaScript can be loaded from.
HttpOnly cookies: Session cookies should be marked as HttpOnly. This prevents JavaScript from accessing them, even if there’s an XSS.
Input validation: Validate and sanitize all user input on both frontend and backend.
Modern frameworks: Modern frameworks like React, Vue, and Angular automatically escape content, preventing many types of XSS. However, you must be careful with dangerouslySetInnerHTML in React or v-html in Vue, which can be dangerous if not used correctly.
CSRF (Cross-Site Request Forgery)
CSRF is an attack where a malicious website causes an authenticated user’s browser to send an unwanted request to another web application where the user is authenticated.
How does CSRF work?
CSRF works by exploiting the trust an application has in the user’s browser. When a user is authenticated in an application, the browser automatically sends session cookies with every request to that application.
Imagine a user is authenticated on their online bank. If they visit a malicious website while having the bank session open, the malicious site could include code that makes the browser send a request to the bank to transfer money. Since the browser automatically sends session cookies, the bank would think it’s a legitimate request from the user.
Why is it dangerous?
CSRF can have serious consequences:
-
Unauthorized actions: An attacker can make an authenticated user perform actions without their knowledge: bank transfers, password changes, purchases, etc.
-
Data modification: It can modify user data without their consent.
-
Chained attacks: It can be combined with other attacks to increase effectiveness.
What vulnerable code looks like
In vulnerable code, you’d see applications that accept state-changing requests (POST, PUT, DELETE) without verifying that the request actually comes from the user and not from a malicious site.
How to prevent CSRF
CSRF Tokens: The most common way to prevent CSRF is to use CSRF tokens. The application generates a unique token for each session and includes it in forms. When the form is submitted, the application verifies the token is valid. Since malicious sites can’t access this token (due to the browser’s same-origin policy), they can’t create valid requests.
SameSite cookies: Modern browsers support the SameSite attribute on cookies. When a cookie has SameSite=Strict, the browser only sends it on same-site requests. This prevents it from being sent on requests from malicious sites.
Origin verification: Verify that the request’s Origin or Referer header matches your domain. This helps detect requests coming from other sites.
Double Submit Cookie: Similar to CSRF tokens, but the token is stored in both a cookie and the form. The application verifies both values match.
Re-authentication for critical actions: For especially sensitive actions (like bank transfers), require the user to authenticate again.
The importance of layered security
Effective security doesn’t depend on a single measure, but on multiple layers of protection:
-
Input validation: Validate and sanitize all input on both frontend and backend.
-
Prepared parameters: Use ORMs or prepared statements for all database queries.
-
Output sanitization: Escape or sanitize all output before displaying it to users.
-
Secure cookies: Use HttpOnly, Secure, and SameSite cookies to protect sessions.
-
HTTPS: Always use HTTPS in production to encrypt traffic between client and server.
-
Security headers: Use security headers like Content-Security-Policy, X-Frame-Options, and X-Content-Type-Options.
-
Principle of least privilege: Give users and processes only the minimum permissions necessary.
-
Monitoring and logging: Monitor for attack attempts and log security events for analysis.
My personal perspective
Web security isn’t something you add at the end of development. It’s something you must consider from day one. I’ve seen projects that worked perfectly but were vulnerable to these basic attacks. Projects that lost data, were compromised, and had to be rewritten from scratch due to security issues.
Understanding how these attacks work isn’t just for security experts. It’s fundamental knowledge every developer must have. Because at the end of the day, if you don’t understand how these attacks work, you can’t prevent them effectively.
Modern ORMs and frontend frameworks have done a lot to prevent these attacks automatically. But they’re not a magic solution. You still need to understand the fundamental principles, validate input, sanitize output, and follow best practices.
Security is a continuous process, not a destination. It requires constant vigilance, dependency updates, and regular code review. But with the right knowledge and proper tools, you can build applications that are resilient to these common attacks.
At the end of the day, security isn’t about making your application impenetrable (that’s impossible). It’s about making it hard enough to attack that attackers look for easier targets. And understanding how these attacks work is the first step to achieving that.