Skip to contentExploitQuest

Lesson 1 of 1 in What Your Errors and Uploads Give Away

Errors, and Files You Did Not Name

What the user sees against what the log holds — and why a filename from a browser is input, not a name.

3 min read

Not yet reviewed

Two places give away more than anybody intends: the error you return when something goes wrong, and the file you accept when somebody uploads one. Both are cases of trusting a value that came from outside.

Two audiences, one event

What the user gets

That did not work. Check the address and try again.

What the log gets

lookup failed  user=91f2…  table=members
driver=23505  constraint=members_email_idx
stack=…

The same event, described twice. The user needs to know what to do; the log needs to know what happened. Sending the second to the first is how a stack trace ends up telling somebody your framework, your file layout and your table names.

The rule that makes this easy: an error carries a message for the person and a cause for the log, and the transport only ever renders the first. Then leaking becomes something you would have to do deliberately rather than something you forget not to do.

Note

Error messages should say what went wrong and what to do about it. No apologies, no vagueness for its own sake, and nothing that names an internal. "Check the address" is more useful than "an error occurred" and leaks less than "no such row in members".

The difference an error can reveal

A login that says "no such user" for one address and "wrong password" for another has told an attacker which addresses have accounts. The fix is one message for both, and it has to be the same in timing as well as wording — an endpoint that returns instantly for unknown users and slowly for known ones is saying the same thing in a different language.

A filename is not a name

An uploaded filename is a string chosen by whoever uploaded it. It is not a name, it is input, and it arrives with every property input has.

../../../etc/cron.d/backdoor
avatar.php
avatar.jpg.php
avatar.jpg%00.php
  1. Line 1Path traversal. Never join an uploaded name to a directory. Generate your own name and store the original as a label if you need it.
  2. Line 2Extension chosen by the uploader. If the directory can execute, you have accepted a program.
  3. Line 3Defeats a naive "ends with .jpg" check on servers that dispatch on the last extension, and several that dispatch on any of them.
  4. Line 4The null byte trick. Old, mostly dead, and it still appears whenever somebody validates a string and passes it to something written in C.

Take care

Do not serve uploads from a directory that can execute anything, and do not trust the content type the browser sent — it is a header, chosen by the client. Check the actual bytes if the type matters, generate your own filename, and cap the size before you read it rather than after.

Rate limiting is a security control

A login endpoint with no limit is an offline cracking exercise conducted online. Limit on both axes — per account, so one account cannot be ground down, and per source, so one client cannot grind down every account — and keep the limiter's own state somewhere an attacker cannot reset by reconnecting.

Your upload handler rejects anything not ending in `.jpg`, stores it under the user's chosen filename, and serves it from `/uploads`. What is the most serious problem?