This APT (Advanced Persistent Threat) detection recipe ingests EDR (Endpoint Detection and Response) and network traffic logs, while monitoring for an IoB (Indicator of Behavior) that matches malicious data exfiltration patterns.
title:APT Detectionsummary:Endpoint logs and network traffic data merge to auto-detect exfiltrationcontributor:https://github.com/rrwrightversion:1description:|-This APT (Advanced Persistent Threat) detection recipe ingests EDR (Endpoint Detection and Response) and network traffic logs, while monitoring for an IoB (Indicator of Behavior) that matches malicious data exfiltration patterns.SCENARIO:Using a standing query, the recipe monitors for covert interprocesscommunication using a file to pass data. When that pattern is matched, with anetwork SEND event, we have our smoking gun and a URL is logged linking tothe Quine Exploration UI with the full activity and context for investigation.In this scenario, a malicious Excel macro collects personal data and stores it in a temporary file. The APT process "ntclean" infiltrated the system previously through an SSH exploit, and now reads from that temporary file and exfiltrates data from the network--hiding it as an HTTP GET request--before deleting the temporary file to cover its tracks. The source of the SSH exploit that planted the APT and the destination for exfiltrated data utilize the same IP address.SAMPLE DATA:endpoint.json - https://recipes.quine.io/apt-detection/endpoint-jsonnetwork.json - https://recipes.quine.io/apt-detection/network-jsonDownload the sample data to the same directory where Quine will be run.RESULTS:When the standing query detects the WRITE->READ->SEND->DELETE pattern, it will output a link to the console that can be copied and pasted into a browser to explore the event in the Quine Exploration UI.ingestStreams:-type:FileIngestpath:endpoint.jsonformat:type:CypherJsonquery:>-MATCH (proc), (event), (object)WHERE id(proc) = idFrom($that.pid)AND id(event) = idFrom($that)AND id(object) = idFrom($that.object)SET proc.id = $that.pid,proc: Process,event.type = $that.event_type,event: EndpointEvent,event.time = $that.time,object.data = $that.objectCREATE (proc)-[:EVENT]->(event)-[:EVENT]->(object)-type:FileIngestpath:network.jsonformat:type:CypherJsonquery:>-MATCH (src), (dst), (event)WHERE id(src) = idFrom($that.src_ip+":"+$that.src_port)AND id(dst) = idFrom($that.dst_ip+":"+$that.dst_port)AND id(event) = idFrom('network_event', $that)SET src.ip = $that.src_ip+":"+$that.src_port,src: IP,dst.ip = $that.dst_ip+":"+$that.dst_port,dst: IP,event.proto = $that.proto,event.time = $that.time,event.detail = $that.detail,event: NetTrafficCREATE (src)-[:NET_TRAFFIC]->(event)-[:NET_TRAFFIC]->(dst)standingQueries:-pattern:type:Cypherquery:>-MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2), (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)WHERE e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"RETURN DISTINCT id(f) as fileIdoutputs:stolen-data:type:CypherQueryquery:>-MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2), (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)WHERE id(f) = $that.data.fileIdAND e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"AND e1.time < e2.timeAND e2.time < e3.timeAND e2.time < e4.timeCREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)WITH e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URLRETURN URLandThen:type:PrintToStandardOutnodeAppearances:-predicate:propertyKeys:[]knownValues:{}dbLabel:Processicon:ion-load-alabel:type:Propertykey:idprefix:"Process:"-predicate:propertyKeys:[]knownValues:{}dbLabel:IPicon:ion-ios-worldlabel:type:Propertykey:ipprefix:""-predicate:propertyKeys:[]knownValues:{}dbLabel:EndpointEventicon:ion-android-checkmark-circlelabel:type:Propertykey:typeprefix:""-predicate:propertyKeys:[]knownValues:{}dbLabel:NetTrafficicon:ion-networklabel:type:Propertykey:protoprefix:""-predicate:propertyKeys:[]knownValues:{}icon:ion-ios-copylabel:type:Propertykey:dataprefix:""quickQueries:-predicate:propertyKeys:[]knownValues:{}quickQuery:name:Adjacent NodesquerySuffix:MATCH (n)--(m) RETURN DISTINCT mqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}quickQuery:name:RefreshquerySuffix:RETURN nqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}quickQuery:name:Local PropertiesquerySuffix:RETURN id(n), properties(n)queryLanguage:Cyphersort:Text-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Files ReadquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(f) WHERE e.type = "READ" RETURN fqueryLanguage:Cyphersort:NodeedgeLabel:read-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Files WrittenquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(f) WHERE e.type = "WRITE" RETURN fqueryLanguage:Cyphersort:NodeedgeLabel:wrote-predicate:propertyKeys:-dataknownValues:{}quickQuery:name:Read ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "READ" RETURN pqueryLanguage:Cyphersort:NodeedgeLabel:written by-predicate:propertyKeys:-dataknownValues:{}quickQuery:name:Written ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "WRITE" RETURN pqueryLanguage:Cyphersort:NodeedgeLabel:written by-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Received DataquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(i) WHERE e.type = "RECEIVE" RETURN iqueryLanguage:Cyphersort:NodeedgeLabel:received-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Sent DataquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(i) WHERE e.type = "SEND" RETURN iqueryLanguage:Cyphersort:NodeedgeLabel:sent-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Started ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "SPAWN" RETURN pqueryLanguage:Cyphersort:NodeedgeLabel:parent process-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Started Other ProcessquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(p) WHERE e.type = "SPAWN" RETURN pqueryLanguage:Cyphersort:NodeedgeLabel:child process-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network SendquerySuffix:MATCH (n)-[:NET_TRAFFIC]->(net) RETURN netqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network ReceivequerySuffix:MATCH (n)<-[:NET_TRAFFIC]-(net) RETURN netqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network CommunicationquerySuffix:MATCH (n)-[:NET_TRAFFIC]-(net)-[:NET_TRAFFIC]-(ip) RETURN ipqueryLanguage:Cyphersort:NodeedgeLabel:CommunicationsampleQueries:[]
This APT (Advanced Persistent Threat) detection recipe ingests EDR (Endpoint Detection and Response) and network traffic logs, while monitoring for an IoB (Indicator of Behavior) that matches malicious data exfiltration patterns.
version:2title:APT Detectionsummary:Endpoint logs and network traffic data merge to auto-detect exfiltrationcontributor:https://github.com/rrwrightdescription:|-This APT (Advanced Persistent Threat) detection recipe ingests EDR (EndpointDetection and Response) and network traffic logs, while monitoring for an IoB(Indicator of Behavior) that matches malicious data exfiltration patterns.SCENARIO:Using a standing query, the recipe monitors for covert interprocesscommunication using a file to pass data. When that pattern is matched, with anetwork SEND event, we have our smoking gun and a URL is logged linking tothe Quine Exploration UI with the full activity and context for investigation.In this scenario, a malicious Excel macro collects personal data and storesit in a temporary file. The APT process "ntclean" infiltrated the systempreviously through an SSH exploit, and now reads from that temporary fileand exfiltrates data from the network--hiding it as an HTTP GET request--before deleting the temporary file to cover its tracks.The source of the SSH exploit that planted the APT and the destinationfor exfiltrated data utilize the same IP address.SAMPLE DATA:endpoint.json - https://recipes.quine.io/apt-detection/endpoint-jsonnetwork.json - https://recipes.quine.io/apt-detection/network-jsonDownload the sample data to the same directory where Quine will be run.RESULTS:When the standing query detects the WRITE->READ->SEND->DELETE pattern, itwill output a link to the console that can be copied and pasted into abrowser to explore the event in the Quine Exploration UI.ingestStreams:-name:endpoint-eventssource:type:Filepath:$endpoint_fileformat:type:Jsonquery:>-MATCH (proc), (event), (object)WHERE id(proc) = idFrom($that.pid)AND id(event) = idFrom($that)AND id(object) = idFrom($that.object)SET proc.id = $that.pid,proc: Process,event.type = $that.event_type,event: EndpointEvent,event.time = $that.time,object.data = $that.objectCREATE (proc)-[:EVENT]->(event)-[:EVENT]->(object)-name:network-eventssource:type:Filepath:$network_fileformat:type:Jsonquery:>-MATCH (src), (dst), (event)WHERE id(src) = idFrom($that.src_ip+":"+$that.src_port)AND id(dst) = idFrom($that.dst_ip+":"+$that.dst_port)AND id(event) = idFrom('network_event', $that)SET src.ip = $that.src_ip+":"+$that.src_port,src: IP,dst.ip = $that.dst_ip+":"+$that.dst_port,dst: IP,event.proto = $that.proto,event.time = $that.time,event.detail = $that.detail,event: NetTrafficCREATE (src)-[:NET_TRAFFIC]->(event)-[:NET_TRAFFIC]->(dst)standingQueries:-name:exfiltration-detectionpattern:type:Cypherquery:>-MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2),(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)WHERE e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"RETURN DISTINCT id(f) as fileIdmode:DISTINCT_IDoutputs:-name:stolen-datapreEnrichmentTransformation:type:InlineDataresultEnrichment:query:>-MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2),(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)WHERE id(f) = $that.fileIdAND e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"AND e1.time < e2.timeAND e2.time < e3.timeAND e2.time < e4.timeCREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)WITH e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URLRETURN URLparameter:thatdestinations:-type:StandardOutnodeAppearances:-predicate:propertyKeys:[]knownValues:{}dbLabel:Processicon:ion-load-alabel:type:Propertykey:idprefix:"Process:"-predicate:propertyKeys:[]knownValues:{}dbLabel:IPicon:ion-ios-worldlabel:type:Propertykey:ipprefix:""-predicate:propertyKeys:[]knownValues:{}dbLabel:EndpointEventicon:ion-android-checkmark-circlelabel:type:Propertykey:typeprefix:""-predicate:propertyKeys:[]knownValues:{}dbLabel:NetTrafficicon:ion-networklabel:type:Propertykey:protoprefix:""-predicate:propertyKeys:[]knownValues:{}icon:ion-ios-copylabel:type:Propertykey:dataprefix:""quickQueries:-predicate:propertyKeys:[]knownValues:{}quickQuery:name:Adjacent NodesquerySuffix:MATCH (n)--(m) RETURN DISTINCT msort:NODE-predicate:propertyKeys:[]knownValues:{}quickQuery:name:RefreshquerySuffix:RETURN nsort:NODE-predicate:propertyKeys:[]knownValues:{}quickQuery:name:Local PropertiesquerySuffix:RETURN id(n), properties(n)sort:TEXT-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Files ReadquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(f) WHERE e.type = "READ" RETURN fsort:NODEedgeLabel:read-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Files WrittenquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(f) WHERE e.type = "WRITE" RETURN fsort:NODEedgeLabel:wrote-predicate:propertyKeys:-dataknownValues:{}quickQuery:name:Read ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "READ" RETURN psort:NODEedgeLabel:written by-predicate:propertyKeys:-dataknownValues:{}quickQuery:name:Written ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "WRITE" RETURN psort:NODEedgeLabel:written by-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Received DataquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(i) WHERE e.type = "RECEIVE" RETURN isort:NODEedgeLabel:received-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Sent DataquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(i) WHERE e.type = "SEND" RETURN isort:NODEedgeLabel:sent-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Started ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "SPAWN" RETURN psort:NODEedgeLabel:parent process-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Started Other ProcessquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(p) WHERE e.type = "SPAWN" RETURN psort:NODEedgeLabel:child process-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network SendquerySuffix:MATCH (n)-[:NET_TRAFFIC]->(net) RETURN netsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network ReceivequerySuffix:MATCH (n)<-[:NET_TRAFFIC]-(net) RETURN netsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network CommunicationquerySuffix:MATCH (n)-[:NET_TRAFFIC]-(net)-[:NET_TRAFFIC]-(ip) RETURN ipsort:NODEedgeLabel:CommunicationsampleQueries:[]
This APT (Advanced Persistent Threat) detection recipe ingests EDR (Endpoint Detection and Response) and network traffic logs, while monitoring for an IoB (Indicator of Behavior) that matches malicious data exfiltration patterns.
No download required
This version streams the sample data directly from data.thatdot.com,
so you can skip the Sample Data step below.
See Recipe Data Service if you want to adjust
how this recipe loads: take a slice of the data, or slow the stream down to watch the
graph build.
version:2title:APT Detectionsummary:Endpoint logs and network traffic data merge to auto-detect exfiltrationcontributor:https://github.com/rrwrightdescription:|-This APT (Advanced Persistent Threat) detection recipe ingests EDR (EndpointDetection and Response) and network traffic logs, while monitoring for an IoB(Indicator of Behavior) that matches malicious data exfiltration patterns.SCENARIO:Using a standing query, the recipe monitors for covert interprocesscommunication using a file to pass data. When that pattern is matched, with anetwork SEND event, we have our smoking gun and a URL is logged linking tothe Quine Exploration UI with the full activity and context for investigation.In this scenario, a malicious Excel macro collects personal data and storesit in a temporary file. The APT process "ntclean" infiltrated the systempreviously through an SSH exploit, and now reads from that temporary fileand exfiltrates data from the network--hiding it as an HTTP GET request--before deleting the temporary file to cover its tracks.The source of the SSH exploit that planted the APT and the destinationfor exfiltrated data utilize the same IP address.SAMPLE DATA:endpoint.json - https://recipes.quine.io/apt-detection/endpoint-jsonnetwork.json - https://recipes.quine.io/apt-detection/network-jsonDownload the sample data to the same directory where Quine will be run.RESULTS:When the standing query detects the WRITE->READ->SEND->DELETE pattern, itwill output a link to the console that can be copied and pasted into abrowser to explore the event in the Quine Exploration UI.ingestStreams:-name:endpoint-eventssource:type:ServerSentEventurl:https://data.thatdot.com/recipe/endpoint.jsonformat:type:Jsonquery:>-MATCH (proc), (event), (object)WHERE id(proc) = idFrom($that.pid)AND id(event) = idFrom($that)AND id(object) = idFrom($that.object)SET proc.id = $that.pid,proc: Process,event.type = $that.event_type,event: EndpointEvent,event.time = $that.time,object.data = $that.objectCREATE (proc)-[:EVENT]->(event)-[:EVENT]->(object)-name:network-eventssource:type:ServerSentEventurl:https://data.thatdot.com/recipe/network.jsonformat:type:Jsonquery:>-MATCH (src), (dst), (event)WHERE id(src) = idFrom($that.src_ip+":"+$that.src_port)AND id(dst) = idFrom($that.dst_ip+":"+$that.dst_port)AND id(event) = idFrom('network_event', $that)SET src.ip = $that.src_ip+":"+$that.src_port,src: IP,dst.ip = $that.dst_ip+":"+$that.dst_port,dst: IP,event.proto = $that.proto,event.time = $that.time,event.detail = $that.detail,event: NetTrafficCREATE (src)-[:NET_TRAFFIC]->(event)-[:NET_TRAFFIC]->(dst)standingQueries:-name:exfiltration-detectionpattern:type:Cypherquery:>-MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2),(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)WHERE e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"RETURN DISTINCT id(f) as fileIdmode:DISTINCT_IDoutputs:-name:stolen-datapreEnrichmentTransformation:type:InlineDataresultEnrichment:query:>-MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2),(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)WHERE id(f) = $that.fileIdAND e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"AND e1.time < e2.timeAND e2.time < e3.timeAND e2.time < e4.timeCREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)WITH e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URLRETURN URLparameter:thatdestinations:-type:StandardOutnodeAppearances:-predicate:propertyKeys:[]knownValues:{}dbLabel:Processicon:ion-load-alabel:type:Propertykey:idprefix:"Process:"-predicate:propertyKeys:[]knownValues:{}dbLabel:IPicon:ion-ios-worldlabel:type:Propertykey:ipprefix:""-predicate:propertyKeys:[]knownValues:{}dbLabel:EndpointEventicon:ion-android-checkmark-circlelabel:type:Propertykey:typeprefix:""-predicate:propertyKeys:[]knownValues:{}dbLabel:NetTrafficicon:ion-networklabel:type:Propertykey:protoprefix:""-predicate:propertyKeys:[]knownValues:{}icon:ion-ios-copylabel:type:Propertykey:dataprefix:""quickQueries:-predicate:propertyKeys:[]knownValues:{}quickQuery:name:Adjacent NodesquerySuffix:MATCH (n)--(m) RETURN DISTINCT msort:NODE-predicate:propertyKeys:[]knownValues:{}quickQuery:name:RefreshquerySuffix:RETURN nsort:NODE-predicate:propertyKeys:[]knownValues:{}quickQuery:name:Local PropertiesquerySuffix:RETURN id(n), properties(n)sort:TEXT-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Files ReadquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(f) WHERE e.type = "READ" RETURN fsort:NODEedgeLabel:read-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Files WrittenquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(f) WHERE e.type = "WRITE" RETURN fsort:NODEedgeLabel:wrote-predicate:propertyKeys:-dataknownValues:{}quickQuery:name:Read ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "READ" RETURN psort:NODEedgeLabel:written by-predicate:propertyKeys:-dataknownValues:{}quickQuery:name:Written ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "WRITE" RETURN psort:NODEedgeLabel:written by-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Received DataquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(i) WHERE e.type = "RECEIVE" RETURN isort:NODEedgeLabel:received-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Sent DataquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(i) WHERE e.type = "SEND" RETURN isort:NODEedgeLabel:sent-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Started ByquerySuffix:MATCH (n)<-[:EVENT]-(e)<-[:EVENT]-(p) WHERE e.type = "SPAWN" RETURN psort:NODEedgeLabel:parent process-predicate:propertyKeys:[]knownValues:{}dbLabel:ProcessquickQuery:name:Started Other ProcessquerySuffix:MATCH (n)-[:EVENT]->(e)-[:EVENT]->(p) WHERE e.type = "SPAWN" RETURN psort:NODEedgeLabel:child process-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network SendquerySuffix:MATCH (n)-[:NET_TRAFFIC]->(net) RETURN netsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network ReceivequerySuffix:MATCH (n)<-[:NET_TRAFFIC]-(net) RETURN netsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:IPquickQuery:name:Network CommunicationquerySuffix:MATCH (n)-[:NET_TRAFFIC]-(net)-[:NET_TRAFFIC]-(ip) RETURN ipsort:NODEedgeLabel:CommunicationsampleQueries:[]
In this scenario, a malicious Excel macro collects personal data and stores it in a temporary file. The APT process ntclean infiltrated the system previously through an SSH exploit, and now reads from that temporary file and exfiltrates data from the network hiding it as an HTTP GET request before deleting the temporary file to cover its tracks.
Using a standing query, the recipe monitors for covert interprocess communication using a file to pass data. When that pattern is matched, with a network SEND event, we have our smoking gun and a URL is logged linking to the Quine Exploration UI with the full activity and context for investigation.
The source of the SSH exploit that planted the APT and the destination for exfiltrated data utilize the same IP address.
The recipe reads observations from the two sample data files using ingest streams to manifest a graph in Quine. A separate ingest stream is configured to process each file, each containing Cypher that parses the observations, manifests nodes, and relates them to each other in the graph.
{"pattern":{"type":"Cypher","query":"MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2), \n (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)\nWHERE e1.type = \"WRITE\"\n AND e2.type = \"READ\"\n AND e3.type = \"DELETE\"\n AND e4.type = \"SEND\"\nRETURN DISTINCT id(f) as fileId"},"outputs":{"stolen-data":{"type":"CypherQuery","query":"MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2), \n (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)\nWHERE id(f) = $that.data.fileId\n AND e1.type = \"WRITE\"\n AND e2.type = \"READ\"\n AND e3.type = \"DELETE\"\n AND e4.type = \"SEND\"\n AND e1.time < e2.time\n AND e2.time < e3.time\n AND e2.time < e4.time\n\nCREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)\nWITH e1, e2, e3, e4, p1, p2, f, ip, \"http://localhost:8080/#MATCH\" + text.urlencode(\" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='\"+strId(p1)+\"' AND id(e1)='\"+strId(e1)+\"' AND id(f)='\"+strId(f)+\"' AND id(e2)='\"+strId(e2)+\"' AND id(p2)='\"+strId(p2)+\"' AND id(e3)='\"+strId(e3)+\"' AND id(e4)='\"+strId(e4)+\"' AND id(ip)='\"+strId(ip)+"'RETURNe1,e2,e3,e4,p1,p2,f,ip\") as URL RETURN URL","andThen":{"type":"PrintToStandardOut"}}}}
standingQueries:-name:exfiltration-detectionpattern:type:Cypherquery:>-MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2),(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)WHERE e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"RETURN DISTINCT id(f) as fileIdmode:DISTINCT_IDoutputs:-name:stolen-datapreEnrichmentTransformation:type:InlineDataresultEnrichment:query:>-MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2),(f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)WHERE id(f) = $that.fileIdAND e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"AND e1.time < e2.timeAND e2.time < e3.timeAND e2.time < e4.timeCREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)WITH e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URLRETURN URLparameter:thatdestinations:-type:StandardOut
POST /api/v2/graph/quine/standingQueries
{"name":"exfiltration-detection","pattern":{"type":"Cypher","query":"MATCH (e1)-[:EVENT]->(f)<-[:EVENT]-(e2), (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4) WHERE e1.type = \"WRITE\" AND e2.type = \"READ\" AND e3.type = \"DELETE\" AND e4.type = \"SEND\" RETURN DISTINCT id(f) as fileId","mode":"DISTINCT_ID"},"outputs":[{"name":"stolen-data","preEnrichmentTransformation":{"type":"InlineData"},"resultEnrichment":{"query":"MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2), (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip) WHERE id(f) = $that.fileId AND e1.type = \"WRITE\" AND e2.type = \"READ\" AND e3.type = \"DELETE\" AND e4.type = \"SEND\" AND e1.time < e2.time AND e2.time < e3.time AND e2.time < e4.time CREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3) WITH e1, e2, e3, e4, p1, p2, f, ip, \"http://localhost:8080/#MATCH\" + text.urlencode(\" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='\"+strId(p1)+\"' AND id(e1)='\"+strId(e1)+\"' AND id(f)='\"+strId(f)+\"' AND id(e2)='\"+strId(e2)+\"' AND id(p2)='\"+strId(p2)+\"' AND id(e3)='\"+strId(e3)+\"' AND id(e4)='\"+strId(e4)+\"' AND id(ip)='\"+strId(ip)+\"' RETURN e1, e2, e3, e4, p1, p2, f, ip\") as URL RETURN URL","parameter":"that"},"destinations":[{"type":"StandardOut"}]}]}
Once Quine detects the pattern, the event is sent to a standing query output for additional processing and action.
outputs:stolen-data:type:CypherQueryquery:>-MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2), (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)WHERE id(f) = $that.data.fileIdAND e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"AND e1.time < e2.timeAND e2.time < e3.timeAND e2.time < e4.timeCREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)With e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URLRETURN URLandThen:type:PrintToStandardOut
outputs:-name:stolen-dataresultEnrichment:query:>-MATCH (p1)-[:EVENT]->(e1)-[:EVENT]->(f)<-[:EVENT]-(e2)<-[:EVENT]-(p2), (f)<-[:EVENT]-(e3)<-[:EVENT]-(p2)-[:EVENT]->(e4)-[:EVENT]->(ip)WHERE id(f) = $that.fileIdAND e1.type = "WRITE"AND e2.type = "READ"AND e3.type = "DELETE"AND e4.type = "SEND"AND e1.time < e2.timeAND e2.time < e3.timeAND e2.time < e4.timeCREATE (e1)-[:NEXT]->(e2)-[:NEXT]->(e4)-[:NEXT]->(e3)With e1, e2, e3, e4, p1, p2, f, ip, "http://localhost:8080/#MATCH" + text.urlencode(" (e1),(e2),(e3),(e4),(p1),(p2),(f),(ip) WHERE id(p1)='"+strId(p1)+"' AND id(e1)='"+strId(e1)+"' AND id(f)='"+strId(f)+"' AND id(e2)='"+strId(e2)+"' AND id(p2)='"+strId(p2)+"' AND id(e3)='"+strId(e3)+"' AND id(e4)='"+strId(e4)+"' AND id(ip)='"+strId(ip)+"' RETURN e1, e2, e3, e4, p1, p2, f, ip") as URLRETURN URLparameter:thatdestinations:-type:StandardOut
The result once the pattern is detected is to output a link to the console that an analyst can use to review the event further within Quine's Exploration UI.
When the standing query detects the WRITE->READ->SEND->DELETE pattern, it will output a link to the console that can be copied and pasted into a browser to explore the event in the Quine Exploration UI. Copy and paste the URL section of the match JSON from your console into your browser.
The nodes will be jumbled together when you first open the graph. Arrange the nodes to look similar to the image below before you start exploring.