Twelve findings, six names
A firm tests a client's website and writes up twelve findings. They look unrelated: an error page, a strange link, a file name in an address.
A good tester reads the list and sees six families. Each family has one cause and one fix. This lesson teaches you to sort findings into families.
The one rule
Warning
This lab is a website that is insecure on purpose. Attack your own private copy and nothing else. It has no way out to the internet. Never try these things on a site that is not yours: in the UK that is a crime under the Computer Misuse Act 1990.
By the end of this lesson
Note
I can match a vulnerable page to the flaw class it shows.
Key words
| Word | What it means | Example |
|---|---|---|
| Flaw class | a family of weaknesses that share one cause | |
| Injection | typed text read as part of a command or query | |
| Path traversal | a file name that climbs out of the folder it should stay in | ../ |
| Open redirect | a link that sends visitors to any address it is given | |
| Input | anything a visitor types or sends to a page | |
| Sink | the place in a program where a value is finally used |
The cells students fill in are left blank in this preview.
Follow the input to its sink
Most flaws share one pattern. A value comes in from a visitor. It travels through the program. It arrives at a sink, where it is used.
The class of flaw depends on the sink. Ask one question of any page: where does what I type end up?
Worked example: sink and class
| Where the value ends up | Flaw class | The fix in one line |
|---|---|---|
| Inside a database query | SQL injection | Send it as a parameter |
| Inside the page the browser draws | Cross-site scripting | Escape it on output |
| Inside a file name the server opens | Path traversal | Allow only names from a list |
| Inside a command the server runs | Command injection | Never build commands from text |
| Inside an address the visitor is sent to | Open redirect | Allow only your own addresses |
| As an id with no owner check | Broken access control | Check the owner every time |
The cells students fill in are left blank in this preview.
Each class leaves a sign in a request
You can often tell the class from a single request. A file name with ../ in it points at path traversal. A tag in a value points at XSS. A redirect= value holding a full address points at an open redirect.
You do not need to run anything to classify. You read the request and ask what the value is for.
Worked example: five invented requests
| Request | Class |
|---|---|
| /view?file=../../etc/hosts | Path traversal |
| /greet?name=<i>Bo</i> | Cross-site scripting |
| /go?redirect=http://other.example | Open redirect |
| /lookup?id=5 UNION SELECT 1 | SQL injection |
| /tools?host=a.example; cat notes | Command injection |
The cells students fill in are left blank in this preview.
Look at the lab's website
Your lab runs Mutillidae, a website with many flaws on purpose. Start it, then run these three in order. The first signs in with the lab's own account and keeps the sign-in in a file. The second looks up that one account by its name and password. The third looks up a name the site does not hold. All three are ordinary use.
Sign in as the lab's own account
curl -s -c jar -d "username=admin&password=adminpass&login-php-submit-button=Login" -o /dev/null -w "%{http_code} %{redirect_url}\n" "http://www/index.php?page=login.php"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Look up one account
curl -s -b jar -c jar -G "http://www/index.php" --data-urlencode "page=user-info.php" --data-urlencode "username=admin" --data-urlencode "password=adminpass" --data-urlencode "user-info-php-submit-button=View Account Details" | grep -o "[0-9]* records found"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Look up a name that is not there
curl -s -b jar -c jar -G "http://www/index.php" --data-urlencode "page=user-info.php" --data-urlencode "username=nobody" --data-urlencode "password=nothing" --data-urlencode "user-info-php-submit-button=View Account Details" | grep -o "[0-9]* records found"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
What the three answers tell you
The first prints 302 and an address ending AU1, so the site accepted the sign-in. The second prints 1 records found. The third prints 0 records found.
So the page counts the rows its query returns. That makes it a sink: the name you type goes into a database query. Which class would you test it for? Check your answer against the table above.
A common mistake
Warning
Many people think one list of bad words can stop every class. It cannot. Each class has a different sink, so each needs the right fix at its own sink. A filter for script tags does nothing for a file name. Fix each sink the way its class needs.
One cause sits under most classes
Injection, XSS, traversal and the rest share one cause. A program mixes data with instructions, and the next layer cannot tell them apart.
The cure is the same idea each time. Keep data in its own lane: parameters, escaping, allowed lists. Mutillidae has security levels 0, 1 and 5, so you can see the same page with and without the cure.
Going further: a disguised traversal
An attacker can write each dot as %2e, so ../ becomes %2e%2e/. The rules in the assignment miss /view?file=%2e%2e/secret and call it normal.
Once your assignment passes, copy classify into a code block of your own. Add the one swap that catches it. Then explain why a real server decodes the whole address once, before any check.