Ravindra BagaleCourses & study guides Track your progress

Labs · Cyber Security

Lab: Fix-It: Turn an Unsafe SQL Query in a Small Training App into a Parameterised Query and Prove It Works

Intermediate35 minPython 3 · Flask · SQLite (built into Python) · Browser · Sample file search_app.py

Course: Cyber Security · Chapter 29: OWASP Top 10 Web Vulnerabilities

Chapter 29 explains the OWASP Top 10; this lab fixes the root cause of injection in real code.

Download search_app.py (training app, 40 lines)

Chala mitrano! SQL injection sounds scary, but the fix is one small habit: never paste user text into a SQL string, pass it as a parameter. Today a customer called O'Brien breaks our app, and that apostrophe teaches us everything. We find the bug, fix it in two lines, and prove it. Chala, developer banuya!

Suppose we are…

Suppose we are a junior developer at Freshworks. A support agent reports: "Customer search works for Asha and Rahul, but searching O'Brien gives an error page with SQL in it." That error is a warning sign: if one apostrophe can change the meaning of the query, an attacker can do the same on purpose. That is SQL injection. We fix the root cause in a small training app on our own laptop.

Goal of this lab

By the end you will be able to:

  • Run a small Flask app on 127.0.0.1 and reproduce the bug safely with a normal name.
  • Explain why string-building SQL is unsafe.
  • Fix it with a parameterised query (? placeholder), hide internal error details, and prove both.

What you need (all free)

  • Python 3 on your laptop (python --version on Windows, python3 --version on Mac/Linux).
  • A text editor (VS Code or Notepad). The training app below. About 35 minutes.

Download search_app.py

Safety and ethics

This app is a training copy that runs only on 127.0.0.1. Use the "O'Brien" test only on your own code. Typing quotes or SQL into other people's websites to "test" them is unauthorised testing.

Steps

  1. Put search_app.py in a new folder, open a terminal there and install Flask:

    python -m pip install flask
    

    (On Mac/Linux use python3 -m pip install flask; if pip complains about a managed environment, first run python3 -m venv venv and source venv/bin/activate.)

  2. Start the app:

    python search_app.py
    

    What you should see: Running on http://127.0.0.1:5000.

  3. In your browser open http://127.0.0.1:5000/search?name=Asha.

    What you should see: {"results":[{"city":"Pune","name":"Asha"}]}.

  4. Now search for a real customer with an apostrophe: http://127.0.0.1:5000/search?name=O'Brien

    What you should see: an error: near "Brien": syntax error, and the full SQL ... WHERE name = 'O'Brien'. The apostrophe ended the text early, so the rest was read as SQL code.

  5. Open search_app.py and find the bug (the line under # BUG):

    sql = f"SELECT name, city FROM customers WHERE name = '{name}'"
    rows = con.execute(sql).fetchall()
    
  6. Fix it with a placeholder. The database receives the SQL and the value separately, so the value can never become code:

    sql = "SELECT name, city FROM customers WHERE name = ?"
    rows = con.execute(sql, (name,)).fetchall()
    

    Note the comma in (name,): it makes a one-item tuple.

  7. Fix the second problem: the error response shows internal SQL. Replace the except block's return line with:

    app.logger.error("search failed: %s", e)
    return jsonify(error="Search failed. Please try again."), 500
    
  8. Stop the app (Ctrl + C), start it again and repeat step 4.

    What you should see: {"results":[{"city":"Bengaluru","name":"O'Brien"}]}. The name is treated purely as data.

  9. Repeat step 3 to confirm normal searches still work, and try a name that does not exist (?name=Zara): you get {"results":[]}, not an error.

  10. Search your own projects for the risky pattern:

    grep -rniE "f[\"'](SELECT|INSERT|UPDATE|DELETE)|[\"'](SELECT|INSERT|UPDATE|DELETE)[^\"']*[\"'] *(\+|%)" --include=*.py --exclude-dir=venv .
    

    What you should see: no match for your fixed search_app.py. Any match (an f-string, + or % building SQL) is a place to switch to placeholders.

Ravindra Bagale's Tip

Every language has the same fix: PHP PDO uses ? or :name, Java uses PreparedStatement, Node uses ? or $1. Escaping quotes by hand is not a fix; parameters are. And keep O'Brien in your test data forever. Ha test kadhi visru naka!

Common mistakes

Mistake What happens Fix
Writing (name) instead of (name,) Python passes a string, not a tuple, and you get a binding error Add the comma
Putting quotes around the placeholder '?' The query looks for the literal text ? Write = ? with no quotes
"Fixing" by removing apostrophes from input Real names like O'Brien and D'Souza break Use parameters, keep the data
Showing database errors to users Attackers learn table and column names Log details, show a generic message
Running Flask with debug=True on a network The debugger can run code Keep debug off and bind to 127.0.0.1

Self-check checklist

0 of 5 done

Try-at-home challenge

Add a second route /city?city=Pune that returns all customers in a city. Write it safely from the start, and test it with D'Souza Nagar as the city.

Check your answer

Use con.execute("SELECT name, city FROM customers WHERE city = ?", (city,)).fetchall() with the same try/except that logs errors and returns a generic message. ?city=D'Souza Nagar must return {"results":[]} (no such city), not an error.

Samjla ka? User text is data, never code: pass it as a parameter. Aata pudhe jaauya: Chapter 30 secures the AWS account itself.