Autopsy  4.17.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
DomainSearchCacheLoader.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2020 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  */
19 package org.sleuthkit.autopsy.discovery.search;
20 
21 import com.google.common.cache.CacheLoader;
22 import java.sql.ResultSet;
23 import java.sql.SQLException;
24 import java.time.Instant;
25 import java.time.temporal.ChronoUnit;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Set;
31 import java.util.HashSet;
32 import java.util.Map;
33 import java.util.Optional;
34 import java.util.StringJoiner;
35 import org.apache.commons.lang3.tuple.Pair;
43 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD;
44 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY;
45 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_ACCOUNT_TYPE;
46 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN;
47 import org.sleuthkit.datamodel.CaseDbAccessManager;
48 import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
49 import org.sleuthkit.datamodel.Content;
50 import org.sleuthkit.datamodel.SleuthkitCase;
51 import org.sleuthkit.datamodel.TskCoreException;
52 
58 class DomainSearchCacheLoader extends CacheLoader<SearchKey, Map<GroupKey, List<Result>>> {
59 
60  @Override
61  public Map<GroupKey, List<Result>> load(SearchKey key) throws DiscoveryException, SQLException, TskCoreException, InterruptedException {
62  List<Result> domainResults = getResultDomainsFromDatabase(key);
63  // Grouping by CR Frequency, for example, will require further processing
64  // in order to make the correct decision. The attribute types that require
65  // more information implement their logic by overriding `addAttributeToResults`.
66  Set<AttributeType> searchAttributes = new HashSet<>();
67  searchAttributes.add(key.getGroupAttributeType());
68  searchAttributes.addAll(key.getFileSortingMethod().getRequiredAttributes());
69  for (AttributeType attr : searchAttributes) {
70  if (Thread.currentThread().isInterrupted()) {
71  throw new InterruptedException();
72  }
73  attr.addAttributeToResults(domainResults,
74  key.getSleuthkitCase(), key.getCentralRepository());
75  }
76  // Apply secondary in memory filters
77  for (AbstractFilter filter : key.getFilters()) {
78  if (Thread.currentThread().isInterrupted()) {
79  throw new InterruptedException();
80  }
81  if (filter.useAlternateFilter()) {
82  domainResults = filter.applyAlternateFilter(domainResults, key.getSleuthkitCase(), key.getCentralRepository());
83  }
84  }
85  // Sort the ResultDomains by the requested criteria.
86  final SearchResults searchResults = new SearchResults(
87  key.getGroupSortingType(),
88  key.getGroupAttributeType(),
89  key.getFileSortingMethod());
90  searchResults.add(domainResults);
91  return searchResults.toLinkedHashMap();
92  }
93 
102  List<Result> getResultDomainsFromDatabase(SearchKey key) throws TskCoreException, SQLException, DiscoveryException, InterruptedException {
103 
104  // Filters chosen in the UI are aggregated into SQL statements to be used in
105  // the queries that follow.
106  final Pair<String, String> filterClauses = createWhereAndHavingClause(key.getFilters());
107  final String whereClause = filterClauses.getLeft();
108  final String havingClause = filterClauses.getRight();
109 
110  // You may think of each row of this result as a TSK_DOMAIN attribute, where the parent
111  // artifact type is within the (optional) filter and the parent artifact
112  // had a date time attribute that was within the (optional) filter. With this
113  // table in hand, we can simply group by domain and apply aggregate functions
114  // to get, for example, # of downloads, # of visits in last 60, etc.
115  final String domainsTable
116  = "SELECT LOWER(MAX(value_text)) AS domain,"
117  + " MAX(value_int64) AS date,"
118  + " artifact_id AS parent_artifact_id,"
119  + " MAX(artifact_type_id) AS parent_artifact_type_id "
120  + "FROM blackboard_attributes "
121  + "WHERE " + whereClause + " "
122  + "GROUP BY artifact_id "
123  + "HAVING " + havingClause;
124 
125  // Needed to populate the visitsInLast60 data.
126  final Instant currentTime = Instant.now();
127  final Instant sixtyDaysAgo = currentTime.minus(60, ChronoUnit.DAYS);
128 
129  // Check the group attribute, if by data source then the GROUP BY clause
130  // should group by data source id before grouping by domain.
131  final AttributeType groupAttribute = key.getGroupAttributeType();
132  final String groupByClause = (groupAttribute instanceof DataSourceAttribute)
133  ? "data_source_obj_id, domain" : "domain";
134 
135  final Optional<AbstractFilter> dataSourceFilter = key.getFilters().stream()
136  .filter(filter -> filter instanceof DataSourceFilter)
137  .findFirst();
138 
139  String dataSourceWhereClause = null;
140  if (dataSourceFilter.isPresent()) {
141  dataSourceWhereClause = dataSourceFilter.get().getWhereClause();
142  }
143 
144  // This query just processes the domains table, performing additional
145  // groupings and applying aggregate functions to calculate discovery data.
146  final String domainsQuery
147  = /*
148  * SELECT
149  */ " domain,"
150  + " MIN(date) AS activity_start,"
151  + " MAX(date) AS activity_end,"
152  + " SUM(CASE "
153  + " WHEN artifact_type_id = " + TSK_WEB_DOWNLOAD.getTypeID() + " THEN 1 "
154  + " ELSE 0 "
155  + " END) AS fileDownloads,"
156  + " SUM(CASE "
157  + " WHEN artifact_type_id = " + TSK_WEB_HISTORY.getTypeID() + " THEN 1 "
158  + " ELSE 0 "
159  + " END) AS totalPageViews,"
160  + " SUM(CASE "
161  + " WHEN artifact_type_id = " + TSK_WEB_HISTORY.getTypeID() + " AND"
162  + " date BETWEEN " + sixtyDaysAgo.getEpochSecond() + " AND " + currentTime.getEpochSecond() + " THEN 1 "
163  + " ELSE 0 "
164  + " END) AS pageViewsInLast60,"
165  + " SUM(CASE "
166  + " WHEN artifact_type_id = " + TSK_WEB_ACCOUNT_TYPE.getTypeID() + " THEN 1 "
167  + " ELSE 0 "
168  + " END) AS countOfKnownAccountTypes,"
169  + " MAX(data_source_obj_id) AS dataSource "
170  + "FROM blackboard_artifacts"
171  + " JOIN (" + domainsTable + ") AS domains_table"
172  + " ON artifact_id = parent_artifact_id "
173  + // Add the data source where clause here if present.
174  ((dataSourceWhereClause != null) ? "WHERE " + dataSourceWhereClause + " " : "")
175  + "GROUP BY " + groupByClause;
176 
177  final SleuthkitCase caseDb = key.getSleuthkitCase();
178  final CaseDbAccessManager dbManager = caseDb.getCaseDbAccessManager();
179 
180  final DomainCallback domainCallback = new DomainCallback(caseDb);
181  dbManager.select(domainsQuery, domainCallback);
182 
183  if (domainCallback.getSQLException() != null) {
184  throw domainCallback.getSQLException();
185  }
186 
187  if (domainCallback.getTskCoreException() != null) {
188  throw domainCallback.getTskCoreException();
189  }
190 
191  if (domainCallback.getInterruptedException() != null) {
192  throw domainCallback.getInterruptedException();
193  }
194 
195  return domainCallback.getResultDomains();
196  }
197 
211  Pair<String, String> createWhereAndHavingClause(List<AbstractFilter> filters) {
212  final StringJoiner whereClause = new StringJoiner(" OR ");
213  final StringJoiner havingClause = new StringJoiner(" AND ");
214 
215  // Capture all types by default.
216  ArtifactTypeFilter artifactTypeFilter = new ArtifactTypeFilter(SearchData.Type.DOMAIN.getArtifactTypes());
217  boolean hasDateTimeFilter = false;
218 
219  for (AbstractFilter filter : filters) {
220  if (filter instanceof ArtifactTypeFilter) {
221  // Replace with user defined types.
222  artifactTypeFilter = ((ArtifactTypeFilter) filter);
223  } else if (!(filter instanceof DataSourceFilter) && !filter.useAlternateFilter()) {
224  if (filter instanceof ArtifactDateRangeFilter) {
225  hasDateTimeFilter = true;
226  }
227 
228  whereClause.add("(" + filter.getWhereClause() + ")");
229  havingClause.add("SUM(CASE WHEN " + filter.getWhereClause() + " THEN 1 ELSE 0 END) > 0");
230  }
231  }
232 
233  if (!hasDateTimeFilter) {
234  whereClause.add(ArtifactDateRangeFilter.createAttributeTypeClause());
235  }
236 
237  String domainAttributeFilter = "attribute_type_id = " + TSK_DOMAIN.getTypeID()
238  + " AND value_text <> ''";
239 
240  whereClause.add("(" + domainAttributeFilter + ")");
241  havingClause.add("SUM(CASE WHEN " + domainAttributeFilter + " THEN 1 ELSE 0 END) > 0");
242 
243  return Pair.of(
244  whereClause.toString() + " AND (" + artifactTypeFilter.getWhereClause(Arrays.asList(TSK_WEB_ACCOUNT_TYPE)) + ")",
245  havingClause.toString()
246  );
247  }
248 
254  private class DomainCallback implements CaseDbAccessQueryCallback {
255 
256  private final List<Result> resultDomains;
257  private final SleuthkitCase skc;
258  private SQLException sqlCause;
259  private TskCoreException coreCause;
260  private InterruptedException interruptedException;
261 
262  private final Set<String> bannedDomains = new HashSet<String>() {
263  {
264  add("localhost");
265  add("127.0.0.1");
266  }
267  };
268 
274  private DomainCallback(SleuthkitCase skc) {
275  this.resultDomains = new ArrayList<>();
276  this.skc = skc;
277  }
278 
279  @Override
280  public void process(ResultSet resultSet) {
281  try {
282  resultSet.setFetchSize(500);
283 
284  while (resultSet.next()) {
285  if (Thread.currentThread().isInterrupted()) {
286  throw new InterruptedException();
287  }
288 
289  String domain = resultSet.getString("domain");
290 
291  if (bannedDomains.contains(domain)) {
292  // Skip banned domains
293  // Domain names are lowercased in the SQL query
294  continue;
295  }
296 
297  long activityStart = resultSet.getLong("activity_start");
298  long activityEnd = resultSet.getLong("activity_end");
299  long filesDownloaded = resultSet.getLong("fileDownloads");
300  long totalPageViews = resultSet.getLong("totalPageViews");
301  long pageViewsInLast60 = resultSet.getLong("pageViewsInLast60");
302  long countOfKnownAccountTypes = resultSet.getLong("countOfKnownAccountTypes");
303  long dataSourceID = resultSet.getLong("dataSource");
304  Content dataSource = skc.getContentById(dataSourceID);
305 
306  resultDomains.add(new ResultDomain(domain, activityStart,
307  activityEnd, totalPageViews, pageViewsInLast60, filesDownloaded,
308  countOfKnownAccountTypes, dataSource));
309  }
310  } catch (SQLException ex) {
311  this.sqlCause = ex;
312  } catch (TskCoreException ex) {
313  this.coreCause = ex;
314  } catch (InterruptedException ex) {
315  this.interruptedException = ex;
316  }
317  }
318 
326  private List<Result> getResultDomains() {
327  return Collections.unmodifiableList(this.resultDomains);
328  }
329 
335  private SQLException getSQLException() {
336  return this.sqlCause;
337  }
338 
344  private TskCoreException getTskCoreException() {
345  return this.coreCause;
346  }
347 
354  private InterruptedException getInterruptedException() {
355  return this.interruptedException;
356  }
357  }
358 }

Copyright © 2012-2021 Basis Technology. Generated on: Tue Jan 19 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.