9

Checkmarx - v 9.3.0 HF11

I am passing env value as data directory path in docker file which used in dev/uat server

ENV DATA /app/data/

In local, using following Environment variable

DATA=C:\projects\app\data\

getDataDirectory("MyDirectoryName"); // MyDirectoryName is present in data folder

public String getDataDirectory(String dirName)
{
    String path = System.getenv("DATA");
    if (path != null) {
        path = sanitizePathValue(path);
        path = encodePath(path);

        dirName = sanitizePathValue(dirName);
        if (!path.endsWith(File.separator)) {
            path = path + File.separator;
        } else if (!path.contains("data")) {
            throw new MyRuntimeException("Data Directory path is incorrect");
        }
    } else {
        return null;
    }

    File file = new File(dirName); // NOSONAR

    if (!file.isAbsolute()) {
        File tmp = new File(SecurityUtil.decodePath(path)); // NOSONAR

        if (!tmp.getAbsolutePath().endsWith(Character.toString(File.separatorChar))) {
            dirName = tmp.getAbsolutePath() + File.separatorChar + dirName;
        } else {
            dirName = tmp.getAbsolutePath() + dirName;
        }

    }

    return dirName;
}

public static String encodePath(String path) {
        try {
            return URLEncoder.encode(path, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            logger.error("Exception while encoding path", e);
        }
        return "";
}

public static String validateAndNormalizePath(String path) {
        path = path.replaceAll("/../", "/");
        path = path.replaceAll("/%46%46/", "/");
        path = SecurityUtil.cleanIt(path);
        path = FilenameUtils.normalize(path); // normalize path
        return path;

    }

public static String sanitizePathValue(String filename){
    filename = validateAndNormalizePath(filename);
    String regEx = "..|\\|/";
    // compile the regex to create pattern
    // using compile() method
    Pattern pattern = Pattern.compile(regEx);
    // get a matcher object from pattern
    Matcher matcher = pattern.matcher(filename);

    // check whether Regex string is
    // found in actualString or not
    boolean matches = matcher.matches();
    if(matches){
        throw new MyAppRuntimeException("filename:'"+filename+"' is bad.");
    }
    return  filename;
}

public static String validateAndNormalizePath(String path) {
    path = path.replaceAll("/../", "/");
    path = path.replaceAll("/%46%46/", "/");
    path = SecurityUtil.cleanIt(path);
    path = FilenameUtils.normalize(path); // normalize path
    return path;

}

[Attempt] - Update code which I tried with the help of few members to prevent path traversal issue.

Tried to sanitize string and normalize string, but no luck and getting same issue.

How to resolve Stored Absolute Path Traversal issue ?

StackOverFlow
  • 4,486
  • 12
  • 52
  • 87
  • 3
    You may try to run your application using a restricted user who has permissions to access only specific directories. Even if the path is not sanitized user will hardly be able to do anything. Something like [this](https://superuser.com/questions/149404/create-an-ssh-user-who-only-has-permission-to-access-specific-folders) – Navin M Dec 20 '20 at 16:45
  • What about [File API - Canonical File](https://docs.oracle.com/javase/7/docs/api/java/io/File.html#getCanonicalFile())? `new File(filename).getCanonicalFile()` – Butiri Dan Dec 23 '20 at 10:39
  • @Butiri Dan Could you please answer, if you are sure. Appriciate your detail answer. – StackOverFlow Dec 23 '20 at 17:38
  • @StackOverFlow I have a few questions to ask you before attempting to the solve this problem : 1. Why are you not taking relative paths as input from the user ? ( instead of taking the entire path ). You can generate the absolute path by concatenating relevant suffix with the relative path. > String filename = System.getEnv("test"); > > File dictionaryFile = new File("Use Suffix here" + filename); – Aniketh Jain Dec 28 '20 at 12:58
  • 2. Even if you restrict the access of a user to a certain directory, does your system stores the sensitive data for every user at a different directory ? Can the user guess a random filename ( risky if filenames have a pattern ) and still get access to unauthorised files ? 3. If you don't have control over segregating the files in different directories based on the user, then do you have any metadata or mapping of the files that a user is allowed to access ? – Aniketh Jain Dec 28 '20 at 12:58
  • @StackOverFlow - Did you happen to find a solution for checkmarkx issue? I am having the same issue. – Jadav Bheda Oct 14 '22 at 04:25

6 Answers6

2

Your first attempt is not going to work because escaping alone isn't going to prevent a path traversal. Replacing single quotes with double quotes won't do it either given you need to make sure someone setting a property/env variable with ../../etc/resolv.conf doesn't succeed in tricking your code into overwriting/reading a sensitive file. I believe Checkmarx won't look for StringUtils as part of recognizing it as sanitized, so the simple working example below is similar without using StringUtils.

Your second attempt won't work because it is a validator that uses control flow to prevent a bad input when it throws an exception. Checkmarx analyzes data flows. When filename is passed as a parameter to sanitizePathValue and returned as-is at the end, the data flow analysis sees this as not making a change to the original value.

There also appears to be some customizations in your system that recognize System.getProperty and System.getenv as untrusted inputs. By default, these are not recognized in this way, so anyone trying to scan your code probably would not have gotten any results for Absolute Path Traversal. It is possible that the risk profile of your application requires that you call properties and environment variables as untrusted inputs, so you can't really just remove these and revert back to the OOTB settings.

As Roman had mentioned, the logic in the query does look for values that are prepended to this untrusted input to remove those data flows as results. The below code shows how this could be done using Roman's method to trick the scanner. (I highly suggest you do not choose the route to trick the scanner.....very bad idea.) There could be other string literal values that would work using this method, but it would require some actions that control how the runtime is executed (like using chroot) to make sure it actually fixed the issue.

If you scan the code below, you should see only one vulnerable data path. The last example is likely something along the lines of what you could use to remediate the issues. It really depends on what you're trying to do with the file being created.

(I tested this on 9.2; it should work for prior versions. If it doesn't work, post your version and I can look into that version's query.)

// Vulnerable
String fn1 = System.getProperty ("test");
File f1 = new File(fn1);


// Path prepend - still vulnerable, tricks the scanner, DO NOT USE
String fn2 = System.getProperty ("test");
File f2 = new File(Paths.get ("", fn2).toString () );

// Path prepend - still vulnerable, tricks the scanner, DO NOT USE
String fn3 = System.getProperty ("test");
File f3 = new File("" + fn3);

// Path prepend - still vulnerable, tricks the scanner, DO NOT USE
String fn4 = System.getProperty ("test");
File f4 = new File("", fn4);

// Sanitized by stripping path separator as defined in the JDK
// This would be the safest method
String fn5 = System.getProperty ("test");
File f5 = new File(fn5.replaceAll (File.separator, ""));


So, in summary (TL;DR), replace the file separator in the untrusted input value:

String fn5 = System.getProperty ("test");
File f5 = new File(fn5.replaceAll (File.separator, ""));

Edit Updating for other Checkmarx users that may come across this in search of an answer.

After my answer, OP updated the question to reveal that the issue being found was due to a mechanism written for the code to run in different environments. Pre-docker, this would have been the method to use. The vulnerability would have still been detected but most courses of action would have been to say "our deployment environment has security measures around it to prevent a bad actor from injecting an undesired path into the environment variable where we store our base path."

But now, with Docker, this is a thing of the past. Generally the point of Docker is to create applications that run the way same everywhere they are deployed. Using a base path in an environment likely means OP is executing the code outside of a container for development (based on the update showing a Windows path) and inside the container for deployment. Why not just run the code in the container for development as well as deployment as is intended by Docker?

Most of the answers tend to explain that OP should use a static path. This is because they are realizing that there is no way to avoid this issue because taking an untrusted input (from the environment) and prefixing it to a path is the exact problem of Absolute Path Traversal.

OP could follow the good advice of many posters here and put a static base path in the code then use Docker volumes or Docker bind mounts.

Is it difficult? Nope. If I were OP, I'd fix the base path prefix in code to a static value of /app/data and do a simple volume binding during development. (When you think about it, if there is storage of data in the container during a deployment then the deployment environment must be doing this exact thing for /app/data unless the data is not kept after the lifetime of the container.)

With the base path fixed at /app/data, one option for OP to run their development build is:

docker run -it -v"C:\\projects\\app\\data":/app/data {container name goes here}

All data written by the application would appear in C:\projects\app\data the same way it does when using the environment variables. The main difference is that there are no environment-variable-prefixed paths and thus no Absolute Path Traversal results from the static analysis scanner.

NathanL
  • 357
  • 2
  • 8
  • Thanks for reply . I am passing env value as path in docker file which used in dev/uat server ENV test /app/data/ In local/dev Environment variable test=C:\projects\app\data – StackOverFlow Jan 13 '21 at 06:23
  • I have updated question ,1] I am getting file path from env/property.2) filepath + my passed filename gets appended and passed to FILE class – StackOverFlow Jan 13 '21 at 06:33
  • Your execution context (which can't be scanned) potentially makes this a false positive. If you trust props/env, it is FP. Otherwise, it is true positive due to untrusted input forming a path. The purpose of using Docker is usually so that a Dockerized app runs the same way in every environment. If you developed it and ran it in Docker, you could have a static path (absolute or maybe relative to WORKDIR). The only way to avoid a path traversal from an untrusted input is to turn it into something that can't be used in a path, which is going to be difficult due to the implementation choices. – NathanL Jan 13 '21 at 16:36
  • Also want to add - usually Dockerized apps use absolute paths and then execution maps a volume to that absolute path. This is so devs can develop locally and substitute local storage for perhaps cloud-based storage or a NAS when deployed in a production environment. Run your container with a path mapped to a volume/local dir rather than an environment variable and you'd have the same effect. If you go to a bootcamp at DockerCon, they actually show you this technique as part of the presentation. – NathanL Jan 13 '21 at 16:40
  • We are using Checkmarx 9.3.0 HF11 version gets used. Also updated code for reference. – StackOverFlow Jul 12 '21 at 05:07
1

It depends on how Checkmarx comes to this point. Most likely because the value that is handed to File is still tainted. So make sure both /../ and /%46%46/ are replaced by /.

checkedInput = userInput.replaceAll("/../", "/");

Secondly, give File a parent directory to start with and later compare the path of the file you want to process. Some common example code is below. If the file doesn't start with the full parent directory, then it means you have a path traversal.

File file = new File(BASE_DIRECTORY, userInput);
if (file.getCanonicalPath().startsWith(BASE_DIRECTORY)) {
    // process file
}

Checkmarx can only check if variables contain a tainted value and in some cases if the logic is correct. Please also think about the running process and file system permissions. A lot of applications have the capability of overwriting their own executables.

hspaans
  • 165
  • 1
  • 7
1

For this issue i would suggest you hard code the absolute path of the directory that you allow your program to work in; like this:

String separator = FileSystems.getDefault().getSeparator();
// should resolve to /app/workdir in linux
String WORKING_DIR = separator + "app"+separator +"workdir"+separator ;

then when you accept the parameter treat it as a relative path like this:

String filename = System.getProperty("test");
sanitize(filename);
filename = WORKING_DIR+filename;
File dictionaryFile = new File(filename);

To sanitize your user's input make sure he does not include .. and does not include also \ nor /

private static void sanitize(filename){
if(Pattern.compile("\\.\\.|\\|/").matcher(filename).find()){
  throw new RuntimeException("filename:'"+filename+"' is bad.");
  }
}

Edit

In case you are running the process in linux you can change the root of the process using chroot maybe you do some googling to know how you should implement it.

achabahe
  • 2,445
  • 1
  • 12
  • 21
  • 1
    nice name by the way it is a gem – achabahe Dec 30 '20 at 14:59
  • I tried your answer but no luck. Checkmarx still reporting Absolute Path Traversal issue-https://cwe.mitre.org/data/definitions/36.html – StackOverFlow Jan 11 '21 at 09:51
  • I have updated question ,1] I am getting file path from env/property.2) filepath + my passed filename gets appended and passed to FILE class – StackOverFlow Jan 13 '21 at 06:35
  • why would you do that startWith check i you are constucting the filename ??? – achabahe Jan 15 '21 at 17:56
  • Yes, like filepath = "/app/config" + filename.ext so checking filepath.startsWith("//app//") – StackOverFlow Jan 16 '21 at 05:26
  • You are missing the point filepath = "/app/" + filename ; means you are sure that the file will be written into app directory because you are 100%sure that filename does not contain ".." so you are sure je will not go to a parent directory – achabahe Jan 16 '21 at 17:17
  • The intended sanitize method here does not work properly for mac. The 2 dots must be escaped. This is how the sanitize should look like: Pattern.compile("\\.\\.|\\|/").matcher(fileName).find() – jkasper Apr 21 '21 at 04:20
  • I confirm I forgot to escape the two dots – achabahe Apr 21 '21 at 11:06
  • achabahe - I have updated my question with checkmex version. Could you please review & revise answer. – StackOverFlow Jul 12 '21 at 08:05
