Input validation checks data before use
Input validation is checking that data has the shape a program expects before the program uses it. It matters because every box a visitor can type into is a way to hand the program something it never planned for.
A sign-up form asks for an age. One visitor types -5. Another types forty thousand letters. A program that never checked will try to use both.
By the end of this lesson
Note
I can check input against clear rules before the program trusts it.
Key words
| Word | What it means | Example |
|---|---|---|
| Validation | checking that input has the shape the program expects | |
| Allow list | a list of the only values or shapes that are accepted | GB, FR, DE |
| Deny list | a list of known bad values that are refused | |
| Range | the smallest and largest value allowed | 13 to 120 |
| Server-side check | a check on the server, which the visitor cannot skip | |
| Reject | to refuse input instead of quietly changing it |
The cells students fill in are left blank in this preview.
Check the type, the length and the range
Most input has a shape. An age is a whole number. A username is short text. A quantity is a small number above zero.
Check three things. The type: is it digits when it should be? The length: is it short enough? The range: is the number between the smallest and largest value you allow? Refuse anything that fails any of the three.
Worked example: rules for a sign-up form
| Field | Rule | Accepted | Refused |
|---|---|---|---|
| Username | 3 to 20 letters, digits or _ | maria_22 | ma, a b! |
| Age | Whole number from 13 to 120 | 19 | 12, 19.5, abc |
| Country | One of GB, FR, DE, JP, ES | GB | UK, empty |
| Quantity | Digits only, 1 to 10 | 4 | 0, 11, -2 |
The cells students fill in are left blank in this preview.
A quantity check
# Change text to "12", then "-3", then "x", then "" and press Run each time.
text = "7"
if text.isdigit() and 1 <= int(text) <= 10:
print("accepted")
else:
print("refused")
Allow lists beat deny lists
A deny list names the bad input and accepts the rest. It fails the day an attacker finds a bad input you did not name.
An allow list names the good input and refuses the rest. A country code must be one of five codes. Anything else is refused, however clever it is. Use an allow list wherever the good values can be listed.
Worked example: a deny list leaks
| Typed text | Deny list of <script> | Allow list of letters |
|---|---|---|
| maria | Accepted | Accepted |
| <script> | Refused | Refused |
| <SCRIPT> | Accepted by mistake | Refused |
| <img src=x> | Accepted by mistake | Refused |
The cells students fill in are left blank in this preview.
Two lists, one input
# Change country to "GB", then to "<script>", and press Run.
country = "<b>"
deny_list = ["<script>", "<iframe>"]
allow_list = ["GB", "FR", "DE", "JP", "ES"]
print("deny list accepts:", country not in deny_list)
print("allow list accepts:", country in allow_list)
Validate on the server and reject bad input
A form can check input in the visitor's browser. That helps honest visitors. It protects nothing, because an attacker can send the request without using your page at all.
The server-side check is the one that counts. When input fails it, reject it with a short message. Do not quietly repair it. A repaired value can turn out to be a new valid attack.
A common mistake
Warning
Many people think a check in the browser is enough, such as a maximum length on the box. It is not. An attacker skips the page and sends the request directly. Keep the browser check for kindness, and repeat every rule on the server.
See what the shop checks
Your lab shop is a search page that takes one word. The next two lines send it harmless requests. The first sends no word at all and counts the lines that come back. The second sends a word a hundred letters long and prints only the status number.
A search with no word at all
curl -s http://app:8000/search | wc -lShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A search with a very long word
curl -s -o /dev/null -w "%{http_code}\n" "http://app:8000/search?q=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
What the shop does not check
The first line prints 5: with no word, the shop searches for nothing, and all five public products match. The second prints 200: it accepted a hundred letters without comment.
Neither is an attack. Both show a shop with no rules about presence or length. Ask what rules the search box should have.