A developer adds a line of JavaScript to a signup form so the ZIP code field accepts only five digits. Two days later a penetration tester puts <script> in that field, and the server stores it without complaint. The tester went around the form: they captured a normal submission in an intercepting proxy, changed the field and sent it on, and the check that was supposed to stop them only ever ran in the browser.
Many application security failures come down to a control placed where the attacker can skip it. On the CompTIA Security+ SY0-801 exam, application security sits under objective 4.1, which expects you to apply mitigating controls in a scenario. This lesson covers the four controls that objective names: input validation, secure cookies, static code analysis and code signing, plus enough about output encoding and dynamic testing to tell them apart. The attacks these controls stop, such as injection and cross-site scripting, are taught in Section 14.
Input validation belongs on the server, checked against an allow list
Input validation means checking that data matches what the application expects before the application uses it. Where the check runs decides whether it is a security control at all.
Client-side validation, done with HTML attributes such as pattern and maxlength or with JavaScript, gives users instant feedback. It provides no security, because the client belongs to the user. An attacker can turn off JavaScript, edit the page in developer tools, or skip the browser entirely and send requests with curl or a proxy. Every check that matters has to be repeated on the server, where the attacker can't change it.
The server-side check should be an allow list, also called positive validation: define exactly what is acceptable and reject everything else. That means checking type, length, range and format:
import re
ZIP = re.compile(r"[0-9]{5}")
QUANTITY = re.compile(r"[1-9][0-9]?") # 1 to 99, digits only
COUNTRIES = {"US", "CA", "MX"}
if not ZIP.fullmatch(form.get("zip", "")):
return error(400, "ZIP code must be five digits")
if not QUANTITY.fullmatch(form.get("quantity", "")):
return error(400, "Quantity must be 1 to 99")
if form.get("country") not in COUNTRIES:
return error(400, "Unsupported country")
Block-list validation, which tries to reject known-bad strings such as <script> or ' OR, loses to anyone who encodes the payload differently: URL encoding, double encoding, Unicode variants or a change of case. A related trap is "cleaning" input by deleting bad strings. Strip <script> once from <scr<script>ipt> and you have built the payload for the attacker. Rejecting bad input is safer than repairing it, and decoding input to its standard form before validating it closes the encoding gap.
Validate everything that crosses a trust boundary, not just visible form fields. Hidden fields, drop-down values, query strings, headers, cookies, JSON bodies, uploaded files and data returned by other systems can all be changed by an attacker. A drop-down that offers three countries guarantees nothing about what arrives at the server.
Validation and output encoding solve different problems
Validation decides whether input is acceptable on the way in. Output encoding makes data safe for the specific place it is written on the way out, so the interpreter there treats it as text and never as code.
The two aren't interchangeable, because a great deal of legitimate data contains characters that are dangerous somewhere. A surname such as O'Brien contains a quote. A support-ticket field has to accept < and > because customers paste error messages. A password field should accept nearly anything. Validation can't reject those characters without breaking the application, so the protection has to happen at output. Before writing a comment into an HTML page, the application encodes < as < and > as >, and the browser displays the text instead of running it. Encoding depends on context: HTML body, HTML attribute, JavaScript and URL each need different rules. For databases, the equivalent protection is the parameterized query, which keeps input out of the SQL structure entirely.
On the exam, a question about script being injected into a page that other users load points to output encoding as the primary control, with validation as a supporting layer. A question about data that doesn't fit its expected format, such as a negative quantity, letters in a numeric ID or a 4,000-character name, points to input validation.