Autopsy  4.16.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.Collections;
28 import java.util.List;
29 import java.util.Set;
30 import java.util.HashSet;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.StringJoiner;
34 import org.apache.commons.lang3.tuple.Pair;
42 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD;
43 import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY;
44 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN;
45 import org.sleuthkit.datamodel.CaseDbAccessManager;
46 import org.sleuthkit.datamodel.CaseDbAccessManager.CaseDbAccessQueryCallback;
47 import org.sleuthkit.datamodel.Content;
48 import org.sleuthkit.datamodel.SleuthkitCase;
49 import org.sleuthkit.datamodel.TskCoreException;
50 
56 class DomainSearchCacheLoader extends CacheLoader<SearchKey, Map<GroupKey, List<Result>>> {
57 
58  @Override
59  public Map<GroupKey, List<Result>> load(SearchKey key) throws DiscoveryException, SQLException, TskCoreException {
60 
61  List<Result> domainResults = getResultDomainsFromDatabase(key);
62 
63  // Apply secondary in memory filters
64  for (AbstractFilter filter : key.getFilters()) {
65  if (filter.useAlternateFilter()) {
66  domainResults = filter.applyAlternateFilter(domainResults, key.getSleuthkitCase(), key.getCentralRepository());
67  }
68  }
69 
70  // Grouping by CR Frequency, for example, will require further processing
71  // in order to make the correct decision. The attribute types that require
72  // more information implement their logic by overriding `addAttributeToResults`.
73  List<AttributeType> searchAttributes = new ArrayList<>();
74  searchAttributes.add(key.getGroupAttributeType());
75  searchAttributes.addAll(key.getFileSortingMethod().getRequiredAttributes());
76 
77  for (AttributeType attr : searchAttributes) {
78  attr.addAttributeToResults(domainResults,
79  key.getSleuthkitCase(), key.getCentralRepository());
80  }
81 
82  // Sort the ResultDomains by the requested criteria.
83  final SearchResults searchResults = new SearchResults(
84  key.getGroupSortingType(),
85  key.getGroupAttributeType(),
86  key.getFileSortingMethod());
87  searchResults.add(domainResults);
88  return searchResults.toLinkedHashMap();
89  }
90 
99  List<Result> getResultDomainsFromDatabase(SearchKey key) throws TskCoreException, SQLException, DiscoveryException {
100 
101  // Filters chosen in the UI are aggregated into SQL statements to be used in
102  // the queries that follow.
103  final Pair<String, String> filterClauses = createWhereAndHavingClause(key.getFilters());
104  final String whereClause = filterClauses.getLeft();
105  final String havingClause = filterClauses.getRight();
106 
107  // You may think of each row of this result as a TSK_DOMAIN attribute, where the parent
108  // artifact type is within the (optional) filter and the parent artifact
109  // had a date time attribute that was within the (optional) filter. With this
110  // table in hand, we can simply group by domain and apply aggregate functions
111  // to get, for example, # of downloads, # of visits in last 60, etc.
112  final String domainsTable
113  = "SELECT LOWER(MAX(value_text)) AS domain,"
114  + " MAX(value_int64) AS date,"
115  + " artifact_id AS parent_artifact_id,"
116  + " MAX(artifact_type_id) AS parent_artifact_type_id "
117  + "FROM blackboard_attributes "
118  + "WHERE " + whereClause + " "
119  + "GROUP BY artifact_id "
120  + "HAVING " + havingClause;
121 
122  // Needed to populate the visitsInLast60 data.
123  final Instant currentTime = Instant.now();
124  final Instant sixtyDaysAgo = currentTime.minus(60, ChronoUnit.DAYS);
125 
126  // Check the group attribute, if by data source then the GROUP BY clause
127  // should group by data source id before grouping by domain.
128  final AttributeType groupAttribute = key.getGroupAttributeType();
129  final String groupByClause = (groupAttribute instanceof DataSourceAttribute)
130  ? "data_source_obj_id, domain" : "domain";
131 
132  final Optional<AbstractFilter> dataSourceFilter = key.getFilters().stream()
133  .filter(filter -> filter instanceof DataSourceFilter)
134  .findFirst();
135 
136  String dataSourceWhereClause = null;
137  if (dataSourceFilter.isPresent()) {
138  dataSourceWhereClause = dataSourceFilter.get().getWhereClause();
139  }
140 
141  // This query just processes the domains table, performing additional
142  // groupings and applying aggregate functions to calculate discovery data.
143  final String domainsQuery
144  = /*
145  * SELECT
146  */ " domain,"
147  + " MIN(date) AS activity_start,"
148  + " MAX(date) AS activity_end,"
149  + " SUM(CASE "
150  + " WHEN artifact_type_id = " + TSK_WEB_DOWNLOAD.getTypeID() + " THEN 1 "
151  + " ELSE 0 "
152  + " END) AS fileDownloads,"
153  + " SUM(CASE "
154  + " WHEN artifact_type_id = " + TSK_WEB_HISTORY.getTypeID() + " THEN 1 "
155  + " ELSE 0 "
156  + " END) AS totalVisits,"
157  + " SUM(CASE "
158  + " WHEN artifact_type_id = " + TSK_WEB_HISTORY.getTypeID() + " AND"
159  + " date BETWEEN " + sixtyDaysAgo.getEpochSecond() + " AND " + currentTime.getEpochSecond() + " THEN 1 "
160  + " ELSE 0 "
161  + " END) AS last60,"
162  + " MAX(data_source_obj_id) AS dataSource "
163  + "FROM blackboard_artifacts"
164  + " JOIN (" + domainsTable + ") AS domains_table"
165  + " ON artifact_id = parent_artifact_id "
166  + // Add the data source where clause here if present.
167  ((dataSourceWhereClause != null) ? "WHERE " + dataSourceWhereClause + " " : "")
168  + "GROUP BY " + groupByClause;
169 
170  final SleuthkitCase caseDb = key.getSleuthkitCase();
171  final CaseDbAccessManager dbManager = caseDb.getCaseDbAccessManager();
172 
173  final DomainCallback domainCallback = new DomainCallback(caseDb);
174  dbManager.select(domainsQuery, domainCallback);
175 
176  if (domainCallback.getSQLException() != null) {
177  throw domainCallback.getSQLException();
178  }
179 
180  if (domainCallback.getTskCoreException() != null) {
181  throw domainCallback.getTskCoreException();
182  }
183 
184  return domainCallback.getResultDomains();
185  }
186 
200  Pair<String, String> createWhereAndHavingClause(List<AbstractFilter> filters) {
201  final StringJoiner whereClause = new StringJoiner(" OR ");
202  final StringJoiner havingClause = new StringJoiner(" AND ");
203 
204  String artifactTypeFilter = null;
205  boolean hasDateTimeFilter = false;
206 
207  for (AbstractFilter filter : filters) {
208  if (filter instanceof ArtifactTypeFilter) {
209  artifactTypeFilter = filter.getWhereClause();
210  } else if (!(filter instanceof DataSourceFilter) && !filter.useAlternateFilter()) {
211  if (filter instanceof ArtifactDateRangeFilter) {
212  hasDateTimeFilter = true;
213  }
214 
215  whereClause.add("(" + filter.getWhereClause() + ")");
216  havingClause.add("SUM(CASE WHEN " + filter.getWhereClause() + " THEN 1 ELSE 0 END) > 0");
217  }
218  }
219 
220  if (!hasDateTimeFilter) {
221  whereClause.add(ArtifactDateRangeFilter.createAttributeTypeClause());
222  }
223 
224  String domainAttributeFilter = "attribute_type_id = " + TSK_DOMAIN.getTypeID()
225  + " AND value_text <> ''";
226 
227  whereClause.add("(" + domainAttributeFilter + ")");
228  havingClause.add("SUM(CASE WHEN " + domainAttributeFilter + " THEN 1 ELSE 0 END) > 0");
229 
230  return Pair.of(
231  whereClause.toString() + ((artifactTypeFilter != null) ? " AND (" + artifactTypeFilter + ")" : ""),
232  havingClause.toString()
233  );
234  }
235 
241  private class DomainCallback implements CaseDbAccessQueryCallback {
242 
243  private final List<Result> resultDomains;
244  private final SleuthkitCase skc;
245  private SQLException sqlCause;
246  private TskCoreException coreCause;
247 
248  private final Set<String> bannedDomains = new HashSet<String>() {{
249  add("localhost");
250  add("127.0.0.1");
251  }};
252 
258  private DomainCallback(SleuthkitCase skc) {
259  this.resultDomains = new ArrayList<>();
260  this.skc = skc;
261  }
262 
263  @Override
264  public void process(ResultSet resultSet) {
265  try {
266  resultSet.setFetchSize(500);
267 
268  while (resultSet.next()) {
269  String domain = resultSet.getString("domain");
270 
271  if (bannedDomains.contains(domain)) {
272  // Skip banned domains
273  // Domain names are lowercased in the SQL query
274  continue;
275  }
276 
277  Long activityStart = resultSet.getLong("activity_start");
278  if (resultSet.wasNull()) {
279  activityStart = null;
280  }
281  Long activityEnd = resultSet.getLong("activity_end");
282  if (resultSet.wasNull()) {
283  activityEnd = null;
284  }
285  Long filesDownloaded = resultSet.getLong("fileDownloads");
286  if (resultSet.wasNull()) {
287  filesDownloaded = null;
288  }
289  Long totalVisits = resultSet.getLong("totalVisits");
290  if (resultSet.wasNull()) {
291  totalVisits = null;
292  }
293 
294  Long visitsInLast60 = resultSet.getLong("last60");
295  if (resultSet.wasNull()) {
296  visitsInLast60 = null;
297  }
298  Long dataSourceID = resultSet.getLong("dataSource");
299 
300  Content dataSource = skc.getContentById(dataSourceID);
301 
302  resultDomains.add(new ResultDomain(domain, activityStart,
303  activityEnd, totalVisits, visitsInLast60, filesDownloaded, dataSource));
304  }
305  } catch (SQLException ex) {
306  this.sqlCause = ex;
307  } catch (TskCoreException ex) {
308  this.coreCause = ex;
309  }
310  }
311 
319  private List<Result> getResultDomains() {
320  return Collections.unmodifiableList(this.resultDomains);
321  }
322 
328  private SQLException getSQLException() {
329  return this.sqlCause;
330  }
331 
337  private TskCoreException getTskCoreException() {
338  return this.coreCause;
339  }
340  }
341 }

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