1
0
mirror of https://github.com/danog/psalm.git synced 2024-12-02 17:52:45 +01:00
psalm/docs/security_analysis/avoiding_false_positives.md

232 lines
5.3 KiB
Markdown
Raw Normal View History

2020-06-19 17:56:04 +02:00
# Avoiding false-positives
When you run Psalm's taint analysis for the first time you may see a bunch of false-positives.
2020-06-19 17:56:04 +02:00
Nobody likes false-positives!
There are a number of ways you can prevent them:
## Escaping tainted input
2020-06-19 17:57:34 +02:00
2020-06-19 18:12:28 +02:00
Some operations remove taints from data for example, wrapping `$_GET['name']` in an `htmlentities` call prevents cross-site-scripting attacks in that `$_GET` call.
2020-06-19 17:57:34 +02:00
2020-06-21 17:43:08 +02:00
Psalm allows you to remove taints via a `@psalm-taint-escape <taint-type>` annotation:
2020-06-19 17:57:34 +02:00
```php
<?php
2020-06-19 17:57:34 +02:00
function echoVar(string $str) : void {
/**
2020-06-21 17:43:08 +02:00
* @psalm-taint-escape html
2020-06-19 17:57:34 +02:00
*/
$str = str_replace(['<', '>'], '', $str);
2020-06-19 17:57:34 +02:00
echo $str;
}
echoVar($_GET["text"]);
```
## Conditionally escaping tainted input
A slightly modified version of the previous example is using a condition to determine whether the return value
is considered secure. Only in case function argument `$escape` is true, the corresponding annotation
`@psalm-taint-escape` is applied for taint type `html` .
```php
<?php
/**
* @param string $str
* @param bool $escape
* @psalm-taint-escape ($escape is true ? 'html' : null)
*/
function processVar(string $str, bool $escape = true) : string {
if ($escape) {
$str = str_replace(['<', '>'], '', $str);
}
return $str;
}
echo processVar($_GET['text'], false); // detects tainted HTML
echo processVar($_GET['text'], true); // considered secure
```
## Sanitizing HTML user input
Whenever possible, applications should be designed to accept & store user input as discrete text fields, rather than blocks of HTML. This allows user input to be fully escaped via `htmlspecialchars` or `htmlentities`. In cases where HTML user input is required (e.g. rich text editors like [TinyMCE](https://www.tiny.cloud/)), a library designed specifically to filter out risky HTML is highly recommended. For example, [HTML Purifier](http://htmlpurifier.org/docs) could be used as follows:
```php
<?php
/**
* @psalm-taint-escape html
* @psalm-taint-escape has_quotes
*/
function sanitizeHTML($html){
$purifier = new HTMLPurifier();
return $purifier->purify($html);
}
```
2020-06-19 17:56:04 +02:00
## Specializing taints in functions
For functions, methods and classes you can use the `@psalm-taint-specialize` annotation.
```php
<?php
2020-06-19 17:56:04 +02:00
function takesInput(string $s) : string {
return $s;
}
echo htmlentities(takesInput($_GET["name"]));
echo takesInput("hello"); // Psalm detects tainted HTML here
```
Adding a `@psalm-taint-specialize` annotation solves the problem, by telling Psalm that each invocation of the function should be treated separately.
```php
<?php
2020-06-19 17:56:04 +02:00
/**
* @psalm-taint-specialize
*/
function takesInput(string $s) : string {
return $s;
}
echo htmlentities(takesInput($_GET["name"]));
echo takesInput("hello"); // No error
```
A specialized function or method will still track tainted input:
```php
<?php
2020-06-19 17:56:04 +02:00
/**
* @psalm-taint-specialize
*/
function takesInput(string $s) : string {
return $s;
}
echo takesInput($_GET["name"]); // Psalm detects tainted input
echo takesInput("hello"); // No error
```
Here were telling Psalm that a functions taintedness is wholly dependent on the input to the function.
2020-06-19 17:56:04 +02:00
If you're familiar with [immutability in Psalm](https://psalm.dev/articles/immutability-and-beyond) then this general idea should be familiar, since a pure function is one where the output is wholly dependent on its input. Unsurprisingly, all functions marked `@psalm-pure` _also_ specialize the taintedness of their output based on input:
```php
<?php
2020-06-19 17:56:04 +02:00
/**
* @psalm-pure
*/
function takesInput(string $s) : string {
return $s;
}
echo htmlentities(takesInput($_GET["name"]));
echo takesInput("hello"); // No error
```
## Specializing taints in classes
Just as taints can be specialized in function calls, tainted properties can also be specialized to a given class.
```php
<?php
2020-06-19 17:56:04 +02:00
class User {
public string $name;
public function __construct(string $name) {
$this->name = $name;
}
}
/**
* @psalm-taint-specialize
*/
function echoUserName(User $user) {
echo $user->name; // Error, detected tainted input
}
$user1 = new User("Keith");
$user2 = new User($_GET["name"]);
echoUserName($user1);
```
Adding `@psalm-taint-specialize` to the class fixes the issue.
```php
<?php
2020-06-19 17:56:04 +02:00
/**
* @psalm-taint-specialize
*/
class User {
public string $name;
public function __construct(string $name) {
$this->name = $name;
}
}
/**
* @psalm-taint-specialize
*/
function echoUserName(User $user) {
echo $user->name; // No error
}
$user1 = new User("Keith");
$user2 = new User($_GET["name"]);
echoUserName($user1);
```
And, because its form of purity enforcement, `@psalm-immutable` can also be used:
```php
<?php
2020-06-19 17:56:04 +02:00
/**
* @psalm-immutable
*/
class User {
public string $name;
public function __construct(string $name) {
$this->name = $name;
}
}
/**
* @psalm-taint-specialize
*/
function echoUserName(User $user) {
echo $user->name; // No error
}
$user1 = new User("Keith");
$user2 = new User($_GET["name"]);
echoUserName($user1);
```
## Avoiding files in taint paths
You can also tell Psalm that youre not interested in any taint paths that flow through certain files or directories by specifying them in your Psalm config:
```xml
<taintAnalysis>
<ignoreFiles>
<directory name="tests"/>
</ignoreFiles>
</taintAnalysis>
```