FreeFungi

Challenges FF-001

Gobstopper CWE-89 A03:2021

Pick 'n' Mix

SQL injection in product search.

The Pick ’n’ Mix search is the first flaw in FreeFungi because it is the first flaw most people ever learn: a SQL injection caused by building a query out of string concatenation. You will read a customer’s password hash out of the database using nothing but the search box.

New to this? What is SQL injection?

A website talks to its database using queries written in a language called SQL. A SQL injection happens when text you type gets pasted into one of those queries instead of being treated as plain data.

That lets you change what the query does: read tables you were never meant to see, slip past a login, or dump the customer list. The fix is to keep your input and the query strictly apart, which is what a parameterised query does.

You need FreeFungi running locally first. If you have Docker, it is one line:

Start the shop
docker run -p 3000:3000 ghcr.io/bad-adventure/freefungi

1 · Find it

Open http://localhost:3000 and use the search box at the top of the shop. Search for fudge and you get the fudge. Now search for a single apostrophe:

'

The page breaks with a database error instead of an empty result. That error is the tell: your apostrophe reached the SQL engine as syntax, not as data. The query was built by pasting your text straight into it.

2 · Exploit it

The search runs something shaped like SELECT name, price FROM sweets WHERE name LIKE '%yourtext%'. Two columns come back. A UNION SELECT lets you append a second query with the same column count, and choose what fills those two columns.

Paste this into the search box:

' UNION SELECT email, password_hash FROM customers-- 

The results table now lists every customer’s email and hashed password where it expected sweet names and prices. The trailing -- (with a space) comments out the rest of the original query so it does not trip over the leftover %'.

Reward: one of those hashes belongs to owner@example.com, the shop owner. It is unsalted MD5, so any lookup cracks it in seconds. Take the password to /login, sign in, and the staff back office at /admin is yours. A search box that ends in an admin session.

3 · Understand it

Here is the actual handler. The problem is the whole query being assembled with a template string, so your input becomes code:

app.get('/api/sweets/search', (req, res) => {
  const q = req.query.q ?? '';
  // The query is built by gluing user input straight in:
  const sql = `SELECT name, price FROM sweets
               WHERE name LIKE '%${q}%'`;
  const rows = db.prepare(sql).all();
  res.json(rows);
});

Nothing distinguishes the data you typed from the SQL around it. The database receives one flat string and does exactly what it says.

4 · Fix it

The fix isn't escaping quotes. It's never letting your input be code in the first place. Use a parameterised query, where ? is a placeholder the driver binds safely:

✗ Vulnerable
const sql =
  `SELECT name, price FROM sweets
   WHERE name LIKE '%${q}%'`;
db.prepare(sql).all();
✓ Patched
const sql =
  `SELECT name, price FROM sweets
   WHERE name LIKE ?`;
db.prepare(sql).all(`%${q}%`);

Now the wildcards are part of the value bound to the placeholder, and the query structure is fixed before your input is ever seen. A UNION SELECT in the search box becomes a literal search for sweets called “' UNION SELECT...”, of which there are none.

The general rule: user input is data, never code. Parameterise every query. An ORM does this for you, as long as you steer clear of its raw-SQL escape hatch, which is where this class of bug usually sneaks back in.