Skip to content

Commit 3f1b57c

Browse files
committed
Initial commit
0 parents  commit 3f1b57c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+10857
-0
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ui/app/styles/vendors/* linguist-vendored

.github/issue_template.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# EDIT THIS TITLE BEFORE POSTING. Use this template for bug reports. If you'd like to request a feature, please be as descriptive as possible and delete the template except the first section (Request Type)
2+
3+
### Request Type
4+
(select Bug or Feature Request and **remove this line**)
5+
Bug / Feature Request
6+
7+
### Work Environment
8+
9+
| Question | Answer
10+
|---------------------------|--------------------
11+
| OS version (server) | Debian, Ubuntu, CentOS, RedHat, ...
12+
| OS version (client) | XP, Seven, 10, Ubuntu, ...
13+
| Cortex version / git hash | 1.x, hash of the commit
14+
| Package Type | Docker, Binary, From source
15+
| Browser type & version | If applicable
16+
17+
18+
### Problem Description
19+
Describe the problem/bug as clearly as possible.
20+
21+
### Steps to Reproduce
22+
1. step 1
23+
1. step 2
24+
1. step 3...
25+
26+
### Possible Solutions
27+
(keep this section if you have suggestions on how to solve the problem. **Otherwise delete it**)
28+
29+
### Complementary information
30+
(add anything that can help identifying the problem such as **logs**, **screenshots**, **configuration dumps** etc.)

.gitignore

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
logs
2+
target
3+
/.idea
4+
/.idea_modules
5+
/.classpath
6+
/.project
7+
/.settings
8+
/RUNNING_PID
9+
.cache-main
10+
.cache-tests
11+
*.py[cod]
12+
/report-templates/*.zip
13+
14+
/bin/
15+
!/bin/activator
16+
!/bin/activator.bat
17+
18+
conf/application.conf

AUTHORS

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Authors
2+
-------
3+
4+
* Thomas Franco <[email protected]> (lead developer, back-end)
5+
* Saâd Kadhi <[email protected]> (project leader, product management & design)
6+
* Jérôme Leonard <[email protected]> (developer, analyzers)
7+
8+
Contributors
9+
------------
10+
11+
* Nabil Adouani
12+
* CERT Banque de France (CERT-BDF)
13+
14+
Copyright (C) 2016-2017 Thomas Franco
15+
Copyright (C) 2016-2017 Saâd Kadhi
16+
Copyright (C) 2016-2017 Jérôme Leonard

LICENSE

+661
Large diffs are not rendered by default.

README.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
2+
**Cortex** tries to solve a common problem frequently encountered by SOCs, CSIRTs and security researchers in the course of threat intelligence, digital forensics and incident response: how to **analyze observables** they have collected, **at scale**, **by querying a single tool** instead of several?
3+
4+
Cortex, an open source and free software, has been created by [TheHive Project](https://thehive-project.org) for this very purpose. Observables, such as IP and email addresses, URLs, domain names, files or hashes, can be analyzed one by one or in bulk mode using a Web interface. Analysts can also **automate** these operations thanks to the Cortex REST API.
5+
![](images/cortex-analyzers.png)
6+
7+
By using Cortex, you won't need to rewrite the wheel every time you'd like to use a service or a tool to analyze an observable and help you investigate the case at hand. Leverage one of the several analyzers it contains and if you are missing a tool or a service, create a suitable program easily and make it available for the whole team (or better, [for the whole community](https://github.com/CERT-BDF/cortex-analyzers/)) thanks to Cortex.
8+
9+
# Cortex and TheHive
10+
Along with [MISP](http://www.misp-project.org/), Cortex is the perfect companion for [TheHive](https://thehive-project.org). Starting from Buckfast (TheHive version 2.10), you can analyze tens or hundreds of observables in a few clicks using one or several Cortex instances depending on your OPSEC needs and security requirements. Moreover, TheHive comes with a report template engine that allows you to adjust the output of Cortex analyzers to your taste instead of having to create your own JSON parsers for Cortex output.
11+
12+
# Architecture
13+
Cortex is written in Scala. The front-end uses AngularJS with Bootstrap. Its REST API is stateless which allows it to be horizontally scalable. The provided analyzers are written in Python. Additional analyzers may be written using the same language or any other language supported by Linux.
14+
15+
<p align="center">
16+
<img src="images/cortex-architecture.png" alt="Cortex Architecture" width="400">
17+
</p>
18+
19+
20+
# Analyzers
21+
Cortex 1.0.0 is provided with 13 analyzers.
22+
23+
+ Abuse Finder: use CERT-SG's [Abuse Finder](https://github.com/certsocietegenerale/abuse_finder) to find the abuse contact associated with domain names, URLs, IP and email addresses.
24+
+ DNSDB\*: leverage Farsight's [DNSDB](https://www.dnsdb.info/) for pDNS.
25+
+ DomainTools\*: look up domain names, IP addresses, WHOIS records, etc. using the popular [DomainTools](http://domaintools.com/) service API.
26+
+ File Info: parse files in several formats such as OLE and OpenXML to detect VBA macros, extract their source code, generate useful information on PE, PDF files and much more.
27+
+ Hippocampe: query threat feeds through [Hippocampe](https://github.com/CERT-BDF/Hippocampe), a FOSS tool that centralizes feeds and allows you to associate a confidence level to each one of them (that can be changed over time) and get a score indicating the data quality.
28+
+ MaxMind: geolocation.
29+
+ Outlook MsgParser: parse Outlook message files automatically and show the key information it contains such as headers, attachments etc.
30+
+ OTXQuery\*: query AlienVault [Open Threat Exchange](https://otx.alienvault.com/) for IPs, domains, URLs, or file hashes.
31+
+ PassiveTotal\*: leverage [RiskIQ's PassiveTotal](https://www.passivetotal.org/) service to gain invaluable insight on observables, identify overlapping infrastructure using Passive DNS, WHOIS, SSL certificates and more.
32+
+ URLCategory: checks the Fortinet categories of URLs.
33+
+ Phishing Initiative\*: queries [Phishing Initiative](https://phishing-initiative.fr/contrib/) to assess whether a URL has been flagged a phishing site.
34+
+ PhishTank\*: queries [PhishTank](https://www.phishtank.com/) to assess whether a URL has been flagged a phishing site.
35+
+ VirusTotal\*: look up files, URLs and hashes through [VirusTotal](https://www.virustotal.com/).
36+
37+
The star (\*) indicates that the analyzer needs an API key to work correctly. **We do not provide API keys**. You have to use your own.
38+
39+
# License
40+
Cortex is an open source and free software released under the [AGPL](https://github.com/CERT-BDF/Cortex/blob/master/LICENSE) (Affero General Public License). We, TheHive Project, are committed to ensure that Cortex will remain a free and open source project on the long-run.
41+
42+
# Updates
43+
Information, news and updates are regularly posted on [TheHive Project Twitter account](https://twitter.com/thehive_project) and on [the blog](https://blog.thehive-project.org/).
44+
45+
# Contributing
46+
We welcome your contributions, **[particularly new analyzers](https://github.com/CERT-BDF/cortex-analyzers/)** that can take away the load off overworked fellow analysts. Please feel free to fork the code, play with it, make some patches and send us pull requests.
47+
48+
# Support
49+
Please [open an issue on GitHub](https://github.com/CERT-BDF/Cortex/issues) if you'd like to report a bug or request a feature.
50+
51+
**Important Note**: if you encounter an issue with an analyzer or would like to request a new one or an improvement to an existing analyzer, please open an issue on the [analyzers' dedicated GitHub repository](https://github.com/CERT-BDF/cortex-analyzers/issues/new). If you have problems with TheHive or would like to request a TheHive-related feature, please [open an issue on its dedicated GitHub repository](https://github.com/CERT-BDF/TheHive/issues/new).
52+
53+
Alternatively, if you need to contact the project team, send an email to <[email protected]>.
54+
55+
# Community Discussions
56+
We have set up a Google forum at <https://groups.google.com/a/thehive-project.org/d/forum/users>. To request access, you need a Google account. You may create one [using a Gmail address](https://accounts.google.com/SignUp?hl=en) or [without one](https://accounts.google.com/SignUpWithoutGmail?hl=en).
57+
58+
# Website
59+
<https://thehive-project.org/>

app/Module.scala

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import play.api.{ Configuration, Environment, Mode }
2+
import play.api.libs.concurrent.AkkaGuiceSupport
3+
4+
import com.google.inject.AbstractModule
5+
6+
import net.codingwell.scalaguice.ScalaModule
7+
8+
import controllers.{ AssetCtrl, AssetCtrlDev, AssetCtrlProd }
9+
import services.JobActor
10+
11+
class Module(environment: Environment, configuration: Configuration) extends AbstractModule with ScalaModule with AkkaGuiceSupport {
12+
13+
override def configure() = {
14+
bindActor[JobActor]("JobActor")
15+
16+
if (environment.mode == Mode.Prod)
17+
bind[AssetCtrl].to[AssetCtrlProd]
18+
else
19+
bind[AssetCtrl].to[AssetCtrlDev]
20+
}
21+
}

app/controllers/Analyzer.scala

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package controllers
2+
3+
import javax.inject.Inject
4+
5+
import scala.annotation.implicitNotFound
6+
import scala.concurrent.{ ExecutionContext, Future }
7+
8+
import play.api.libs.json.{ JsObject, JsString, Json }
9+
import play.api.mvc.{ Action, AnyContent, Controller, Request }
10+
11+
import models.{ DataArtifact, FileArtifact }
12+
import models.JsonFormat.{ analyzerWrites, dataActifactReads, jobWrites }
13+
import services.{ AnalyzerSrv, JobSrv }
14+
15+
class AnalyzerCtrl @Inject() (
16+
analyzerSrv: AnalyzerSrv,
17+
jobSrv: JobSrv,
18+
implicit val ec: ExecutionContext) extends Controller {
19+
20+
def list = Action { request
21+
Ok(Json.toJson(analyzerSrv.list))
22+
}
23+
24+
def get(analyzerId: String) = Action { request
25+
analyzerSrv.get(analyzerId) match {
26+
case Some(analyzer) Ok(Json.toJson(analyzer))
27+
case None NotFound
28+
}
29+
}
30+
31+
private[controllers] def readDataArtifact(request: Request[AnyContent]) = {
32+
for {
33+
json request.body.asJson
34+
artifact json.asOpt[DataArtifact]
35+
} yield artifact
36+
}
37+
38+
private[controllers] def readFileArtifact(request: Request[AnyContent]) = {
39+
for {
40+
parts request.body.asMultipartFormData
41+
filePart parts.file("data").headOption
42+
attrList parts.dataParts.get("_json")
43+
attrStr attrList.headOption
44+
attr Json.parse(attrStr).asOpt[JsObject]
45+
} yield FileArtifact(filePart.ref.file, attr +
46+
("content-type" JsString(filePart.contentType.getOrElse("application/octet-stream"))) +
47+
("filename" JsString(filePart.filename)))
48+
}
49+
50+
def analyze(analyzerId: String) = Action.async { request
51+
readDataArtifact(request)
52+
.orElse(readFileArtifact(request))
53+
.map { artifact
54+
jobSrv.create(artifact, analyzerId)
55+
.map(j Ok(Json.toJson(j)))
56+
}
57+
.getOrElse(Future.successful(BadRequest("???")))
58+
}
59+
60+
def listForType(dataType: String) = Action { request
61+
Ok(Json.toJson(analyzerSrv.listForType(dataType)))
62+
}
63+
}

app/controllers/Asset.scala

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package controllers
2+
3+
import javax.inject.{ Inject, Singleton }
4+
5+
import play.api.Environment
6+
import play.api.http.HttpErrorHandler
7+
import play.api.mvc.{ Action, AnyContent, Controller }
8+
9+
trait AssetCtrl {
10+
def get(file: String): Action[AnyContent]
11+
}
12+
13+
@Singleton
14+
class AssetCtrlProd @Inject() (errorHandler: HttpErrorHandler) extends Assets(errorHandler) with AssetCtrl {
15+
def get(file: String) = at("/ui", file)
16+
}
17+
18+
@Singleton
19+
class AssetCtrlDev @Inject() (environment: Environment) extends ExternalAssets(environment) with AssetCtrl {
20+
def get(file: String) = {
21+
if (file.startsWith("bower_components/")) {
22+
at("ui", file)
23+
}
24+
else {
25+
at("ui/app", file)
26+
}
27+
}
28+
}

app/controllers/Job.scala

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package controllers
2+
3+
import javax.inject.Inject
4+
5+
import scala.annotation.implicitNotFound
6+
import scala.concurrent.ExecutionContext
7+
import scala.concurrent.duration.Duration
8+
import scala.util.{ Failure, Success }
9+
10+
import play.api.libs.json.{ JsString, Json }
11+
import play.api.mvc.{ Action, Controller }
12+
13+
import models.JsonFormat.{ jobStatusWrites, jobWrites }
14+
import services.JobSrv
15+
16+
class JobCtrl @Inject() (
17+
jobSrv: JobSrv,
18+
implicit val ec: ExecutionContext) extends Controller {
19+
def list(dataTypeFilter: Option[String], dataFilter: Option[String], analyzerFilter: Option[String], start: Int, limit: Int) = Action.async { request
20+
jobSrv.list(dataTypeFilter, dataFilter, analyzerFilter, start, limit).map {
21+
case (total, jobs) Ok(Json.toJson(jobs)).withHeaders("X-Total" total.toString)
22+
}
23+
}
24+
25+
def get(jobId: String) = Action.async { request
26+
jobSrv.get(jobId).map { job
27+
Ok(Json.toJson(job))
28+
}
29+
}
30+
31+
def remove(jobId: String) = Action.async { request
32+
jobSrv.remove(jobId).map(_ Ok(""))
33+
}
34+
35+
def report(jobId: String) = Action.async { request
36+
jobSrv
37+
.get(jobId)
38+
.map { job
39+
val report = job.report.value match {
40+
case Some(Success(report)) report
41+
case Some(Failure(error)) JsString(error.getMessage)
42+
case None JsString("Running")
43+
}
44+
Ok(jobWrites.writes(job) +
45+
("status" jobStatusWrites.writes(job.status)) +
46+
("report" report))
47+
}
48+
}
49+
50+
def waitReport(jobId: String, atMost: String) = Action.async { request
51+
for {
52+
job jobSrv.get(jobId)
53+
(status, report) jobSrv.waitReport(jobId, Duration(atMost))
54+
} yield Ok(jobWrites.writes(job) +
55+
("status" jobStatusWrites.writes(job.status)) +
56+
("report" report))
57+
}
58+
}

app/models/Analyzer.scala

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package models
2+
3+
import scala.concurrent.Future
4+
import play.api.libs.json.JsObject
5+
6+
abstract class Analyzer {
7+
def analyze(artifact: Artifact): Future[JsObject]
8+
val name: String
9+
val version: String
10+
val description: String
11+
val dataTypeList: Seq[String]
12+
val id = (name + "_" + version).replaceAll("\\.", "_")
13+
}

app/models/Artifact.scala

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package models
2+
3+
import play.api.libs.json.JsObject
4+
import java.io.File
5+
6+
abstract class Artifact(attributes: JsObject) {
7+
def dataTypeFilter(filter: String): Boolean = (attributes \ "dataType").asOpt[String].fold(false)(_.toLowerCase.contains(filter.toLowerCase))
8+
def dataFilter(filter: String): Boolean = false
9+
}
10+
class FileArtifact(val data: File, val attributes: JsObject) extends Artifact(attributes) {
11+
override def finalize {
12+
data.delete()
13+
}
14+
}
15+
object FileArtifact {
16+
def apply(data: File, attributes: JsObject) = {
17+
val tempFile = File.createTempFile("cortex-", "-datafile")
18+
data.renameTo(tempFile)
19+
new FileArtifact(tempFile, attributes)
20+
}
21+
def unapply(fileArtifact: FileArtifact) = Some(fileArtifact.data fileArtifact.attributes)
22+
}
23+
case class DataArtifact(data: String, attributes: JsObject) extends Artifact(attributes) {
24+
override def dataFilter(filter: String): Boolean = data.toLowerCase.contains(filter.toLowerCase)
25+
}

0 commit comments

Comments
 (0)