-
Notifications
You must be signed in to change notification settings - Fork 102
SONARPY-376 Rule S5443: Using publicly writable directories is securi… #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| { | ||
| 'project:buildbot-0.8.6p1/contrib/bb_applet.py':[ | ||
| 30, | ||
| 174, | ||
| ], | ||
| 'project:docker-compose-1.24.1/tests/acceptance/cli_test.py':[ | ||
| 2617, | ||
| ], | ||
| 'project:docker-compose-1.24.1/tests/integration/service_test.py':[ | ||
| 284, | ||
| 303, | ||
| 448, | ||
| 706, | ||
| ], | ||
| 'project:docker-compose-1.24.1/tests/unit/config/config_test.py':[ | ||
| 1352, | ||
| 1367, | ||
| 4306, | ||
| ], | ||
| 'project:tornado-2.3/demos/s3server/s3server.py':[ | ||
| 47, | ||
| ], | ||
| 'project:twisted-12.1.0/doc/core/examples/courier.py':[ | ||
| 10, | ||
| ], | ||
| 'project:twisted-12.1.0/doc/core/examples/ptyserv.py':[ | ||
| 18, | ||
| ], | ||
| 'project:twisted-12.1.0/doc/core/examples/rotatinglog.py':[ | ||
| 14, | ||
| ], | ||
| 'project:twisted-12.1.0/twisted/names/authority.py':[ | ||
| 18, | ||
| ], | ||
| 'project:twisted-12.1.0/twisted/test/test_amp.py':[ | ||
| 2989, | ||
| 2993, | ||
| ], | ||
| 'project:twisted-12.1.0/twisted/test/test_ftp.py':[ | ||
| 1870, | ||
| 1885, | ||
| 1901, | ||
| 1916, | ||
| 1930, | ||
| 1945, | ||
| 1961, | ||
| 1976, | ||
| ], | ||
| 'project:twisted-12.1.0/twisted/test/test_process.py':[ | ||
| 2000, | ||
| 2013, | ||
| ], | ||
| 'project:twisted-12.1.0/twisted/web/test/test_template.py':[ | ||
| 185, | ||
| 193, | ||
| ], | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
...ecks/src/main/java/org/sonar/python/checks/hotspots/PubliclyWritableDirectoriesCheck.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| /* | ||
| * SonarQube Python Plugin | ||
| * Copyright (C) 2011-2019 SonarSource SA | ||
| * mailto:info AT sonarsource DOT com | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 3 of the License, or (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public License | ||
| * along with this program; if not, write to the Free Software Foundation, | ||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| */ | ||
| package org.sonar.python.checks.hotspots; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
| import java.util.regex.Pattern; | ||
| import org.sonar.check.Rule; | ||
| import org.sonar.python.PythonSubscriptionCheck; | ||
| import org.sonar.python.api.tree.PyArgumentTree; | ||
| import org.sonar.python.api.tree.PyCallExpressionTree; | ||
| import org.sonar.python.api.tree.PyExpressionTree; | ||
| import org.sonar.python.api.tree.PyNameTree; | ||
| import org.sonar.python.api.tree.PyQualifiedExpressionTree; | ||
| import org.sonar.python.api.tree.PyStringLiteralTree; | ||
| import org.sonar.python.api.tree.PySubscriptionExpressionTree; | ||
| import org.sonar.python.api.tree.Tree.Kind; | ||
| import org.sonar.python.semantic.Symbol; | ||
| import org.sonar.python.semantic.SymbolTable; | ||
|
|
||
| @Rule(key = "S5443") | ||
| public class PubliclyWritableDirectoriesCheck extends PythonSubscriptionCheck { | ||
|
|
||
| private static final String MESSAGE = "Make sure publicly writable directories are used safely here."; | ||
| private static final List<String> UNIX_WRITABLE_DIRECTORIES = Arrays.asList( | ||
| "/tmp/", "/var/tmp/", "/usr/tmp/", "/dev/shm/", "/dev/mqueue/", "/run/lock/", "/var/run/lock/", | ||
| "/library/caches/", "/users/shared/", "/private/tmp/", "/private/var/tmp/"); | ||
| private static final List<String> NONCOMPLIANT_ENVIRON_VARIABLES = Arrays.asList("tmpdir", "tmp"); | ||
|
|
||
| private static final Pattern WINDOWS_WRITABLE_DIRECTORIES = Pattern.compile("[^\\\\]*\\\\(Windows\\\\Temp|Temp|TMP)(\\\\.*|$)", Pattern.CASE_INSENSITIVE); | ||
|
|
||
| @Override | ||
| public void initialize(Context context) { | ||
| context.registerSyntaxNodeConsumer(Kind.STRING_LITERAL, ctx -> { | ||
| PyStringLiteralTree tree = (PyStringLiteralTree) ctx.syntaxNode(); | ||
| String stringLiteral = tree.trimmedQuotesValue().toLowerCase(Locale.ENGLISH); | ||
| if (UNIX_WRITABLE_DIRECTORIES.stream().anyMatch(dir -> containsDirectory(stringLiteral, dir)) || | ||
| WINDOWS_WRITABLE_DIRECTORIES.matcher(stringLiteral).matches()) { | ||
| ctx.addIssue(tree, MESSAGE); | ||
| } | ||
| }); | ||
|
|
||
| context.registerSyntaxNodeConsumer(Kind.CALL_EXPR, ctx -> { | ||
| PyCallExpressionTree tree = (PyCallExpressionTree) ctx.syntaxNode(); | ||
| if (isOsEnvironGetter(tree.callee(), ctx.symbolTable()) && | ||
| tree.arguments().stream().map(PyArgumentTree::expression) | ||
| .anyMatch(PubliclyWritableDirectoriesCheck::isNonCompliantOsEnvironArgument)) { | ||
| ctx.addIssue(tree, MESSAGE); | ||
| } | ||
| }); | ||
|
|
||
| context.registerSyntaxNodeConsumer(Kind.SUBSCRIPTION, ctx -> { | ||
| PySubscriptionExpressionTree tree = (PySubscriptionExpressionTree) ctx.syntaxNode(); | ||
| if (isOsEnvironQualifiedExpression(tree.object(), ctx.symbolTable()) && tree.subscripts().expressions().stream() | ||
| .anyMatch(PubliclyWritableDirectoriesCheck::isNonCompliantOsEnvironArgument)) { | ||
| ctx.addIssue(tree, MESSAGE); | ||
| } | ||
| }); | ||
|
|
||
| } | ||
|
|
||
| private static boolean containsDirectory(String stringLiteral, String dir) { | ||
| return stringLiteral.startsWith(dir) || stringLiteral.equals(dir.substring(0, dir.length() - 1)); | ||
| } | ||
|
|
||
| private static boolean isNonCompliantOsEnvironArgument(PyExpressionTree expression) { | ||
| return expression.is(Kind.STRING_LITERAL) && | ||
| NONCOMPLIANT_ENVIRON_VARIABLES.contains(((PyStringLiteralTree) expression).trimmedQuotesValue().toLowerCase(Locale.ENGLISH)); | ||
| } | ||
|
|
||
| // Could be either `os.environ.get` or `environ.get` | ||
| private static boolean isOsEnvironGetter(PyExpressionTree expression, SymbolTable symbolTable) { | ||
| if (!expression.is(Kind.QUALIFIED_EXPR)) { | ||
| return false; | ||
| } | ||
| PyQualifiedExpressionTree qualifiedExpression = (PyQualifiedExpressionTree) expression; | ||
| if (qualifiedExpression.name().name().equals("get")) { | ||
| return isOsEnvironQualifiedExpression(qualifiedExpression.qualifier(), symbolTable); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| // Could be either `os.environ` or `from os import environ; ... ; environ` | ||
| private static boolean isOsEnvironQualifiedExpression(PyExpressionTree expression, SymbolTable symbolTable) { | ||
| Symbol symbol = symbolTable.getSymbol(expression); | ||
| if (symbol != null) { | ||
| return symbol.qualifiedName().equals("os.environ"); | ||
| } | ||
| if (!expression.is(Kind.QUALIFIED_EXPR)) { | ||
| return false; | ||
| } | ||
| PyQualifiedExpressionTree qualifiedExpression = (PyQualifiedExpressionTree) expression; | ||
| if (qualifiedExpression.qualifier().is(Kind.NAME)) { | ||
| return ((PyNameTree) qualifiedExpression.qualifier()).name().equals("os") && qualifiedExpression.name().name().equals("environ"); | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
70 changes: 70 additions & 0 deletions
70
python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S5443.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| <p>Operating systems have global directories where any user have write access. Those folders are mostly used as temporary storage areas like | ||
| <code>/tmp</code> in Linux based systems. An application manipulating files from these folders is exposed to race conditions on filenames: a malicious | ||
| user can try to create a file with a predictable name before the application does. A successful attack can result in other files being accessed, | ||
| modified, corrupted or deleted. This risk is even higher if the application runs with elevated permissions.</p> | ||
| <p>In the past, it has led to the following vulnerabilities:</p> | ||
| <ul> | ||
| <li> <a href="https://nvd.nist.gov/vuln/detail/CVE-2012-2451">CVE-2012-2451</a> </li> | ||
| <li> <a href="https://nvd.nist.gov/vuln/detail/CVE-2015-1838">CVE-2015-1838</a> </li> | ||
| </ul> | ||
| <p>This rule raises an issue whenever it detects a hard-coded path to a publicly writable directory like <code>/tmp</code> (see complete list bellow). | ||
| It also detects access to <code>TMP</code> and <code>TMPDIR</code> environment variables.</p> | ||
| <ul> | ||
| <li> <code>/tmp</code> </li> | ||
| <li> <code>/var/tmp</code> </li> | ||
| <li> <code>/usr/tmp</code> </li> | ||
| <li> <code>/dev/shm</code> </li> | ||
| <li> <code>/dev/mqueue</code> </li> | ||
| <li> <code>/run/lock</code> </li> | ||
| <li> <code>/var/run/lock</code> </li> | ||
| <li> <code>/Library/Caches</code> </li> | ||
| <li> <code>/Users/Shared</code> </li> | ||
| <li> <code>/private/tmp</code> </li> | ||
| <li> <code>/private/var/tmp</code> </li> | ||
| <li> <code>\Windows\Temp</code> </li> | ||
| <li> <code>\Temp</code> </li> | ||
| <li> <code>\TMP</code> </li> | ||
| </ul> | ||
| <h2>Ask Yourself Whether</h2> | ||
| <ul> | ||
| <li> Files are read from or written into a publicly writable folder </li> | ||
| <li> The application creates files with predictable names into a publicly writable folder </li> | ||
| </ul> | ||
| <p>You are at risk if you answered yes to any of those questions.</p> | ||
| <h2>Recommended Secure Coding Practices</h2> | ||
| <ul> | ||
| <li> Use a dedicated sub-folder with tightly controlled permissions </li> | ||
| <li> Use secure-by-design APIs to create temporary files. Such API will make sure: | ||
| <ul> | ||
| <li> The generated filename is unpredictable </li> | ||
| <li> The file is readable and writable only by the creating user ID </li> | ||
| <li> The file descriptor is not inherited by child processes </li> | ||
| <li> The file will be destroyed as soon as it is closed </li> | ||
| </ul> </li> | ||
| </ul> | ||
| <h2>Sensitive Code Examples</h2> | ||
| <pre> | ||
| file = open("/tmp/temporary_file","w+") # Questionable | ||
| </pre> | ||
| <pre> | ||
| tmp_dir = os.environ.get('TMPDIR') # Questionable | ||
| file = open(tmp_dir+"/temporary_file","w+") | ||
| </pre> | ||
| <h2>Compliant Solution</h2> | ||
| <pre> | ||
| import tempfile | ||
|
|
||
| file = tempfile.TemporaryFile(dir="/tmp/my_subdirectory", mode='"w+") # Compliant | ||
| </pre> | ||
| <h2>See</h2> | ||
| <ul> | ||
| <li> <a href="https://www.owasp.org/index.php/Top_10-2017_A5-Broken_Access_Control">OWASP Top 10 2017 Category A5</a> - Broken Access Control </li> | ||
| <li> <a href="https://www.owasp.org/index.php/Top_10-2017_A3-Sensitive_Data_Exposure">OWASP Top 10 2017 Category A3</a> - Sensitive Data Exposure | ||
| </li> | ||
| <li> <a href="http://cwe.mitre.org/data/definitions/377">MITRE, CWE-377</a> - Insecure Temporary File </li> | ||
| <li> <a href="http://cwe.mitre.org/data/definitions/379">MITRE, CWE-379</a> - Creation of Temporary File in Directory with Incorrect Permissions | ||
| </li> | ||
| <li> <a href="https://www.owasp.org/index.php/Insecure_Temporary_File">OWASP, Insecure Temporary File</a> </li> | ||
| <li> <a href="https://docs.python.org/3/library/tempfile.html">Python tempfile module</a> </li> | ||
| </ul> | ||
|
|
24 changes: 24 additions & 0 deletions
24
python-checks/src/main/resources/org/sonar/l10n/py/rules/python/S5443.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| { | ||
| "title": "Using publicly writable directories is security-sensitive", | ||
| "type": "SECURITY_HOTSPOT", | ||
| "status": "ready", | ||
| "tags": [ | ||
| "cwe", | ||
| "owasp-a5", | ||
| "owasp-a3" | ||
| ], | ||
| "defaultSeverity": "Critical", | ||
| "ruleSpecification": "RSPEC-5443", | ||
| "sqKey": "S5443", | ||
| "scope": "Main", | ||
| "securityStandards": { | ||
| "CWE": [ | ||
| 377, | ||
| 379 | ||
| ], | ||
| "OWASP": [ | ||
| "A5", | ||
| "A3" | ||
| ] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,7 @@ | |
| "S4828", | ||
| "S4829", | ||
| "S5332", | ||
| "S5443", | ||
| "S5445" | ||
| ] | ||
| } | ||
30 changes: 30 additions & 0 deletions
30
.../src/test/java/org/sonar/python/checks/hotspots/PubliclyWritableDirectoriesCheckTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| /* | ||
| * SonarQube Python Plugin | ||
| * Copyright (C) 2011-2019 SonarSource SA | ||
| * mailto:info AT sonarsource DOT com | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 3 of the License, or (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public License | ||
| * along with this program; if not, write to the Free Software Foundation, | ||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| */ | ||
| package org.sonar.python.checks.hotspots; | ||
|
|
||
| import org.junit.Test; | ||
| import org.sonar.python.checks.utils.PythonCheckVerifier; | ||
|
|
||
| public class PubliclyWritableDirectoriesCheckTest { | ||
| @Test | ||
| public void test() { | ||
| PythonCheckVerifier.verify("src/test/resources/checks/hotspots/publiclyWritableDirectories.py", new PubliclyWritableDirectoriesCheck()); | ||
| } | ||
| } |
47 changes: 47 additions & 0 deletions
47
python-checks/src/test/resources/checks/hotspots/publiclyWritableDirectories.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # Common | ||
| open("/tmp/f","w+") # Noncompliant | ||
| open("/tmp","w+") # Noncompliant | ||
| open("/var/tmp/f","w+") # Noncompliant | ||
| open("/usr/tmp/f","w+") # Noncompliant | ||
| open("/dev/shm/f","w+") # Noncompliant | ||
| open("dev/shm/f","w+") # OK | ||
| open("/tmpx","w+") # OK | ||
|
|
||
| # Linux | ||
| open("/dev/mqueue/f","w+") # Noncompliant | ||
| open("/run/lock/f","w+") # Noncompliant | ||
| open("/var/run/lock/f","w+") # Noncompliant | ||
|
|
||
| # MacOS | ||
| open("/Library/Caches/f","w+") # Noncompliant | ||
| open("/Users/Shared/f","w+") # Noncompliant | ||
| open("/private/tmp/f","w+") # Noncompliant | ||
| open("/private/var/tmp/f","w+") # Noncompliant | ||
|
|
||
| # Windows | ||
| open("\Windows\Temp\f") # Noncompliant | ||
| open("D:\Windows\Temp\f") # Noncompliant | ||
| open("\Windows\Temp\f") # Noncompliant | ||
| open("\Temp\f") # Noncompliant | ||
| open("\TEMP\f") # Noncompliant | ||
| open("\TMP\f") # Noncompliant | ||
| open("C:\Temperatures") # OK | ||
|
|
||
| def environ_variables(): | ||
| import os | ||
| import myos | ||
| from os import environ | ||
| tmp_dir = os.environ.get('TMPDIR') # Noncompliant | ||
| tmp_dir = os.environ.get('TMP') # Noncompliant | ||
| tmp_dir = os.environ['TMPDIR'] # Noncompliant | ||
| tmp_dir = os.environ[foo] # OK | ||
| tmp_dir = os.environ.other_method('TMPDIR') # OK | ||
| tmp_dir = os.environ.get('OTHER') # OK | ||
| tmp_dir = os.environ['OTHER'] # OK | ||
| tmp_dir = os.environ['OTHER'] # OK | ||
| tmp_dir = os.other['TMPDIR'] # OK | ||
| tmp_dir = other['TMPDIR'] # OK | ||
| tmp_dir = os.foo.environ['TMPDIR'] # OK | ||
| tmp_dir = environ['TMPDIR'] # Noncompliant | ||
| tmp_dir = environ.get('TMPDIR') # Noncompliant | ||
| tmp_dir = myos.environ.get('TMPDIR') # OK |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could refine that to avoid some FPs: