This commit is contained in:
Greg DiCristofaro 2023-07-21 10:43:56 -04:00
parent b5b196fd65
commit eb7bbfd75c
6 changed files with 103 additions and 54 deletions

View File

@ -30,7 +30,10 @@ import java.security.UnrecoverableKeyException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.logging.Level;
import javax.net.ssl.KeyManager;
@ -39,6 +42,8 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
@ -121,17 +126,33 @@ public class CTCloudHttpClient {
null
);
}
public <O> O doPost(String urlPath, Object jsonBody, Class<O> classType) throws CTCloudException {
return doPost(urlPath, Collections.emptyMap(), jsonBody, classType);
}
public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jsonBody, Class<O> classType) throws CTCloudException {
String url = Constants.CT_CLOUD_SERVER + urlPath;
try {
LOGGER.log(Level.INFO, "initiating http connection to ctcloud server");
try (CloseableHttpClient httpclient = createConnection(getProxySettings(), sslContext)) {
URIBuilder builder = new URIBuilder(url);
if (!MapUtils.isEmpty(urlReqParams)) {
for (Entry<String, String> e : urlReqParams.entrySet()) {
String key = e.getKey();
String value = e.getValue();
if (StringUtils.isNotBlank(key) || StringUtils.isNotBlank(value)) {
builder.addParameter(key, value);
}
}
}
URI postURI = builder.build();
HttpPost postRequest = new HttpPost(postURI);
configureRequestTimeout(postRequest);
postRequest.setHeader("Content-type", "application/json");

View File

@ -24,7 +24,9 @@ import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseRequest;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.util.CTHostIDGenerationUtil;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.sleuthkit.autopsy.coreutils.Version;
@ -38,9 +40,8 @@ public class CTApiDAO {
private static final String AUTH_TOKEN_REQUEST_PATH = "/_ah/api/auth/v2/generate_token";
private static final String CTCLOUD_SERVER_HASH_PATH = "/_ah/api/reputation/v1/query/file/hash/md5?query_types=CORRELATION,MALWARE";
private static final String AUTOPSY_PRODUCT = "AUTOPSY";
private static final CTApiDAO instance = new CTApiDAO();
private CTApiDAO() {
}
@ -52,9 +53,8 @@ public class CTApiDAO {
private static String getAppVersion() {
return Version.getVersion();
}
private final CTCloudHttpClient httpClient = CTCloudHttpClient.getInstance();
private final CTCloudHttpClient httpClient = CTCloudHttpClient.getInstance();
public LicenseResponse getLicenseInfo(String licenseString) throws CTCloudException {
LicenseRequest licenseRequest = new LicenseRequest()
@ -76,18 +76,31 @@ public class CTApiDAO {
return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class);
}
private static Map<String, String> getAuthParams(AuthenticatedRequestData authenticatedRequestData) {
return new HashMap<String, String>() {
{
put("api_key", authenticatedRequestData.getApiKey());
put("token", authenticatedRequestData.getToken());
put("host_id", authenticatedRequestData.getHostId());
}
};
}
public List<CTCloudBean> getReputationResults(AuthenticatedRequestData authenticatedRequestData, List<String> md5Hashes) throws CTCloudException {
if (CollectionUtils.isEmpty(md5Hashes)) {
return Collections.emptyList();
}
FileReputationRequest fileRepReq = new FileReputationRequest()
.setApiKey(authenticatedRequestData.getApiKey())
.setHostId(CTHostIDGenerationUtil.generateLicenseHostID())
.setToken(authenticatedRequestData.getToken())
.setHashes(md5Hashes);
CTCloudBeanResponse resp = httpClient.doPost(CTCLOUD_SERVER_HASH_PATH, fileRepReq, CTCloudBeanResponse.class);
CTCloudBeanResponse resp = httpClient.doPost(
CTCLOUD_SERVER_HASH_PATH,
getAuthParams(authenticatedRequestData),
fileRepReq,
CTCloudBeanResponse.class
);
return resp == null || resp.getItems() == null
? Collections.emptyList()
: resp.getItems();

View File

@ -24,13 +24,16 @@ import java.time.Instant;
* POJO for an auth token response.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class AuthTokenResponse extends AuthenticatedRequestData {
public class AuthTokenResponse {
private final Long hashLookupCount;
private final Long hashLookupLimit;
private final Long fileUploadLimit;
private final Long fileUploadCount;
private final String fileUploadUrl;
private final Instant expiration;
private final String token;
private final String apiKey;
@JsonCreator
public AuthTokenResponse(
@ -41,7 +44,7 @@ public class AuthTokenResponse extends AuthenticatedRequestData {
@JsonProperty("fileUploadLimit") Long fileUploadLimit,
@JsonProperty("fileUploadCount") Long fileUploadCount,
@JsonProperty("fileUploadUrl") String fileUploadUrl,
@JsonDeserialize(using=InstantEpochSecsDeserializer.class)
@JsonDeserialize(using = InstantEpochSecsDeserializer.class)
@JsonProperty("expiration") Instant expiration
) {
this.token = token;
@ -77,4 +80,13 @@ public class AuthTokenResponse extends AuthenticatedRequestData {
public Instant getExpiration() {
return expiration;
}
public String getToken() {
return token;
}
public String getApiKey() {
return apiKey;
}
}

View File

@ -13,17 +13,24 @@
************************************************************************** */
package com.basistech.df.cybertriage.autopsy.ctapi.json;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Data required for an authenticated request.
*/
public abstract class AuthenticatedRequestData {
public class AuthenticatedRequestData {
@JsonProperty("token")
protected String token;
@JsonProperty("api_key")
protected String apiKey;
private final String token;
private final String apiKey;
private final String hostId;
public AuthenticatedRequestData(DecryptedLicenseResponse decrypted, AuthTokenResponse authResp) {
this(authResp.getToken(), authResp.getApiKey(), decrypted.getLicenseHostId());
}
public AuthenticatedRequestData(String token, String apiKey, String hostId) {
this.token = token;
this.apiKey = apiKey;
this.hostId = hostId;
}
public String getToken() {
return token;
@ -33,4 +40,8 @@ public abstract class AuthenticatedRequestData {
return apiKey;
}
public String getHostId() {
return hostId;
}
}

View File

@ -19,14 +19,11 @@ import java.util.List;
/**
* Request for file reputation results.
*/
public class FileReputationRequest extends AuthenticatedRequestData {
public class FileReputationRequest {
@JsonProperty("hashes")
private List<String> hashes;
@JsonProperty("host_id")
private String hostId;
public List<String> getHashes() {
return hashes;
}
@ -35,24 +32,4 @@ public class FileReputationRequest extends AuthenticatedRequestData {
this.hashes = hashes;
return this;
}
public FileReputationRequest setToken(String token) {
this.token = token;
return this;
}
public FileReputationRequest setApiKey(String apiKey) {
this.apiKey = apiKey;
return this;
}
public String getHostId() {
return hostId;
}
public FileReputationRequest setHostId(String hostId) {
this.hostId = hostId;
return this;
}
}

View File

@ -15,6 +15,7 @@ package com.basistech.df.cybertriage.autopsy.malwarescan;
import com.basistech.df.cybertriage.autopsy.ctapi.CTApiDAO;
import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse;
import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthenticatedRequestData;
import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean;
import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseInfo;
import com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud.CTLicensePersistence;
@ -28,6 +29,7 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
@ -38,6 +40,7 @@ import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestModule;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.AnalysisResult;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Score;
@ -111,6 +114,7 @@ public class MalwareScanIngestModule implements FileIngestModule {
private boolean noMoreHashLookups = false;
private IngestModuleException startupException;
private long dsId = 0;
private long ingestJobId = 0;
@Messages({
"MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title=Hash Lookups Low",
@ -157,6 +161,7 @@ public class MalwareScanIngestModule implements FileIngestModule {
BlackboardArtifact.Category.ANALYSIS_RESULT);
fileTypeDetector = new FileTypeDetector();
dsId = context.getDataSource().getId();
ingestJobId = context.getJobId();
licenseInfo = licenseInfoOpt.get();
startupException = null;
noMoreHashLookups = false;
@ -256,9 +261,13 @@ public class MalwareScanIngestModule implements FileIngestModule {
}
// using auth token, get results
List<CTCloudBean> repResult = ctApiDAO.getReputationResults(authTokenResponse, md5Hashes);
List<CTCloudBean> repResult = ctApiDAO.getReputationResults(
new AuthenticatedRequestData(licenseInfo.getDecryptedLicense(), authTokenResponse),
md5Hashes
);
if (repResult != null && !repResult.isEmpty()) {
List<BlackboardArtifact> createdArtifacts = new ArrayList<>();
if (!CollectionUtils.isEmpty(repResult)) {
SleuthkitCase.CaseDbTransaction trans = null;
try {
trans = tskCase.beginTransaction();
@ -270,7 +279,10 @@ public class MalwareScanIngestModule implements FileIngestModule {
}
for (Long objId : objIds) {
createAnalysisResult(objId, result, trans);
AnalysisResult res = createAnalysisResult(objId, result, trans);
if (res != null) {
createdArtifacts.add(res);
}
}
}
@ -279,10 +291,15 @@ public class MalwareScanIngestModule implements FileIngestModule {
} finally {
if (trans != null) {
trans.rollback();
createdArtifacts.clear();
trans = null;
}
}
if (!CollectionUtils.isEmpty(createdArtifacts)) {
tskCase.getBlackboard().postArtifacts(createdArtifacts, Bundle.MalwareScanIngestModuleFactory_displayName(), ingestJobId);
}
// if we only processed part of the batch, after processing, notify that we are out of scans.
if (exceededScanLimit) {
noMoreHashLookups = true;
@ -309,22 +326,22 @@ public class MalwareScanIngestModule implements FileIngestModule {
"MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes=YES",
"MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No=NO"
})
private void createAnalysisResult(Long objId, CTCloudBean cloudBean, SleuthkitCase.CaseDbTransaction trans) throws Blackboard.BlackboardException {
private AnalysisResult createAnalysisResult(Long objId, CTCloudBean cloudBean, SleuthkitCase.CaseDbTransaction trans) throws Blackboard.BlackboardException {
if (objId == null || cloudBean == null || cloudBean.getMalwareResult() == null) {
return;
return null;
}
Score score = cloudBean.getMalwareResult().getCTScore() == null
? Score.SCORE_UNKNOWN
? Score.SCORE_UNKNOWN
: cloudBean.getMalwareResult().getCTScore().getTskCore();
String conclusion = score.getSignificance() == Score.Significance.NOTABLE || score.getSignificance() == Score.Significance.LIKELY_NOTABLE
? Bundle.MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes()
: Bundle.MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No();
String justification = cloudBean.getMalwareResult().getStatusDescription();
tskCase.getBlackboard().newAnalysisResult(
return tskCase.getBlackboard().newAnalysisResult(
malwareType,
objId,
dsId,
@ -333,7 +350,7 @@ public class MalwareScanIngestModule implements FileIngestModule {
MALWARE_CONFIG,
justification,
Collections.emptyList(),
trans);
trans).getAnalysisResult();
}
@Messages({
@ -356,8 +373,6 @@ public class MalwareScanIngestModule implements FileIngestModule {
ex);
} finally {
// set state to shut down and clear any remaining
malwareType = null;
fileTypeDetector = null;
noMoreHashLookups = false;
runState = RunState.SHUT_DOWN;
startupException = null;