diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/UsernameAnonymizer.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/UsernameAnonymizer.java new file mode 100644 index 0000000000000000000000000000000000000000..2b00ece4ab43db397d307602ff1cf7b3ef20c6ae --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/UsernameAnonymizer.java @@ -0,0 +1,155 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.basistech.df.cybertriage.autopsy.malwarescan; + +import com.google.common.net.InetAddresses; +import java.net.InetAddress; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Utility class to anonymize username in paths also anonymizes hostname / ip + * from UNC paths + */ +public class UsernameAnonymizer { + + private static final Logger LOGGER = Logger.getLogger(UsernameAnonymizer.class.getName()); + + private final String USER_PATH_FORWARD_SLASH_REGEX = "(?<!all )([/]{0,1}\\Qusers\\E/)(?!(public|Default|defaultAccount|All Users))([^/]+)(/){0,1}"; + private final String USER_PATH_BACK_SLASH_REGEX = "(?<!all )([\\\\]{0,1}\\Qusers\\E\\\\)(?!(public|Default|defaultAccount|All Users))([^\\\\]+)([\\\\]){0,1}"; + + private final double WINDOWS_VERSION; + private final double DEFAULT_WINDOWS_VERSION = 10.0; + private final String USER_PATH_FORWARD_SLASH_REGEX_XP = "([/]{0,1}\\Qdocuments and settings\\E/)(?!(Default User|All Users))([^/]+)(/){0,1}"; + private final String USER_PATH_BACK_SLASH_REGEX_XP = "([\\\\]{0,1}\\Qdocuments and settings\\E\\\\)(?!(Default User|All Users))([^\\\\]+)(\\\\){0,1}"; + + private final Pattern UNC_PATH_FORWARD_SLASH_PATTERN = Pattern.compile("(//)([^/]+)(/){0,1}"); + private final Pattern UNC_PATH_BACK_SLASH_PATTERN = Pattern.compile("(\\\\\\\\)([^\\\\]+)(\\\\){0,1}"); + + public UsernameAnonymizer() { + // This constructor was added for the unit tests + // For most purposes, the other constructor should be used so we get the collection info such as users and windows version + + WINDOWS_VERSION = DEFAULT_WINDOWS_VERSION; + } + + public String anonymousUsername(String inputString) { + if (StringUtils.isBlank(inputString)) { + return ""; + } + + String anonymousString = anonymizeUserFromPathsWithForwardSlashes(inputString); + anonymousString = anonymizeUserFromPathsWithBackSlashes(anonymousString); + anonymousString = anonymizeServerFromUNCPath(anonymousString); + + return anonymousString; + } + + private String anonymizeUserFromPathsWithForwardSlashes(String stringWithUsername) { + Pattern pattern = WINDOWS_VERSION < 6 ? Pattern.compile(USER_PATH_FORWARD_SLASH_REGEX_XP, Pattern.CASE_INSENSITIVE) : Pattern.compile(USER_PATH_FORWARD_SLASH_REGEX, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(stringWithUsername.toLowerCase(Locale.ENGLISH)); + String replacement = ""; + while (matcher.find()) { + replacement = String.format("$1%s$4", "<user>"); + } + String anonymousString = matcher.replaceAll(replacement); + + return anonymousString; + } + + // Most paths in CyberTriage are normalized with forward slashes + // but there can still be strings containing paths that are not normalized such paths contained in arguments or event log payloads + private String anonymizeUserFromPathsWithBackSlashes(String stringWithUsername) { + Pattern pattern = WINDOWS_VERSION < 6 ? Pattern.compile(USER_PATH_BACK_SLASH_REGEX_XP, Pattern.CASE_INSENSITIVE) : Pattern.compile(USER_PATH_BACK_SLASH_REGEX, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(stringWithUsername.toLowerCase(Locale.ENGLISH)); + String replacement = ""; + while (matcher.find()) { + replacement = String.format("$1%s$4", "<user>"); + } + String anonymousString = matcher.replaceAll(replacement); + + return anonymousString; + } + + private String anonymizeServerFromUNCPath(String inputString) { + + Set<String> serverNames = new HashSet<>(); + String anonymousString = inputString.toLowerCase(Locale.ENGLISH); + + Matcher forwardSlashMatcher = UNC_PATH_FORWARD_SLASH_PATTERN.matcher(anonymousString); + while (forwardSlashMatcher.find()) { + String serverName = forwardSlashMatcher.group(2); + serverNames.add(serverName); + } + + Matcher backSlashMatcher = UNC_PATH_BACK_SLASH_PATTERN.matcher(anonymousString); + while (backSlashMatcher.find()) { + String serverName = backSlashMatcher.group(2); + serverNames.add(serverName); + } + + for (String serverName : serverNames) { + + if (StringUtils.isBlank(serverName)) { + continue; + } + + if (InetAddresses.isInetAddress(serverName)) { + if (isLocalIP(serverName)) { + anonymousString = StringUtils.replace(anonymousString, "\\" + serverName + "\\", "\\<private_ip>\\"); + anonymousString = StringUtils.replace(anonymousString, "/" + serverName + "/", "/<private_ip>/"); + } + } else { + anonymousString = StringUtils.replace(anonymousString, "\\" + serverName + "\\", "\\<hostname>\\"); + anonymousString = StringUtils.replace(anonymousString, "/" + serverName + "/", "/<hostname>/"); + } + + } + + return anonymousString; + } + + /** + * Returns true if IP Address is Any Local / Site Local / Link Local / Loop + * back local. Sample list "0.0.0.0", wildcard addres + * "10.1.1.1","10.10.10.10", site local address "127.0.0.0","127.2.2.2", + * loopback address "169.254.0.0","169.254.10.10", Link local address + * "172.16.0.0","172.31.245.245", site local address + * + * @param ipAddress + * @return + */ + public static boolean isLocalIP(String ipAddress) { + try { + InetAddress a = InetAddresses.forString(ipAddress); + return a.isAnyLocalAddress() || a.isSiteLocalAddress() + || a.isLoopbackAddress() || a.isLinkLocalAddress(); + } catch (IllegalArgumentException ex) { + LOGGER.log(Level.WARNING, "Invalid IP string", ex); + return false; + } + } + +}