This guideline highlights some of the most common security problems and how to prevent them. Please review your app if it contains any of the following security holes.
Program defensively: for instance always check for CSRF or escape strings, even if you do not need it. This prevents future problems where you might miss a change that leads to a security hole.
All App Framework security features depend on the call of the controller through
OCA\AppFramework\App::main. If the controller method is executed directly, no security checks are being performed!
SQL Injection occurs when SQL query strings are concatenated with variables.
To prevent this, always use prepared queries:
<?php $sql = 'SELECT * FROM `users` WHERE `id` = ?'; $query = \OCP\DB::prepare($sql); $params = array(1); $result = $query->execute($params);
If the App Framework is used, write SQL queries like this in a class that extends the Mapper:
<?php // inside a child mapper class $sql = 'SELECT * FROM `users` WHERE `id` = ?'; $params = array(1); $result = $this->execute($sql, $params);
Cross site scripting¶
Let’s assume you use the following example in your application:
<?php echo $_GET['username'];
An attacker might now easily send the user a link to:
to overtake the user account. The same problem occurs when outputting content from the database or any other location that is writable by users.
To prevent XSS in your app, never use echo, print() or <%= - use p() instead which will sanitize the input. Also validate URLs to start with the expected protocol (starts with http for instance)!
Should you ever require to print something unescaped, double check if it is really needed. If there is no other way (e.g. when including of subtemplates) use print_unescaped with care.
var html = '<li>' + username + '</li>"';
var html = '<li>' + escapeHTML(username) + '</li>';
An even better way to make your app safer is to use the jQuery built-in function $.text() instead of $.html().
Clickjacking tricks the user to click into an invisible iframe to perform an arbitrary action (e.g. delete an user account)
To prevent such attacks Nextcloud sends the X-Frame-Options header to all template responses. Don’t remove this header if you don’t really need it!
This is already built into Nextcloud in
Code executions / file inclusions¶
Code Execution means that an attacker is able to include an arbitrary PHP file. This PHP file runs with all the privileges granted to the normal application and can do an enormous amount of damage.
Code executions and file inclusions can be easily prevented by never allowing user-input to run through the following functions:
Also never allow the user to upload files into a folder which is reachable from the URL!
<?php require("/includes/" . $_GET['file']);
If you have to pass user input to a potentially dangerous function, double check to be sure that there is no other way. If it is not possible otherwise sanitize every user parameter and ask people to audit your sanitize function.
Very often developers forget about sanitizing the file path (removing all \ and /), this allows an attacker to traverse through directories on the server which opens several potential attack vectors including privilege escalations, code executions or file disclosures.
<?php $username = OC_User::getUser(); fopen("/data/" . $username . "/" . $_GET['file'] . ".txt");
<?php $username = OC_User::getUser(); $file = str_replace(array('/', '\\'), '', $_GET['file']); fopen("/data/" . $username . "/" . $file . ".txt");
PHP also interprets the backslash (\) in paths, don’t forget to replace it too!
Shell Injection occurs if PHP code executes shell commands (e.g. running a latex compiler). Before doing this, check if there is a PHP library that already provides the needed functionality. If you really need to execute a command be aware that you have to escape every user parameter passed to one of these functions:
Please require/request additional programmers to audit your escape function.
Without escaping the user input this will allow an attacker to execute arbitrary shell commands on your server.
PHP offers the following functions to escape user input:
- escapeshellarg(): Escape a string to be used as a shell argument
- escapeshellcmd(): Escape shell metacharacters
<?php system('ls '.$_GET['dir']);
<?php system('ls '.escapeshellarg($_GET['dir']));
Auth bypass / privilege escalations¶
Auth bypass/privilege escalations happen when a user is able to perform unauthorized actions.
Nextcloud offers three simple checks:
- OCP\JSON::checkLoggedIn(): Checks if the logged in user is logged in
- OCP\JSON::checkAdminUser(): Checks if the logged in user has admin privileges
- OCP\JSON::checkSubAdminUser(): Checks if the logged in user has group admin privileges
Using the App Framework, these checks are already automatically performed for each request and have to be explicitly turned off by using annotations above your controller method, see Controllers.
Additionally always check if the user has the right to perform that action. (e.g. a user should not be able to delete other users’ bookmarks).
Sensitive data exposure¶
Always store user data or configuration files in safe locations, e.g. nextcloud/data/ and not in the webroot where they can be accessed by anyone using a web browser.
Cross site request forgery¶
Using CSRF one can trick a user into executing a request that he did not want to make. Thus every POST and GET request needs to be protected against it. The only places where no CSRF checks are needed are in the main template, which is rendering the application, or in externally callable interfaces.
Submitting a form is also a POST/GET request!
To prevent CSRF in an app, be sure to call the following method at the top of all your files:
If you are using the App Framework, every controller method is automatically checked for CSRF unless you explicitly exclude it by setting the @NoCSRFRequired annotation before the controller method, see Controllers
This is more of an annoyance than a critical security vulnerability since it may be used for social engineering or phishing.
Always validate the URL before redirecting if the requested URL is on the same domain or an allowed resource.
<?php header('Location:'. $_GET['redirectURL']);
<?php header('Location: https://example.com'. $_GET['redirectURL']);