
org.sonar.java.checks.security.PubliclyWritableDirectoriesCheck Maven / Gradle / Ivy
The newest version!
/*
* SonarQube Java
* Copyright (C) 2012-2025 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks.security;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.sonar.check.Rule;
import org.sonar.java.checks.helpers.ExpressionsHelper;
import org.sonarsource.analyzer.commons.collections.SetUtils;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;
import static org.sonar.java.checks.helpers.ExpressionsHelper.getInvokedSymbol;
@Rule(key = "S5443")
public class PubliclyWritableDirectoriesCheck extends IssuableSubscriptionVisitor {
private static final String STRING_TYPE = "java.lang.String";
private static final String JAVA_NIO_FILE_FILES = "java.nio.file.Files";
private static final String JAVA_NIO_FILE_PATHS = "java.nio.file.Paths";
private static final String JAVA_NIO_FILE_PATH = "java.nio.file.Path";
private static final String JAVA_IO_FILE = "java.io.File";
private static final String MESSAGE = "Make sure publicly writable directories are used safely here.";
private static final List PUBLIC_WRITABLE_DIRS = 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",
"\\\\Windows\\\\Temp",
"\\\\Temp",
"\\\\TMP");
private static final Set TMP_DIR_ENV = SetUtils.immutableSetOf("TMP", "TMPDIR");
private static final MethodMatchers CREATE_FILE_MATCHERS = MethodMatchers.or(
MethodMatchers.create()
.ofTypes(JAVA_NIO_FILE_PATHS, JAVA_NIO_FILE_PATH)
.names("get")
.withAnyParameters()
.build(),
MethodMatchers.create()
.ofTypes(JAVA_NIO_FILE_PATH)
.names("of")
.withAnyParameters()
.build());
private static final MethodMatchers CREATE_FILE_CONSTRUCTOR_MATCHERS = MethodMatchers.create()
.ofTypes(JAVA_IO_FILE, "java.io.FileReader")
.constructor()
.addParametersMatcher(STRING_TYPE)
.addParametersMatcher(STRING_TYPE, STRING_TYPE)
.addParametersMatcher(STRING_TYPE, "java.nio.charset.Charset")
.build();
private static final MethodMatchers TEMP_DIR_MATCHER = MethodMatchers.create()
.ofTypes(JAVA_IO_FILE)
.names("createTempFile")
.addParametersMatcher(STRING_TYPE, STRING_TYPE)
.build();
private static final MethodMatchers NIO_TEMP_DIR_MATCHER = MethodMatchers.create()
.ofTypes(JAVA_NIO_FILE_FILES)
.names("createTempDirectory")
.withAnyParameters()
.build();
private static final MethodMatchers NIO_TEMP_FILE_MATCHER = MethodMatchers.create()
.ofTypes(JAVA_NIO_FILE_FILES)
.names("createTempFile")
.withAnyParameters()
.build();
private static final MethodMatchers MAP_GET = MethodMatchers.create()
.ofSubTypes("java.util.Map")
.names("get")
.addParametersMatcher("java.lang.Object")
.build();
private static final MethodMatchers SYSTEM_GETENV = MethodMatchers.create()
.ofSubTypes("java.lang.System")
.names("getenv")
.addWithoutParametersMatcher()
.build();
@Override
public List nodesToVisit() {
return Arrays.asList(Tree.Kind.METHOD_INVOCATION, Tree.Kind.NEW_CLASS);
}
@Override
public void visitNode(Tree tree) {
if (tree.is(Tree.Kind.METHOD_INVOCATION)) {
MethodInvocationTree mit = (MethodInvocationTree) tree;
if (createdInTempDir(mit) || hasSensitiveFileName(mit) || usesSystemTempDir(mit)) {
reportIssue(tree, MESSAGE);
}
} else {
NewClassTree newClassTree = (NewClassTree) tree;
if (CREATE_FILE_CONSTRUCTOR_MATCHERS.matches(newClassTree) &&
isSensitiveFileName(newClassTree.arguments().get(0))) {
reportIssue(tree, MESSAGE);
}
}
}
private static boolean hasSensitiveFileName(MethodInvocationTree mit) {
return CREATE_FILE_MATCHERS.matches(mit) &&
isSensitiveFileName(mit.arguments().get(0));
}
private static boolean usesSystemTempDir(MethodInvocationTree mit) {
return MAP_GET.matches(mit) && hasTMPAsArgument(mit) && isInitializedWithSystemGetEnv(mit);
}
private static boolean hasTMPAsArgument(MethodInvocationTree mit) {
return mit.arguments().get(0).asConstant(String.class)
.map(TMP_DIR_ENV::contains)
.orElse(false);
}
private static boolean createdInTempDir(MethodInvocationTree mit) {
return TEMP_DIR_MATCHER.matches(mit) ||
(NIO_TEMP_DIR_MATCHER.matches(mit) && (mit.arguments().size() == 1)) ||
(NIO_TEMP_FILE_MATCHER.matches(mit) && (mit.arguments().size() == 2));
}
private static boolean isSensitiveFileName(ExpressionTree expressionTree) {
return expressionTree.asConstant(String.class)
.filter(path -> PUBLIC_WRITABLE_DIRS.stream().anyMatch(path::startsWith))
.isPresent();
}
private static boolean isInitializedWithSystemGetEnv(MethodInvocationTree mit) {
return getInvokedSymbol(mit)
.filter(ExpressionsHelper::isNotReassigned)
.map(Symbol::declaration)
.filter(decl -> decl.is(Tree.Kind.VARIABLE))
.map(VariableTree.class::cast)
.map(VariableTree::initializer)
.map(ExpressionUtils::skipParentheses)
.filter(initializer -> initializer.is(Tree.Kind.METHOD_INVOCATION))
.map(MethodInvocationTree.class::cast)
.map(SYSTEM_GETENV::matches)
.orElse(false);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy