Skip to content

Commit a8d3cc5

Browse files
committed
#237 Save alert attachment in datastore
1 parent 959b5b2 commit a8d3cc5

File tree

3 files changed

+62
-26
lines changed

3 files changed

+62
-26
lines changed

thehive-backend/app/models/Alert.scala

+25-7
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import javax.inject.{ Inject, Singleton }
55

66
import models.JsonFormat.alertStatusFormat
77
import org.elastic4play.controllers.JsonInputValue
8-
import org.elastic4play.{ AttributeCheckingError, InvalidFormatAttributeError }
9-
import org.elastic4play.models.{ Attribute, AttributeDef, BaseEntity, EntityDef, HiveEnumeration, ModelDef, AttributeFormat F, AttributeOption O }
8+
import org.elastic4play.models.{ Attribute, AttributeDef, BaseEntity, EntityDef, HiveEnumeration, ModelDef, MultiAttributeFormat, OptionalAttributeFormat, AttributeFormat F, AttributeOption O }
9+
import org.elastic4play.services.DBLists
1010
import org.elastic4play.utils.Hasher
11+
import org.elastic4play.{ AttributeCheckingError, InvalidFormatAttributeError }
1112
import play.api.Logger
1213
import play.api.libs.json._
1314
import services.AuditedModel
@@ -43,7 +44,7 @@ trait AlertAttributes {
4344
}
4445

4546
@Singleton
46-
class AlertModel @Inject() (artifactModel: ArtifactModel)
47+
class AlertModel @Inject() (dblists: DBLists)
4748
extends ModelDef[AlertModel, Alert]("alert")
4849
with AlertAttributes
4950
with AuditedModel {
@@ -52,17 +53,34 @@ class AlertModel @Inject() (artifactModel: ArtifactModel)
5253
override val defaultSortBy: Seq[String] = Seq("-date")
5354
override val removeAttribute: JsObject = Json.obj("status" AlertStatus.Ignored)
5455

55-
override def artifactAttributes: Seq[Attribute[_]] = artifactModel
56-
.attributes
57-
.filter(_.isForm)
56+
override def artifactAttributes: Seq[Attribute[_]] = {
57+
val remoteAttachmentAttributes = Seq(
58+
Attribute("alert", "reference", F.stringFmt, Nil, None, ""),
59+
Attribute("alert", "filename", OptionalAttributeFormat(F.stringFmt), Nil, None, ""),
60+
Attribute("alert", "contentType", OptionalAttributeFormat(F.stringFmt), Nil, None, ""),
61+
Attribute("alert", "size", OptionalAttributeFormat(F.numberFmt), Nil, None, ""),
62+
Attribute("alert", "hash", MultiAttributeFormat(F.stringFmt), Nil, None, ""),
63+
Attribute("alert", "type", OptionalAttributeFormat(F.stringFmt), Nil, None, ""))
64+
65+
Seq(
66+
Attribute("alert", "data", OptionalAttributeFormat(F.stringFmt), Nil, None, ""),
67+
Attribute("alert", "dataType", F.stringFmt, Nil, None, ""),
68+
Attribute("alert", "message", OptionalAttributeFormat(F.stringFmt), Nil, None, ""),
69+
Attribute("alert", "startDate", OptionalAttributeFormat(F.dateFmt), Nil, None, ""),
70+
Attribute("alert", "attachment", OptionalAttributeFormat(F.attachmentFmt), Nil, None, ""),
71+
Attribute("alert", "remoteAttachment", OptionalAttributeFormat(F.objectFmt(remoteAttachmentAttributes)), Nil, None, ""),
72+
Attribute("alert", "tlp", OptionalAttributeFormat(F.numberFmt), Nil, None, ""),
73+
Attribute("alert", "tags", MultiAttributeFormat(F.stringFmt), Nil, None, ""),
74+
Attribute("alert", "ioc", OptionalAttributeFormat(F.stringFmt), Nil, None, ""))
75+
}
5876

5977
override def creationHook(parent: Option[BaseEntity], attrs: JsObject): Future[JsObject] = {
6078
// check if data attribute is present on all artifacts
6179
val missingDataErrors = (attrs \ "artifacts")
6280
.asOpt[Seq[JsValue]]
6381
.getOrElse(Nil)
6482
.filter { a
65-
(a \ "data").toOption.isEmpty ||
83+
((a \ "data").toOption.isEmpty && (a \ "attachment").toOption.isEmpty && (a \ "remoteAttachment").toOption.isEmpty) ||
6684
((a \ "tags").toOption.isEmpty && (a \ "message").toOption.isEmpty)
6785
}
6886
.map(v InvalidFormatAttributeError("artifacts", "artifact", JsonInputValue(v)))

thehive-backend/app/services/AlertSrv.scala

+20-3
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import play.api.{ Configuration, Logger }
1717

1818
import scala.collection.immutable
1919
import scala.concurrent.{ ExecutionContext, Future }
20-
import scala.util.{ Failure, Success, Try }
20+
import scala.util.{ Failure, Try }
21+
import org.elastic4play.services.JsonFormat.attachmentFormat
2122

2223
trait AlertTransformer {
2324
def createCase(alert: Alert, customCaseTemplate: Option[String])(implicit authContext: AuthContext): Future[Case]
25+
2426
def mergeWithCase(alert: Alert, caze: Case)(implicit authContext: AuthContext): Future[Case]
2527
}
2628

@@ -76,8 +78,23 @@ class AlertSrv(
7678

7779
private[AlertSrv] lazy val logger = Logger(getClass)
7880

79-
def create(fields: Fields)(implicit authContext: AuthContext): Future[Alert] =
80-
createSrv[AlertModel, Alert](alertModel, fields)
81+
def create(fields: Fields)(implicit authContext: AuthContext): Future[Alert] = {
82+
83+
val artifactsFields =
84+
Future.traverse(fields.getValues("artifacts")) {
85+
case a: JsObject if (a \ "dataType").asOpt[String].contains("file")
86+
(a \ "data").asOpt[String] match {
87+
case Some(dataExtractor(filename, contentType, data))
88+
attachmentSrv.save(filename, contentType, java.util.Base64.getDecoder.decode(data))
89+
.map(attachment a - "data" + ("attachment" Json.toJson(attachment)))
90+
case _ Future.successful(a)
91+
}
92+
case a Future.successful(a)
93+
}
94+
artifactsFields.flatMap { af
95+
createSrv[AlertModel, Alert](alertModel, fields.set("artifacts", JsArray(af)))
96+
}
97+
}
8198

8299
def bulkCreate(fieldSet: Seq[Fields])(implicit authContext: AuthContext): Future[Seq[Try[Alert]]] =
83100
createSrv[AlertModel, Alert](alertModel, fieldSet)

thehive-misp/app/connectors/misp/MispSrv.scala

+17-16
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,10 @@ class MispSrv @Inject() (
300300
"dataType" "file",
301301
"message" a.comment,
302302
"tags" (artifactTags.value ++ a.tags.map(JsString)),
303-
"data" Json.obj(
303+
"remoteAttachment" Json.obj(
304304
"filename" a.value,
305-
"attributeId" a.id,
306-
"attributeType" a.tpe).toString,
305+
"reference" a.id,
306+
"type" a.tpe),
307307
"startDate" a.date))
308308
case a convertAttribute(a).map { j
309309
val tags = artifactTags ++ (j \ "tags").asOpt[JsArray].getOrElse(JsArray(Nil))
@@ -319,20 +319,20 @@ class MispSrv @Inject() (
319319
attr: JsObject)(implicit authContext: AuthContext): Option[Future[Fields]] = {
320320
(for {
321321
dataType (attr \ "dataType").validate[String]
322-
data (attr \ "data").validate[String]
322+
data (attr \ "data").validateOpt[String]
323323
message (attr \ "message").validate[String]
324324
startDate (attr \ "startDate").validate[Date]
325-
attachment = dataType match {
326-
case "file"
327-
val json = Json.parse(data)
328-
for {
329-
attributeId (json \ "attributeId").asOpt[String]
330-
attributeType (json \ "attributeType").asOpt[String]
331-
fiv = downloadAttachment(mispConnection, attributeId)
332-
} yield if (attributeType == "malware-sample") fiv.map(extractMalwareAttachment)
333-
else fiv
334-
case _ None
335-
}
325+
attachmentReference (attr \ "remoteAttachment" \ "reference").validateOpt[String]
326+
attachmentType (attr \ "remoteAttachment" \ "type").validateOpt[String]
327+
attachment = attachmentReference
328+
.flatMap {
329+
case ref if dataType == "file" Some(downloadAttachment(mispConnection, ref))
330+
case _ None
331+
}
332+
.map {
333+
case f if attachmentType.contains("malware-sample") f.map(extractMalwareAttachment)
334+
case f f
335+
}
336336
tags = (attr \ "tags").asOpt[Seq[String]].getOrElse(Nil)
337337
tlp = tags.map(_.toLowerCase)
338338
.collectFirst {
@@ -351,7 +351,8 @@ class MispSrv @Inject() (
351351
.filterNot(_.toLowerCase.startsWith("tlp:"))
352352
.map(JsString)))
353353
.set("tlp", tlp)
354-
} yield attachment.fold(Future.successful(fields.set("data", data)))(_.map { fiv
354+
if attachment.isDefined != data.isDefined
355+
} yield attachment.fold(Future.successful(fields.set("data", data.get)))(_.map { fiv
355356
fields.set("attachment", fiv)
356357
})) match {
357358
case JsSuccess(r, _) Some(r)

0 commit comments

Comments
 (0)