Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
PathNormalizer.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2023 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package com.basistech.df.cybertriage.autopsy.malwarescan;
20
21import com.google.common.net.InetAddresses;
22import java.net.InetAddress;
23import java.util.Collections;
24import java.util.HashSet;
25import java.util.List;
26import java.util.Locale;
27import java.util.Set;
28import java.util.logging.Level;
29import java.util.regex.Matcher;
30import java.util.regex.Pattern;
31import java.util.stream.Collectors;
32import org.apache.commons.lang3.StringUtils;
33import org.sleuthkit.autopsy.coreutils.Logger;
34import org.sleuthkit.datamodel.SleuthkitCase;
35import org.sleuthkit.datamodel.TskCoreException;
36
40class PathNormalizer {
41
42 private static final Logger LOGGER = Logger.getLogger(PathNormalizer.class.getName());
43
44 private static final String ANONYMIZED_USERNAME = "<user>";
45 private static final String ANONYMIZED_IP = "<private_ip>";
46 private static final String ANONYMIZED_HOSTNAME = "<hostname>";
47 private static final String FORWARD_SLASH = "/";
48 private static final String BACK_SLASH = "\\";
49
50 private static final Pattern USER_PATH_FORWARD_SLASH_REGEX = Pattern.compile("(?<!all )([/]{0,1}\\Qusers\\E/)(?!(public|Default|defaultAccount|All Users))([^/]+)(/){0,1}", Pattern.CASE_INSENSITIVE);
51 private static final Pattern USER_PATH_BACK_SLASH_REGEX = Pattern.compile("(?<!all )([\\\\]{0,1}\\Qusers\\E\\\\)(?!(public|Default|defaultAccount|All Users))([^\\\\]+)([\\\\]){0,1}", Pattern.CASE_INSENSITIVE);
52
53 private static final Pattern USER_PATH_FORWARD_SLASH_REGEX_XP = Pattern.compile("([/]{0,1}\\Qdocuments and settings\\E/)(?!(Default User|All Users))([^/]+)(/){0,1}", Pattern.CASE_INSENSITIVE);
54 private static final Pattern USER_PATH_BACK_SLASH_REGEX_XP = Pattern.compile("([\\\\]{0,1}\\Qdocuments and settings\\E\\\\)(?!(Default User|All Users))([^\\\\]+)(\\\\){0,1}", Pattern.CASE_INSENSITIVE);
55
56 private static final Pattern UNC_PATH_FORWARD_SLASH_PATTERN = Pattern.compile("(//)([^/]+)(/){0,1}");
57 private static final Pattern UNC_PATH_BACK_SLASH_PATTERN = Pattern.compile("(\\\\\\\\)([^\\\\]+)(\\\\){0,1}");
58
59 private static final String USERNAME_REGEX_REPLACEMENT = "$1" + ANONYMIZED_USERNAME + "$4";
60
61 private final SleuthkitCase skCase;
62
63 PathNormalizer(SleuthkitCase skCase) {
64 this.skCase = skCase;
65 }
66
67 protected List<String> getUsernames() {
68 try {
69 return this.skCase.getOsAccountManager().getOsAccounts().stream()
70 .filter(acct -> acct != null)
71 .map(acct -> acct.getLoginName().orElse(null))
72 .filter(StringUtils::isNotBlank)
73 .collect(Collectors.toList());
74 } catch (TskCoreException ex) {
75 LOGGER.log(Level.WARNING, "There was an error getting current os accounts", ex);
76 return Collections.emptyList();
77 }
78 }
79
80 public String normalizePath(String inputString) {
81 if (StringUtils.isBlank(inputString)) {
82 return "";
83 }
84
85 String anonymousString = anonymizeUserFromPathsWithForwardSlashes(inputString);
86 anonymousString = anonymizeUserFromPathsWithBackSlashes(anonymousString);
87 anonymousString = anonymizeServerFromUNCPath(anonymousString);
88
89 return anonymousString;
90 }
91
92 private String anonymizeUserFromPathsWithForwardSlashes(String stringWithUsername) {
93 String anonymousString = stringWithUsername;
94 anonymousString = regexReplace(anonymousString, USER_PATH_FORWARD_SLASH_REGEX_XP, USERNAME_REGEX_REPLACEMENT);
95 anonymousString = regexReplace(anonymousString, USER_PATH_FORWARD_SLASH_REGEX, USERNAME_REGEX_REPLACEMENT);
96 anonymousString = replaceFolder(anonymousString, getUsernames(), ANONYMIZED_USERNAME, FORWARD_SLASH);
97 return anonymousString;
98 }
99
100 // Most paths in CyberTriage are normalized with forward slashes
101 // but there can still be strings containing paths that are not normalized such paths contained in arguments or event log payloads
102 private String anonymizeUserFromPathsWithBackSlashes(String stringWithUsername) {
103 String anonymousString = stringWithUsername;
104 anonymousString = regexReplace(anonymousString, USER_PATH_BACK_SLASH_REGEX_XP, USERNAME_REGEX_REPLACEMENT);
105 anonymousString = regexReplace(anonymousString, USER_PATH_BACK_SLASH_REGEX, USERNAME_REGEX_REPLACEMENT);
106 anonymousString = replaceFolder(anonymousString, getUsernames(), ANONYMIZED_USERNAME, BACK_SLASH);
107
108 return anonymousString;
109 }
110
111 private String anonymizeServerFromUNCPath(String inputString) {
112
113 Set<String> serverNames = new HashSet<>();
114 String anonymousString = inputString.toLowerCase(Locale.ENGLISH);
115
116 Matcher forwardSlashMatcher = UNC_PATH_FORWARD_SLASH_PATTERN.matcher(anonymousString);
117 while (forwardSlashMatcher.find()) {
118 String serverName = forwardSlashMatcher.group(2);
119 serverNames.add(serverName);
120 }
121
122 Matcher backSlashMatcher = UNC_PATH_BACK_SLASH_PATTERN.matcher(anonymousString);
123 while (backSlashMatcher.find()) {
124 String serverName = backSlashMatcher.group(2);
125 serverNames.add(serverName);
126 }
127
128 for (String serverName : serverNames) {
129
130 if (StringUtils.isBlank(serverName)) {
131 continue;
132 }
133
134 if (InetAddresses.isInetAddress(serverName) && isLocalIP(serverName)) {
135 anonymousString = replaceFolder(anonymousString, Collections.singletonList(serverName), ANONYMIZED_IP);
136 } else {
137 anonymousString = replaceFolder(anonymousString, Collections.singletonList(serverName), ANONYMIZED_HOSTNAME);
138 }
139
140 }
141
142 return anonymousString;
143 }
144
145 private static String regexReplace(String orig, Pattern pattern, String regexReplacement) {
146 Matcher matcher = pattern.matcher(orig);
147 return matcher.replaceAll(regexReplacement);
148 }
149
150 private static String replaceFolder(String orig, List<String> valuesToReplace, String replacementValue) {
151 String anonymized = orig;
152 anonymized = replaceFolder(anonymized, valuesToReplace, replacementValue, FORWARD_SLASH);
153 anonymized = replaceFolder(anonymized, valuesToReplace, replacementValue, BACK_SLASH);
154 return anonymized;
155 }
156
157 private static String replaceFolder(String orig, List<String> valuesToReplace, String replacementValue, String folderDelimiter) {
158 if (orig == null || valuesToReplace == null) {
159 return orig;
160 }
161
162 String anonymousString = orig;
163
164 // ensure non-null
165 folderDelimiter = StringUtils.defaultString(folderDelimiter);
166 replacementValue = StringUtils.defaultString(replacementValue);
167
168 // replace
169 for (String valueToReplace : valuesToReplace) {
170 if (StringUtils.isNotEmpty(valueToReplace)) {
171 anonymousString = anonymousString.replace(
172 folderDelimiter + valueToReplace + folderDelimiter,
173 folderDelimiter + replacementValue + folderDelimiter);
174 }
175 }
176
177 return anonymousString;
178 }
179
190 public static boolean isLocalIP(String ipAddress) {
191 try {
192 InetAddress a = InetAddresses.forString(ipAddress);
193 return a.isAnyLocalAddress() || a.isSiteLocalAddress()
194 || a.isLoopbackAddress() || a.isLinkLocalAddress();
195 } catch (IllegalArgumentException ex) {
196 LOGGER.log(Level.WARNING, "Invalid IP string", ex);
197 return false;
198 }
199 }
200
201}

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.