We continue to post the series of articles about AppSec Manifesto by Alex Tatulchenkov, Senior Software Developer at Intetics. Here is the next part.
This is another regarding the adoption of the AppSec Manifesto. In previous articles, we looked into how the first two rules (Absolute Zero and The Lord of the Sinks) can be adopted in PHP applications. These rules don’t rely on the previous two (actually all of them may be used independently) and even can be implemented separately but due to the proposed solution, it makes sense to clarify them together.
RULE #2
Least (Computational) Power Principle
Access to computational power is a privilege
This rule is a compilation of the Principle of least privilege and Fail-fast approach. If at some point it becomes obvious that input data doesn’t conform to our domain then we should stop immediately and do not transfer invalid data across the application. This is very similar to the Rule of Repair and is widely used Design by contract
- fail immediately instead of postponing the failure
- don’t work around the failure
This is absolutely obvious if we are talking about the malicious input that is the result of an attack but if invalid input is caused by a legitimate user’s mistake then we come to the well-known trade-off between the security and the UX. This will be additionally covered during clarification of the Rule #4.
In general, prefer Fail-fast
approach over Forgive
approach.
In this context, it’s worth mentioning the 6th commandment from The Ten Commandments for C Programmers.
RULE #3
Forget-me-not
Don’t forget information regarding the validity of a certain input
If you’ve validated some input once there is no sense to validate it again, just don’t lose that knowledge. How can we “forget” obtained knowledge? e.g.
public function foo(string $input) { if (isValid($input)) { $this->bar($input); } }public function bar(string $input) { ... }
In the example above method foo() obtains knowledge regarding the validity of $input but immediately loses it during the bar() method call. Inside bar() you can not be certain that $input is valid. So you should rely on the validation inside the foo() and pray for nobody will call bar() directly or duplicate validation introducing a Shotgun parsing problem.
There are two methods to ensure that provided input conforms to our domain rules: parsing and validation.
Parsing: A parser is a software component that takes input data (frequently text) and builds a data structure
Validation: Validation is a process that uses routines, often called “validation rules”, “validation constraints”, or “check routines”, that check for correctness, meaningfulness, and security of data that are input to the system.
Parsing
<?php /** * creates instance of the custom type DirectoryName from * the user input */$directoryName = DirectoryName::fromString($user_input);
Validation
<?php /** * returns boolean answer whether user input valid or not */$isValid = DirectoryValidator::validate($user_input);
Both rules (Rule #2 and Rule #3) can be satisfied by the adoption of TypeDD.
Let’s see how we can do it in PHP. In the heart of the TypeDD is Parse, don’t validate principle. To implement custom data types we’ll use ValueObjects.
<?phpnamespace app\types;use Assert\Assertion; final class DirectoryName { private $name; private function __construct() { } public static function fromString(string $name): self { Assertion::betweenLength($name, 1, 127); Assertion::regex($name, '/^[a-z0-9\-_]*$/i', sprintf('Invalid Directory Name "%s" (a-z, 0-9, dash and underscore allowed)', $name)); $directoryName = new self(); $directoryName->name = $name; return $directoryName; } public function value(): string { return $this->name; } }
Using such ValueObject we can mitigate Path Traversal and Null byte injections in filesystem-related operations.
First of all — create an instance of the ValueObject as close to the Source as possible.
Source: The program point that reads external resource (user-input or any other data that can be manipulated by a potential attacker).
After you have an instance of the ValueObject you pass it across the application:
<?php function readDirectory(DirectoryName $dir) { ... }
readDirectory() doesn’t validate $dir instead it relies on the knowledge obtained by the system previously. In addition, by extracting all parsing logic into ValueObjects we follow DRY principle.
The benefits of such an approach are well described in books related Domain-Driven Design where value objects are heavily used.
Although AppSec Manifesto uses ValueObject term its ValueObjects are much more sophisticated than ones described in Wikipedia and more similar to domain primitives idea extended to other layers (not only domain level but application and infrastructure levels as well). So in fact it’s a custom datatype, often with a custom operators represented by its methods and follows Information Expert pattern from GRASP.
Lets recap:
Parse
, don’tvalidate
input data as close to thesource
as possible- Use Always valid
ValueObjects
(force invariants) - Do not transfer malicious data from
source
tosink
Instantiate once, use everywhere