1

If there is one thing to remember it is this

use allow lists not deny lists

(traditionally known as whitelists and blacklists).

For instance, consider replacing /../ with / suggested in another answer. My response is to contain the sequence /../../. You could pursue this iteratively, and I might run out of adversarial examples, but that doesn't mean there are any.

Another problem is knowing all the special characters. \0 used to truncate the file name. What happens to non-ASCII characters - I can't remember. Might other code be changed in future so that the path ends up on a command line with other special characters - worse, OS/command line dependent.

Canonicalisation has its problems too. It can be used to some extent probe the file system (and perhaps beyond the machine).

So, choose what you allow. Say

if (filename.matches("[a-zA-Z0-9_]+")) {
    return filename;
} else {
    throw new MyException(...);
}

(No need to go through the whole Pattern/Matcher palaver in this situation.)

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
0

Based on reading the Checkmarx query for absolute path traversal vulnerability (and I believe in general one of the mitigation approach), is to prepend a hard coded path to avoid the attackers traversing through the file system:

File has a constructor that accepts a second parameter that will allow you to perform some prepending

String filename = System.getEnv("test");
File dictionaryFile = new File("/home/", filename);

UPDATE: The validateAndNormalizePath would have technically sufficed but I believe Checkmarx is unable to recognize this as a sanitizer (being a custom written function). I would advice to work with your App Security team for them to use the CxAudit and overwrite the base Stored Path Traversal Checkmarx query to recognize validateAndNormalizePath as a valid sanitizer.

securecodeninja
  • 2,497
  • 3
  • 16
  • 22
0

how about using Java's Path to make the check("../test1.txt" is the input from user):

File base=new File("/your/base");
Path basePath=base.toPath();
Path resolve = basePath.resolve("../test1.txt");
Path relativize = basePath.relativize(resolve);
if(relativize.startsWith("..")){
    throw new Exception("invalid path");
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31