1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Add dedicated types for 'file', 'header' and 'cookie' (#4630)

* [WIP] Add dedicated sinks for 'file', 'header' and 'cookie'

* Add documentation

* Add mapping for taint flows

* Add tests

* Fix test
This commit is contained in:
Lukas Reschke 2020-11-19 23:47:29 +01:00 committed by GitHub
parent 70c9fd97c7
commit 78f4a0691c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 207 additions and 5 deletions

View File

@ -371,8 +371,11 @@
<xs:element name="ReferenceConstraintViolation" type="IssueHandlerType" minOccurs="0" /> <xs:element name="ReferenceConstraintViolation" type="IssueHandlerType" minOccurs="0" />
<xs:element name="ReservedWord" type="IssueHandlerType" minOccurs="0" /> <xs:element name="ReservedWord" type="IssueHandlerType" minOccurs="0" />
<xs:element name="StringIncrement" type="IssueHandlerType" minOccurs="0" /> <xs:element name="StringIncrement" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedCookie" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedCustom" type="IssueHandlerType" minOccurs="0" /> <xs:element name="TaintedCustom" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedEval" type="IssueHandlerType" minOccurs="0" /> <xs:element name="TaintedEval" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedFile" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedHeader" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedHtml" type="IssueHandlerType" minOccurs="0" /> <xs:element name="TaintedHtml" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedInclude" type="IssueHandlerType" minOccurs="0" /> <xs:element name="TaintedInclude" type="IssueHandlerType" minOccurs="0" />
<xs:element name="TaintedInput" type="IssueHandlerType" minOccurs="0" /> <xs:element name="TaintedInput" type="IssueHandlerType" minOccurs="0" />

View File

@ -5,10 +5,24 @@
return [ return [
'exec' => [['shell']], 'exec' => [['shell']],
'create_function' => [['text'], ['eval']], 'create_function' => [['text'], ['eval']],
'file_get_contents' => [['text']], 'file_get_contents' => [['file']],
'file_put_contents' => [['shell']], 'file_put_contents' => [['file']],
'fopen' => [['shell']], 'fopen' => [['file']],
'header' => [['text']], 'unlink' => [['file']],
'copy' => [['file'], ['file']],
'file' => [['file']],
'link' => [['file'], ['file']],
'mkdir' => [['file']],
'move_uploaded_file' => [['file'], ['file']],
'parse_ini_file' => [['file']],
'chown' => [['file']],
'lchown' => [['file']],
'readfile' => [['file']],
'rename' => [['file'], ['file']],
'rmdir' => ['file'],
'header' => [['header']],
'symlink' => [['file']],
'tempnam' => [['file']],
'igbinary_unserialize' => [['unserialize']], 'igbinary_unserialize' => [['unserialize']],
'ldap_search' => [[], ['ldap'], ['ldap']], 'ldap_search' => [[], ['ldap'], ['ldap']],
'mysqli_query' => [[], ['sql']], 'mysqli_query' => [[], ['sql']],
@ -35,7 +49,7 @@ return [
'pg_send_prepare' => [[], [], ['sql']], 'pg_send_prepare' => [[], [], ['sql']],
'pg_send_query' => [[], ['sql']], 'pg_send_query' => [[], ['sql']],
'pg_send_query_params' => [[], ['sql'], []], 'pg_send_query_params' => [[], ['sql'], []],
'setcookie' => [['text'], ['text']], 'setcookie' => [['cookie'], ['cookie']],
'shell_exec' => [['shell']], 'shell_exec' => [['shell']],
'system' => [['shell']], 'system' => [['shell']],
'unserialize' => [['unserialize']], 'unserialize' => [['unserialize']],

View File

@ -0,0 +1,34 @@
# TaintedCookie
Potential cookie injection. This rule is emitted when user-controlled input can be passed into a cookie.
## Risk
The risk of setting arbitrary cookies depends on further application configuration.
Examples of potential issues:
- Session Fixation: If the authentication cookie doesn't change after a successful login an attacker could fixate the session cookie. If a victim logs in with a fixated cookie, the attacker can now take over the session of the user.
- Cross-Site-Scripting (XSS): Some application code could read cookies and print it out unsanitized to the user.
## Example
```php
<?php
setcookie('authtoken', $_GET['value'], time() + (86400 * 30), '/');
```
## Mitigations
If this is required functionality, limit the cookie setting to values and not the name. (e.g. `authtoken` in the example)
Make sure to change session tokens after authentication attempts.
## Further resources
- [OWASP Wiki for Session fixation](https://owasp.org/www-community/attacks/Session_fixation)
- [Session Management Cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
- [CWE-384](https://cwe.mitre.org/data/definitions/384.html)

View File

@ -0,0 +1,43 @@
# TaintedFile
This rule is emitted when user-controlled input can be passed into a sensitive file operation.
## Risk
The risk here depends on the actual operation that contains user-controlled input, and how it is later on processed.
It could range from:
- Creating files
- Example: `file_put_contents`
- Risk: Depending on the server configuration this may result in remote code execution. (e.g. writing a file in the web root)
- Modifying files
- Example: `file_put_contents`
- Risk: Depending on the server configuration this may result in remote code execution. (e.g. modifying a PHP file)
- Reading files
- Example: `file_get_contents`
- Risk: Sensitive data could be exposed from the filesystem. (e.g. config values, source code, user-submitted files)
- Deleting files
- Example: `unlink`
- Risk: Denial of Service or potentially RCE. (e.g. deleting application code, removing a .htaccess file)
## Example
```php
<?php
$content = file_get_contents($_GET['header']);
echo $content;
```
## Mitigations
Use an allowlist approach where possible to verify names on file operations.
Sanitize user-controlled filenames by stripping `..`, `\` and `/`.
## Further resources
- [File Upload Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html)
- [OWASP Wiki for Unrestricted FIle Upload](https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload)
- [CWE-73](https://cwe.mitre.org/data/definitions/73.html)

View File

@ -0,0 +1,36 @@
# TaintedHeader
Potential header injection. This rule is emitted when user-controlled input can be passed into a HTTP header.
## Risk
The risk of a header injection depends hugely on your environment.
If your webserver supports something like [`XSendFile`](https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/) / [`X-Accel`](https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/), an attacker could potentially access arbitrary files on the systems.
If your system does not do that, there may be other concerns, such as:
- Cookie Injection
- Open Redirects
- Proxy Cache Poisoning
## Example
```php
<?php
header($_GET['header']);
```
## Mitigations
Make sure only the value and not the key can be set by an attacker. (e.g. `header('Location: ' . $_GET['target']);`)
Verify the set values are sensible. Consider using an allow list. (e.g. for redirections)
## Further resources
- [Unvalidated Redirects and Forwards Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html)
- [OWASP Wiki for Cache Poisoning](https://owasp.org/www-community/attacks/Cache_Poisoning)
- [CWE-601](https://cwe.mitre.org/data/definitions/601.html)
- [CWE-644](https://cwe.mitre.org/data/definitions/644.html)

View File

@ -6,8 +6,11 @@ use Psalm\CodeLocation;
use Psalm\Internal\DataFlow\DataFlowNode; use Psalm\Internal\DataFlow\DataFlowNode;
use Psalm\Internal\DataFlow\TaintSink; use Psalm\Internal\DataFlow\TaintSink;
use Psalm\Internal\DataFlow\TaintSource; use Psalm\Internal\DataFlow\TaintSource;
use Psalm\Issue\TaintedCookie;
use Psalm\Issue\TaintedCustom; use Psalm\Issue\TaintedCustom;
use Psalm\Issue\TaintedEval; use Psalm\Issue\TaintedEval;
use Psalm\Issue\TaintedFile;
use Psalm\Issue\TaintedHeader;
use Psalm\Issue\TaintedHtml; use Psalm\Issue\TaintedHtml;
use Psalm\Issue\TaintedInclude; use Psalm\Issue\TaintedInclude;
use Psalm\Issue\TaintedLdap; use Psalm\Issue\TaintedLdap;
@ -375,6 +378,33 @@ class TaintFlowGraph extends DataFlowGraph
); );
break; break;
case TaintKind::INPUT_COOKIE:
$issue = new TaintedCookie(
'Detected tainted cookie',
$issue_location,
$issue_trace,
$path
);
break;
case TaintKind::INPUT_FILE:
$issue = new TaintedFile(
'Detected tainted file handling',
$issue_location,
$issue_trace,
$path
);
break;
case TaintKind::INPUT_HEADER:
$issue = new TaintedHeader(
'Detected tainted header',
$issue_location,
$issue_trace,
$path
);
break;
default: default:
$issue = new TaintedCustom( $issue = new TaintedCustom(
'Detected tainted ' . $matching_taint, 'Detected tainted ' . $matching_taint,

View File

@ -0,0 +1,7 @@
<?php
namespace Psalm\Issue;
class TaintedCookie extends TaintedInput
{
public const SHORTCODE = 257;
}

View File

@ -0,0 +1,7 @@
<?php
namespace Psalm\Issue;
class TaintedFile extends TaintedInput
{
public const SHORTCODE = 255;
}

View File

@ -0,0 +1,7 @@
<?php
namespace Psalm\Issue;
class TaintedHeader extends TaintedInput
{
public const SHORTCODE = 256;
}

View File

@ -16,6 +16,9 @@ class TaintKind
public const INPUT_HTML = 'html'; public const INPUT_HTML = 'html';
public const INPUT_SHELL = 'shell'; public const INPUT_SHELL = 'shell';
public const INPUT_SSRF = 'ssrf'; public const INPUT_SSRF = 'ssrf';
public const INPUT_FILE = 'file';
public const INPUT_COOKIE = 'cookie';
public const INPUT_HEADER = 'header';
public const USER_SECRET = 'user_secret'; public const USER_SECRET = 'user_secret';
public const SYSTEM_SECRET = 'system_secret'; public const SYSTEM_SECRET = 'system_secret';
} }

View File

@ -17,5 +17,8 @@ class TaintKindGroup
TaintKind::INPUT_INCLUDE, TaintKind::INPUT_INCLUDE,
TaintKind::INPUT_SSRF, TaintKind::INPUT_SSRF,
TaintKind::INPUT_LDAP, TaintKind::INPUT_LDAP,
TaintKind::INPUT_FILE,
TaintKind::INPUT_HEADER,
TaintKind::INPUT_COOKIE,
]; ];
} }

View File

@ -1810,6 +1810,21 @@ class TaintTest extends TestCase
ldap_search($ds, $dn, $filter, []);', ldap_search($ds, $dn, $filter, []);',
'error_message' => 'TaintedLdap', 'error_message' => 'TaintedLdap',
], ],
'taintedFile' => [
'<?php
file_get_contents($_GET[\'taint\']);',
'error_message' => 'TaintedFile',
],
'taintedHeader' => [
'<?php
header($_GET[\'taint\']);',
'error_message' => 'TaintedHeader',
],
'taintedCookie' => [
'<?php
setcookie($_GET[\'taint\'], \'value\');',
'error_message' => 'TaintedCookie',
],
'potentialTaintThroughChildClassSettingProperty' => [ 'potentialTaintThroughChildClassSettingProperty' => [
'<?php '<?php
class A { class A {