version:1title:Password Spraying Detectioncontributor:https://github.com/thatdotsummary:Ingests JSON-formatted IAM-style password authentication log file and creates relationships to detect Password Spraying attacks.description:|-Ingests password-based authentication logs modeled on the top IAM providers (hosted and on-prem) and generates a graph manifesting the following nodes:attempt - transaction representing a password authentication attemptuser - user that originated the attemptclient - client (computer/mobile/unknown) from which user originated the attemptasn - ASN from which user originated the attemptasset - asset (server, service, etc.) that the user targetedtime - time of attemptThe first standing query uses the manifested graph structure to generate synthetic edges between sequential attempts for a user:(attempt1)-[:NEXT]->(attempt2)-[:NEXT]->(attempt3)The second standing query looks for four consecutive failed attempts followed by a successful attempt from a user to trigger an alert with a link to the subgraph that represents a potential password spraying attack.Ensure that the attempts.json file is in the same directory as Quine and issue the following command to begin:java -jar quine-x.x.x.jar -r password_spraying.ymlwhere x.x.x represents the version (e.g., quine-1.3.2.jar represents Quine version 1.3.2).ingestStreams:-type:FileIngestpath:attempts.jsonformat:type:CypherJsonquery:|-//////////////////////////////// Set IDs for nodes//////////////////////////////MATCH (attempt), (client), (asn), (user), (asset)WHERE id(attempt) = idFrom('attempt', $that.eventId, $that.timestamp)AND id(client) = idFrom('client', $that.user.id, $that.client.ipAddress)AND id(asn) = idFrom('asn', $that.client.asn)AND id(user) = idFrom('user', $that.user.id)AND id(asset) = idFrom('asset', $that.transaction.entityId)//////////////////////////////// Bucketing for counters//////////////////////////////CALL incrementCounter(client, "clientCount", 1) YIELD count AS clientCountCALL incrementCounter(client, toLower($that.outcome.result), 1) YIELD count AS clientOutcomeCountCALL incrementCounter(user, "userCount", 1) YIELD count AS userCountCALL incrementCounter(user, toLower($that.outcome.result), 1) YIELD count AS userOutcomeCountCALL incrementCounter(asset, "assetCount", 1) YIELD count AS assetCountCALL incrementCounter(asset, toLower($that.outcome.result), 1) YIELD count AS assetOutcomeCount//////////////////////////////// Client//////////////////////////////SET client.device = $that.client.device,client.ipAddress = $that.client.ipAddress,client.userAgent = $that.client.userAgent,client: client// Identify last time client seen across clients //SET client.lastseen = coll.max([$that.timestamp, coalesce(client.lastseen, $that.timestamp)])// Percentage of success vs. failure //SET client.successPercent = ceil(coalesce((client.success*1.0)/(client.count*1.0)*100.0, 0.0))SET client.failurePercent = floor(coalesce((client.failure*1.0)/(client.count*1.0)*100.0, 0.0))SET client.state = CASE// Set threshold ratios below for each of three cases //WHEN client.successPercent >= 90 THEN 'good'WHEN client.successPercent >= 75 AND client.successPercent < 90 THEN 'warn'WHEN client.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// User//////////////////////////////SET user.id = $that.user.id,user.alternateId = $that.user.alternateId,user.displayName = $that.user.displayName,user.type = $that.user.type,user: user// Identify last time user seen across users //SET user.lastseen = coll.max([$that.timestamp, coalesce(user.lastseen, $that.timestamp)])// Percentage of success vs. failure //SET user.successPercent = ceil(coalesce((user.success*1.0)/(user.count*1.0)*100.0, 0.0))SET user.failurePercent = floor(coalesce((user.failure*1.0)/(user.count*1.0)*100.0, 0.0))SET user.state = CASE// Set threshold ratios below for each of three cases //WHEN user.successPercent >= 90 THEN 'good'WHEN user.successPercent >= 75 AND user.successPercent < 90 THEN 'warn'WHEN user.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// Attempts//////////////////////////////SET attempt.schemaVersion = $that.schemaVersion,attempt.eventId = $that.eventId,attempt.transactionId = $that.transaction.id,attempt.timestamp = $that.timestamp,attempt.entityId = $that.transaction.entityId,attempt.eventType = $that.eventType,attempt.transactionType = $that.transaction.type,attempt.eventCode = $that.eventCode,attempt.displayMessage = $that.displayMessage,attempt.outcomeResult = $that.outcome.result,attempt.logLevel = $that.level,attempt.zone = $that.client.zone,attempt.client = $that.client.ipAddress,attempt.userSequence = coalesce(userCount,0),attempt.clientSequence = coalesce(clientCount,0),attempt: attempt//////////////////////////////// ASN//////////////////////////////SET asn.id = $that.client.asn,asn: asn//////////////////////////////// Asset//////////////////////////////SET asset.id = $that.transaction.entityId,asset.detail = $that.client.requestUri,asset: asset// Percentage of success vs. failure //SET asset.successPercent = ceil(coalesce((asset.success*1.0)/(asset.count*1.0)*100.0, 0.0))SET asset.failurePercent = floor(coalesce((asset.failure*1.0)/(asset.count*1.0)*100.0, 0.0))SET asset.state = CASE// Set threshold ratios below for each of three cases //WHEN asset.successPercent >= 90 THEN 'good'WHEN asset.successPercent >= 75 AND asset.successPercent < 90 THEN 'warn'WHEN asset.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// Create relationship between nodes//////////////////////////////CREATE (user)-[:ORIGINATED]->(attempt)-[:USING]->(client),(client)<-[:USING]-(attempt)-[:TARGETED]->(asset),(user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(asset),(attempt)-[:OVER]->(asn)standingQueries:-pattern:type:Cypherparallelism:32query:|-////////////////////////////////////////////////////////// Subquery to sequence attempts (attempt)-[:NEXT]->(attempt)////////////////////////////////////////////////////////MATCH (client2)<-[:USING]-(attempt1)<-[:ORIGINATED]-(user)-[:ORIGINATED]->(attempt2)-[:USING]->(client1)RETURN DISTINCT id(attempt2) AS attempt2mode:DistinctIdoutputs:sequence:type:CypherQueryquery:|-MATCH (client2)<-[:USING]-(attempt2)<-[:ORIGINATED]-(user)-[:ORIGINATED]->(attempt1 {clientSequence: (attempt2.clientSequence-1)})-[:USING]->(client1)WHERE id(attempt2) = $that.data.attempt2AND id(client1) = id(client2)CREATE (attempt2)<-[:NEXT]-(attempt1)shouldRetry:false-pattern:type:Cypherquery:|-///////////////////////////////////////////////////////////////////////////////////// Subquery to match 4 consecutive failed attempts followed by a successful attempt///////////////////////////////////////////////////////////////////////////////////MATCH (attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})-[:USING]->(client4)RETURN DISTINCT id(attempt1) AS attempt1mode:DistinctIdoutputs:alert:type:CypherQueryquery:|-MATCH (attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})WHERE id(attempt1)=$that.data.attempt1RETURN 'Password Spraying Attack: ' + 'http://localhost:8080/#' + text.urlencode('MATCH (user)-[:ORIGINATED]->(attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})-[:USING]->(client) WHERE id(attempt1)="' + toString(strId(attempt1)) + '" RETURN DISTINCT user,attempt1,attempt2,attempt3,attempt4,attempt5,client') AS QuineUILinkandThen:type:PrintToStandardOutnodeAppearances:# Attempt Appearance for FAILURE *******************-predicate:propertyKeys:-outcomeResultknownValues:outcomeResult:"FAILURE"dbLabel:attempticon:ion-log-incolor:"#F44336"size:label:type:Propertykey:timestampprefix:"attempt:"# Attempt Appearance for SUCCESS *******************-predicate:propertyKeys:-outcomeResultknownValues:outcomeResult:"SUCCESS"dbLabel:attempticon:ion-log-incolor:"#32a852"size:label:type:Propertykey:timestampprefix:"attempt:"# Client Type (computer/mobile/unknown) Appearance *******************-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"good"dbLabel:clienticon:ion-android-laptopcolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"warn"dbLabel:clienticon:ion-android-laptopcolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"alarm"dbLabel:clienticon:ion-android-laptopcolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"good"dbLabel:clienticon:ion-iphonecolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"warn"dbLabel:clienticon:ion-iphonecolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"alarm"dbLabel:clienticon:ion-iphonecolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"good"dbLabel:clienticon:ion-helpcolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"warn"dbLabel:clienticon:ion-helpcolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"alarm"dbLabel:clienticon:ion-helpcolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"# ASN Appearance *******************-predicate:propertyKeys:[]knownValues:{}dbLabel:asnicon:radio-wavescolor:size:48label:type:Propertykey:idprefix:"asn:"# User (USER/ADMIN/GUEST/CONTRACTOR) Appearance *******************-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"good"dbLabel:usericon:ion-ios-contact-outlinecolor:"#32a852"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"warn"dbLabel:usericon:ion-ios-contact-outlinecolor:"#d68400"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"alarm"dbLabel:usericon:ion-ios-contact-outlinecolor:"#cf151e"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"good"dbLabel:usericon:ion-atcolor:"#32a852"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"warn"dbLabel:usericon:ion-atcolor:"#d68400"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"alarm"dbLabel:usericon:ion-atcolor:"#cf151e"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"good"dbLabel:usericon:ion-hammercolor:"#32a852"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"warn"dbLabel:usericon:ion-hammercolor:"#d68400"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"alarm"dbLabel:usericon:ion-hammercolor:"#cf151e"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"good"dbLabel:usericon:ion-social-octocatcolor:"#32a852"size:label:type:Propertykey:idprefix:"guest:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"warn"dbLabel:usericon:ion-social-octocatcolor:"#d68400"size:label:type:Propertykey:idprefix:"guest:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"alarm"dbLabel:usericon:ion-social-octocatcolor:"#cf151e"size:label:type:Propertykey:idprefix:"guest:"# Zone (ONNET/OFFNET/VPN) Appearance *******************-predicate:propertyKeys:-idknownValues:id:"VPN"dbLabel:zoneicon:ion-lockedcolor:size:label:type:Propertykey:idprefix:"zone:"-predicate:propertyKeys:-idknownValues:id:"ONNET"dbLabel:zoneicon:ion-networkcolor:size:label:type:Propertykey:idprefix:"zone:"-predicate:propertyKeys:-idknownValues:id:"OFFNET"dbLabel:zoneicon:ion-android-globecolor:size:label:type:Propertykey:idprefix:"zone:"# Asset Appearance *******************-predicate:propertyKeys:-stateknownValues:state:"good"dbLabel:asseticon:ion-ios-briefcasecolor:"#32a852"size:label:type:Propertykey:idprefix:"asset:"-predicate:propertyKeys:-stateknownValues:state:"warn"dbLabel:asseticon:ion-ios-briefcasecolor:"#d68400"size:label:type:Propertykey:idprefix:"asset:"-predicate:propertyKeys:-stateknownValues:state:"alarm"dbLabel:asseticon:ion-ios-briefcasecolor:"#cf151e"size:label:type:Propertykey:idprefix:"asset:"# Period (year/month/day/hour/minute/second) Appearance *******************-predicate:propertyKeys:-periodknownValues:period:"second"dbLabel:icon:ion-clockcolor:size:22label:type:Propertykey:startprefix:"timestamp:"-predicate:propertyKeys:-periodknownValues:period:"minute"dbLabel:icon:ion-clockcolor:size:24label:type:Propertykey:startprefix:"timestamp:"-predicate:propertyKeys:-periodknownValues:period:"hour"dbLabel:icon:ion-clockcolor:size:32-predicate:propertyKeys:-periodknownValues:period:"day"dbLabel:icon:ion-android-calendarcolor:size:24-predicate:propertyKeys:-periodknownValues:period:"month"dbLabel:icon:ion-android-calendarcolor:size:32-predicate:propertyKeys:-periodknownValues:period:"year"dbLabel:icon:ion-android-calendarcolor:size:40sampleQueries:# Provide easy access to node types in the Exploration UI-name:Last 10 Nodesquery:CALL recentNodes(10)-name:Legendquery:MATCH (n) WHERE labels(n) IS NOT NULL WITH labels(n) AS kind, collect(n) AS legend RETURN legend[0]-name:One User Nodequery:MATCH (user:user) RETURN user LIMIT 1-name:One Asset Nodequery:MATCH (asset:asset) RETURN asset LIMIT 1-name:One ASN Nodequery:MATCH (asn:asn) RETURN asn LIMIT 1-name:One Attempt Nodequery:MATCH (attempt:attempt) RETURN attempt LIMIT 1-name:One Client Nodequery:MATCH (client:client) RETURN client LIMIT 1-name:Table of logins showing spraying attack (run with SHIFT/RETURN)query:MATCH (n) WHERE id(n) = idFrom('user', '8b2d78e3-6d4d-42f4-9221-4d91111fe62d') MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestamp-name:Table of logins showing spraying attack with only attacker (run with SHIFT/RETURN)query:MATCH (n) WHERE id(n) = idFrom('user', '8b2d78e3-6d4d-42f4-9221-4d91111fe62d') MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) WHERE o.ipAddress="217.21.4.61" RETURN m.timestamp AS Timestamp, m.userSequence AS Sequence, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestamp-name:Find Incoming NEXT Loopquery:MATCH (attempt1:attempt)-[:NEXT]->(attempt0:attempt)<-[:NEXT]-(attempt2:attempt) RETURN attempt0,attempt1,attempt2 LIMIT 1-name:Find Outgoing NEXT Loopquery:MATCH (attempt1:attempt)<-[:NEXT]-(attempt0:attempt)-[:NEXT]->(attempt2:attempt) RETURN attempt0,attempt1,attempt2 LIMIT 1-name:Dirty Attempts (SHIFT-ENTER)query:MATCH (attempt:attempt)-[r]-() WITH attempt, count(r) AS edgeCount WHERE edgeCount>7 RETURN count(attempt) AS OOGIE_NODESquickQueries:-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Node]AdjacentNodes"querySuffix:MATCH (n)--(m) RETURN DISTINCT mqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Node]Refresh"querySuffix:RETURN nqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Text]LocalProperties"querySuffix:RETURN id(n), properties(n)queryLanguage:Cyphersort:Text-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]AllUserTypesthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) RETURN userqueryLanguage:Cyphersort:NodeedgeLabel:TARGETED_BY-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]AdminsthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Admin" RETURN userqueryLanguage:Cyphersort:NodeedgeLabel:TARGETED_BY_ADMIN-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]ContractorsthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Contractor" RETURN userqueryLanguage:Cyphersort:NodeedgeLabel:TARGETED_BY_CONTRACTOR-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]ContractorsthatFailedAuthenticationforAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Contractor" AND attempt.outcomeResult = "FAILURE" RETURN userqueryLanguage:Cyphersort:NodeedgeLabel:FAILED_AUTH_BY_CONTRACTOR-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]GueststhatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Guest" RETURN userqueryLanguage:Cyphersort:NodeedgeLabel:TARGETED_BY_GUEST-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]UsersthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "User" RETURN userqueryLanguage:Cyphersort:NodeedgeLabel:TARGETED_BY_USER-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]PreviousAttempt"querySuffix:MATCH (n)<-[:NEXT]-(attempt) RETURN attemptqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]NextAttempt"querySuffix:MATCH (n)-[:NEXT]->(attempt) RETURN attemptqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]ShowClientandASN"querySuffix:MATCH (n)-[:USING]->(m) MATCH (n)-[:OVER]->(o) RETURN DISTINCT n,m,oqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}dbLabel:clientquickQuery:name:"[Node]TargetedAssets"querySuffix:MATCH (n)<-[:USING]-(attempt)-[:TARGETED]->(asset:asset) RETURN assetqueryLanguage:Cyphersort:NodeedgeLabel:TARGETED-predicate:propertyKeys:[]knownValues:{}dbLabel:clientquickQuery:name:"[Text]Authenticationattemptsinchronologicalorder"querySuffix:MATCH (n)<-[:USING]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, n.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestampqueryLanguage:Cyphersort:Text-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]FailedPasswordAuthenticationAttempts"querySuffix:MATCH (n)-[:ORIGINATED]->(attempt {outcomeResult:"FAILURE"}) RETURN attemptqueryLanguage:Cyphersort:Node-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]TargetedAssets"querySuffix:MATCH (n)-[:ORIGINATED]->(attempt)-[:TARGETED]->(asset:asset) RETURN assetqueryLanguage:Cyphersort:NodeedgeLabel:TARGETED-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Text]Authenticationattemptsinchronologicalorder"querySuffix:MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestampqueryLanguage:Cyphersort:Text-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]AttemptsTimeline"querySuffix:MATCH (n)-[:ORIGINATED]->(event)-[:NEXT]->(m) RETURN DISTINCT mqueryLanguage:Cyphersort:Node
version:2title:Password Spraying Detectioncontributor:https://github.com/thatdotsummary:Ingests JSON-formatted IAM-style password authentication log file and creates relationships to detect Password Spraying attacks.description:|-Ingests password-based authentication logs modeled on the top IAM providers (hosted and on-prem) and generates a graph manifesting the following nodes:attempt - transaction representing a password authentication attemptuser - user that originated the attemptclient - client (computer/mobile/unknown) from which user originated the attemptasn - ASN from which user originated the attemptasset - asset (server, service, etc.) that the user targetedtime - time of attemptThe first standing query uses the manifested graph structure to generate synthetic edges between sequential attempts for a user:(attempt1)-[:NEXT]->(attempt2)-[:NEXT]->(attempt3)The second standing query looks for four consecutive failed attempts followed by a successful attempt from a user to trigger an alert with a link to the subgraph that represents a potential password spraying attack.Ensure that the attempts.json file is in the same directory as Quine and issue the following command to begin:java -jar quine-x.x.x.jar -r password_spraying.yamlwhere x.x.x represents the version (e.g., quine-1.3.2.jar represents Quine version 1.3.2).ingestStreams:-name:attempts-file-ingestsource:type:Filepath:$in_fileformat:type:Jsonquery:|-//////////////////////////////// Set IDs for nodes//////////////////////////////MATCH (attempt), (client), (asn), (user), (asset)WHERE id(attempt) = idFrom('attempt', $that.eventId, $that.timestamp)AND id(client) = idFrom('client', $that.user.id, $that.client.ipAddress)AND id(asn) = idFrom('asn', $that.client.asn)AND id(user) = idFrom('user', $that.user.id)AND id(asset) = idFrom('asset', $that.transaction.entityId)//////////////////////////////// Bucketing for counters//////////////////////////////CALL incrementCounter(client, "clientCount", 1) YIELD count AS clientCountCALL incrementCounter(client, toLower($that.outcome.result), 1) YIELD count AS clientOutcomeCountCALL incrementCounter(user, "userCount", 1) YIELD count AS userCountCALL incrementCounter(user, toLower($that.outcome.result), 1) YIELD count AS userOutcomeCountCALL incrementCounter(asset, "assetCount", 1) YIELD count AS assetCountCALL incrementCounter(asset, toLower($that.outcome.result), 1) YIELD count AS assetOutcomeCount//////////////////////////////// Client//////////////////////////////SET client.device = $that.client.device,client.ipAddress = $that.client.ipAddress,client.userAgent = $that.client.userAgent,client: client// Identify last time client seen across clients //SET client.lastseen = coll.max([$that.timestamp, coalesce(client.lastseen, $that.timestamp)])// Percentage of success vs. failure //SET client.successPercent = ceil(coalesce((client.success*1.0)/(client.count*1.0)*100.0, 0.0))SET client.failurePercent = floor(coalesce((client.failure*1.0)/(client.count*1.0)*100.0, 0.0))SET client.state = CASE// Set threshold ratios below for each of three cases //WHEN client.successPercent >= 90 THEN 'good'WHEN client.successPercent >= 75 AND client.successPercent < 90 THEN 'warn'WHEN client.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// User//////////////////////////////SET user.id = $that.user.id,user.alternateId = $that.user.alternateId,user.displayName = $that.user.displayName,user.type = $that.user.type,user: user// Identify last time user seen across users //SET user.lastseen = coll.max([$that.timestamp, coalesce(user.lastseen, $that.timestamp)])// Percentage of success vs. failure //SET user.successPercent = ceil(coalesce((user.success*1.0)/(user.count*1.0)*100.0, 0.0))SET user.failurePercent = floor(coalesce((user.failure*1.0)/(user.count*1.0)*100.0, 0.0))SET user.state = CASE// Set threshold ratios below for each of three cases //WHEN user.successPercent >= 90 THEN 'good'WHEN user.successPercent >= 75 AND user.successPercent < 90 THEN 'warn'WHEN user.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// Attempts//////////////////////////////SET attempt.schemaVersion = $that.schemaVersion,attempt.eventId = $that.eventId,attempt.transactionId = $that.transaction.id,attempt.timestamp = $that.timestamp,attempt.entityId = $that.transaction.entityId,attempt.eventType = $that.eventType,attempt.transactionType = $that.transaction.type,attempt.eventCode = $that.eventCode,attempt.displayMessage = $that.displayMessage,attempt.outcomeResult = $that.outcome.result,attempt.logLevel = $that.level,attempt.zone = $that.client.zone,attempt.client = $that.client.ipAddress,attempt.userSequence = coalesce(userCount,0),attempt.clientSequence = coalesce(clientCount,0),attempt: attempt//////////////////////////////// ASN//////////////////////////////SET asn.id = $that.client.asn,asn: asn//////////////////////////////// Asset//////////////////////////////SET asset.id = $that.transaction.entityId,asset.detail = $that.client.requestUri,asset: asset// Percentage of success vs. failure //SET asset.successPercent = ceil(coalesce((asset.success*1.0)/(asset.count*1.0)*100.0, 0.0))SET asset.failurePercent = floor(coalesce((asset.failure*1.0)/(asset.count*1.0)*100.0, 0.0))SET asset.state = CASE// Set threshold ratios below for each of three cases //WHEN asset.successPercent >= 90 THEN 'good'WHEN asset.successPercent >= 75 AND asset.successPercent < 90 THEN 'warn'WHEN asset.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// Create relationship between nodes//////////////////////////////CREATE (user)-[:ORIGINATED]->(attempt)-[:USING]->(client),(client)<-[:USING]-(attempt)-[:TARGETED]->(asset),(user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(asset),(attempt)-[:OVER]->(asn)standingQueries:-name:sequence-attemptspattern:type:Cypherquery:|-////////////////////////////////////////////////////////// Subquery to sequence attempts (attempt)-[:NEXT]->(attempt)////////////////////////////////////////////////////////MATCH (client2)<-[:USING]-(attempt1)<-[:ORIGINATED]-(user)-[:ORIGINATED]->(attempt2)-[:USING]->(client1)RETURN DISTINCT id(attempt2) AS attempt2mode:DISTINCT_IDoutputs:-name:sequencepreEnrichmentTransformation:type:InlineDatadestinations:-type:CypherQueryquery:|-MATCH (client2)<-[:USING]-(attempt2)<-[:ORIGINATED]-(user)-[:ORIGINATED]->(attempt1 {clientSequence: (attempt2.clientSequence-1)})-[:USING]->(client1)WHERE id(attempt2) = $that.attempt2AND id(client1) = id(client2)CREATE (attempt2)<-[:NEXT]-(attempt1)parameter:that-name:detect-password-sprayingpattern:type:Cypherquery:|-///////////////////////////////////////////////////////////////////////////////////// Subquery to match 4 consecutive failed attempts followed by a successful attempt///////////////////////////////////////////////////////////////////////////////////MATCH (attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})-[:USING]->(client4)RETURN DISTINCT id(attempt1) AS attempt1mode:DISTINCT_IDoutputs:-name:alertpreEnrichmentTransformation:type:InlineDataresultEnrichment:query:|-MATCH (attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})WHERE id(attempt1)=$that.attempt1RETURN 'Password Spraying Attack: ' + 'http://localhost:8080/#' + text.urlencode('MATCH (user)-[:ORIGINATED]->(attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})-[:USING]->(client) WHERE id(attempt1)="' + toString(strId(attempt1)) + '" RETURN DISTINCT user,attempt1,attempt2,attempt3,attempt4,attempt5,client') AS QuineUILinkparameter:thatdestinations:-type:StandardOutnodeAppearances:# Attempt Appearance for FAILURE *******************-predicate:propertyKeys:-outcomeResultknownValues:outcomeResult:"FAILURE"dbLabel:attempticon:ion-log-incolor:"#F44336"size:label:type:Propertykey:timestampprefix:"attempt:"# Attempt Appearance for SUCCESS *******************-predicate:propertyKeys:-outcomeResultknownValues:outcomeResult:"SUCCESS"dbLabel:attempticon:ion-log-incolor:"#32a852"size:label:type:Propertykey:timestampprefix:"attempt:"# Client Type (computer/mobile/unknown) Appearance *******************-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"good"dbLabel:clienticon:ion-android-laptopcolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"warn"dbLabel:clienticon:ion-android-laptopcolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"alarm"dbLabel:clienticon:ion-android-laptopcolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"good"dbLabel:clienticon:ion-iphonecolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"warn"dbLabel:clienticon:ion-iphonecolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"alarm"dbLabel:clienticon:ion-iphonecolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"good"dbLabel:clienticon:ion-helpcolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"warn"dbLabel:clienticon:ion-helpcolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"alarm"dbLabel:clienticon:ion-helpcolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"# ASN Appearance *******************-predicate:propertyKeys:[]knownValues:{}dbLabel:asnicon:radio-wavescolor:size:48label:type:Propertykey:idprefix:"asn:"# User (USER/ADMIN/GUEST/CONTRACTOR) Appearance *******************-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"good"dbLabel:usericon:ion-ios-contact-outlinecolor:"#32a852"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"warn"dbLabel:usericon:ion-ios-contact-outlinecolor:"#d68400"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"alarm"dbLabel:usericon:ion-ios-contact-outlinecolor:"#cf151e"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"good"dbLabel:usericon:ion-atcolor:"#32a852"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"warn"dbLabel:usericon:ion-atcolor:"#d68400"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"alarm"dbLabel:usericon:ion-atcolor:"#cf151e"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"good"dbLabel:usericon:ion-hammercolor:"#32a852"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"warn"dbLabel:usericon:ion-hammercolor:"#d68400"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"alarm"dbLabel:usericon:ion-hammercolor:"#cf151e"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"good"dbLabel:usericon:ion-social-octocatcolor:"#32a852"size:label:type:Propertykey:idprefix:"guest:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"warn"dbLabel:usericon:ion-social-octocatcolor:"#d68400"size:label:type:Propertykey:idprefix:"guest:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"alarm"dbLabel:usericon:ion-social-octocatcolor:"#cf151e"size:label:type:Propertykey:idprefix:"guest:"# Zone (ONNET/OFFNET/VPN) Appearance *******************-predicate:propertyKeys:-idknownValues:id:"VPN"dbLabel:zoneicon:ion-lockedcolor:size:label:type:Propertykey:idprefix:"zone:"-predicate:propertyKeys:-idknownValues:id:"ONNET"dbLabel:zoneicon:ion-networkcolor:size:label:type:Propertykey:idprefix:"zone:"-predicate:propertyKeys:-idknownValues:id:"OFFNET"dbLabel:zoneicon:ion-android-globecolor:size:label:type:Propertykey:idprefix:"zone:"# Asset Appearance *******************-predicate:propertyKeys:-stateknownValues:state:"good"dbLabel:asseticon:ion-ios-briefcasecolor:"#32a852"size:label:type:Propertykey:idprefix:"asset:"-predicate:propertyKeys:-stateknownValues:state:"warn"dbLabel:asseticon:ion-ios-briefcasecolor:"#d68400"size:label:type:Propertykey:idprefix:"asset:"-predicate:propertyKeys:-stateknownValues:state:"alarm"dbLabel:asseticon:ion-ios-briefcasecolor:"#cf151e"size:label:type:Propertykey:idprefix:"asset:"# Period (year/month/day/hour/minute/second) Appearance *******************-predicate:propertyKeys:-periodknownValues:period:"second"dbLabel:icon:ion-clockcolor:size:22label:type:Propertykey:startprefix:"timestamp:"-predicate:propertyKeys:-periodknownValues:period:"minute"dbLabel:icon:ion-clockcolor:size:24label:type:Propertykey:startprefix:"timestamp:"-predicate:propertyKeys:-periodknownValues:period:"hour"dbLabel:icon:ion-clockcolor:size:32-predicate:propertyKeys:-periodknownValues:period:"day"dbLabel:icon:ion-android-calendarcolor:size:24-predicate:propertyKeys:-periodknownValues:period:"month"dbLabel:icon:ion-android-calendarcolor:size:32-predicate:propertyKeys:-periodknownValues:period:"year"dbLabel:icon:ion-android-calendarcolor:size:40sampleQueries:# Provide easy access to node types in the Exploration UI-name:Last 10 Nodesquery:CALL recentNodes(10)-name:Legendquery:MATCH (n) WHERE labels(n) IS NOT NULL WITH labels(n) AS kind, collect(n) AS legend RETURN legend[0]-name:One User Nodequery:MATCH (user:user) RETURN user LIMIT 1-name:One Asset Nodequery:MATCH (asset:asset) RETURN asset LIMIT 1-name:One ASN Nodequery:MATCH (asn:asn) RETURN asn LIMIT 1-name:One Attempt Nodequery:MATCH (attempt:attempt) RETURN attempt LIMIT 1-name:One Client Nodequery:MATCH (client:client) RETURN client LIMIT 1-name:Table of logins showing spraying attack (run with SHIFT/RETURN)query:MATCH (n) WHERE id(n) = idFrom('user', '8b2d78e3-6d4d-42f4-9221-4d91111fe62d') MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestamp-name:Table of logins showing spraying attack with only attacker (run with SHIFT/RETURN)query:MATCH (n) WHERE id(n) = idFrom('user', '8b2d78e3-6d4d-42f4-9221-4d91111fe62d') MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) WHERE o.ipAddress="217.21.4.61" RETURN m.timestamp AS Timestamp, m.userSequence AS Sequence, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestamp-name:Find Incoming NEXT Loopquery:MATCH (attempt1:attempt)-[:NEXT]->(attempt0:attempt)<-[:NEXT]-(attempt2:attempt) RETURN attempt0,attempt1,attempt2 LIMIT 1-name:Find Outgoing NEXT Loopquery:MATCH (attempt1:attempt)<-[:NEXT]-(attempt0:attempt)-[:NEXT]->(attempt2:attempt) RETURN attempt0,attempt1,attempt2 LIMIT 1-name:Dirty Attempts (SHIFT-ENTER)query:MATCH (attempt:attempt)-[r]-() WITH attempt, count(r) AS edgeCount WHERE edgeCount>7 RETURN count(attempt) AS OOGIE_NODESquickQueries:-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Node]AdjacentNodes"querySuffix:MATCH (n)--(m) RETURN DISTINCT msort:NODE-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Node]Refresh"querySuffix:RETURN nsort:NODE-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Text]LocalProperties"querySuffix:RETURN id(n), properties(n)sort:TEXT-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]AllUserTypesthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) RETURN usersort:NODEedgeLabel:TARGETED_BY-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]AdminsthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Admin" RETURN usersort:NODEedgeLabel:TARGETED_BY_ADMIN-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]ContractorsthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Contractor" RETURN usersort:NODEedgeLabel:TARGETED_BY_CONTRACTOR-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]ContractorsthatFailedAuthenticationforAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Contractor" AND attempt.outcomeResult = "FAILURE" RETURN usersort:NODEedgeLabel:FAILED_AUTH_BY_CONTRACTOR-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]GueststhatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Guest" RETURN usersort:NODEedgeLabel:TARGETED_BY_GUEST-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]UsersthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "User" RETURN usersort:NODEedgeLabel:TARGETED_BY_USER-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]PreviousAttempt"querySuffix:MATCH (n)<-[:NEXT]-(attempt) RETURN attemptsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]NextAttempt"querySuffix:MATCH (n)-[:NEXT]->(attempt) RETURN attemptsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]ShowClientandASN"querySuffix:MATCH (n)-[:USING]->(m) MATCH (n)-[:OVER]->(o) RETURN DISTINCT n,m,osort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:clientquickQuery:name:"[Node]TargetedAssets"querySuffix:MATCH (n)<-[:USING]-(attempt)-[:TARGETED]->(asset:asset) RETURN assetsort:NODEedgeLabel:TARGETED-predicate:propertyKeys:[]knownValues:{}dbLabel:clientquickQuery:name:"[Text]Authenticationattemptsinchronologicalorder"querySuffix:MATCH (n)<-[:USING]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, n.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestampsort:TEXT-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]FailedPasswordAuthenticationAttempts"querySuffix:MATCH (n)-[:ORIGINATED]->(attempt {outcomeResult:"FAILURE"}) RETURN attemptsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]TargetedAssets"querySuffix:MATCH (n)-[:ORIGINATED]->(attempt)-[:TARGETED]->(asset:asset) RETURN assetsort:NODEedgeLabel:TARGETED-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Text]Authenticationattemptsinchronologicalorder"querySuffix:MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestampsort:TEXT-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]AttemptsTimeline"querySuffix:MATCH (n)-[:ORIGINATED]->(event)-[:NEXT]->(m) RETURN DISTINCT msort:NODE
version:2title:Password Spraying Detectioncontributor:https://github.com/thatdotsummary:Ingests JSON-formatted IAM-style password authentication log file and creates relationships to detect Password Spraying attacks.description:|-Ingests password-based authentication logs modeled on the top IAM providers (hosted and on-prem) and generates a graph manifesting the following nodes:attempt - transaction representing a password authentication attemptuser - user that originated the attemptclient - client (computer/mobile/unknown) from which user originated the attemptasn - ASN from which user originated the attemptasset - asset (server, service, etc.) that the user targetedtime - time of attemptThe first standing query uses the manifested graph structure to generate synthetic edges between sequential attempts for a user:(attempt1)-[:NEXT]->(attempt2)-[:NEXT]->(attempt3)The second standing query looks for four consecutive failed attempts followed by a successful attempt from a user to trigger an alert with a link to the subgraph that represents a potential password spraying attack.Ensure that the attempts.json file is in the same directory as Quine and issue the following command to begin:java -jar quine-x.x.x.jar -r password_spraying.yamlwhere x.x.x represents the version (e.g., quine-1.3.2.jar represents Quine version 1.3.2).ingestStreams:-name:attempts-file-ingestsource:type:ServerSentEventurl:https://data.thatdot.com/recipe/attempts.jsonlformat:type:Jsonquery:|-//////////////////////////////// Set IDs for nodes//////////////////////////////MATCH (attempt), (client), (asn), (user), (asset)WHERE id(attempt) = idFrom('attempt', $that.eventId, $that.timestamp)AND id(client) = idFrom('client', $that.user.id, $that.client.ipAddress)AND id(asn) = idFrom('asn', $that.client.asn)AND id(user) = idFrom('user', $that.user.id)AND id(asset) = idFrom('asset', $that.transaction.entityId)//////////////////////////////// Bucketing for counters//////////////////////////////CALL incrementCounter(client, "clientCount", 1) YIELD count AS clientCountCALL incrementCounter(client, toLower($that.outcome.result), 1) YIELD count AS clientOutcomeCountCALL incrementCounter(user, "userCount", 1) YIELD count AS userCountCALL incrementCounter(user, toLower($that.outcome.result), 1) YIELD count AS userOutcomeCountCALL incrementCounter(asset, "assetCount", 1) YIELD count AS assetCountCALL incrementCounter(asset, toLower($that.outcome.result), 1) YIELD count AS assetOutcomeCount//////////////////////////////// Client//////////////////////////////SET client.device = $that.client.device,client.ipAddress = $that.client.ipAddress,client.userAgent = $that.client.userAgent,client: client// Identify last time client seen across clients //SET client.lastseen = coll.max([$that.timestamp, coalesce(client.lastseen, $that.timestamp)])// Percentage of success vs. failure //SET client.successPercent = ceil(coalesce((client.success*1.0)/(client.count*1.0)*100.0, 0.0))SET client.failurePercent = floor(coalesce((client.failure*1.0)/(client.count*1.0)*100.0, 0.0))SET client.state = CASE// Set threshold ratios below for each of three cases //WHEN client.successPercent >= 90 THEN 'good'WHEN client.successPercent >= 75 AND client.successPercent < 90 THEN 'warn'WHEN client.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// User//////////////////////////////SET user.id = $that.user.id,user.alternateId = $that.user.alternateId,user.displayName = $that.user.displayName,user.type = $that.user.type,user: user// Identify last time user seen across users //SET user.lastseen = coll.max([$that.timestamp, coalesce(user.lastseen, $that.timestamp)])// Percentage of success vs. failure //SET user.successPercent = ceil(coalesce((user.success*1.0)/(user.count*1.0)*100.0, 0.0))SET user.failurePercent = floor(coalesce((user.failure*1.0)/(user.count*1.0)*100.0, 0.0))SET user.state = CASE// Set threshold ratios below for each of three cases //WHEN user.successPercent >= 90 THEN 'good'WHEN user.successPercent >= 75 AND user.successPercent < 90 THEN 'warn'WHEN user.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// Attempts//////////////////////////////SET attempt.schemaVersion = $that.schemaVersion,attempt.eventId = $that.eventId,attempt.transactionId = $that.transaction.id,attempt.timestamp = $that.timestamp,attempt.entityId = $that.transaction.entityId,attempt.eventType = $that.eventType,attempt.transactionType = $that.transaction.type,attempt.eventCode = $that.eventCode,attempt.displayMessage = $that.displayMessage,attempt.outcomeResult = $that.outcome.result,attempt.logLevel = $that.level,attempt.zone = $that.client.zone,attempt.client = $that.client.ipAddress,attempt.userSequence = coalesce(userCount,0),attempt.clientSequence = coalesce(clientCount,0),attempt: attempt//////////////////////////////// ASN//////////////////////////////SET asn.id = $that.client.asn,asn: asn//////////////////////////////// Asset//////////////////////////////SET asset.id = $that.transaction.entityId,asset.detail = $that.client.requestUri,asset: asset// Percentage of success vs. failure //SET asset.successPercent = ceil(coalesce((asset.success*1.0)/(asset.count*1.0)*100.0, 0.0))SET asset.failurePercent = floor(coalesce((asset.failure*1.0)/(asset.count*1.0)*100.0, 0.0))SET asset.state = CASE// Set threshold ratios below for each of three cases //WHEN asset.successPercent >= 90 THEN 'good'WHEN asset.successPercent >= 75 AND asset.successPercent < 90 THEN 'warn'WHEN asset.successPercent < 75 THEN 'alarm'ELSE 'alarm'END//////////////////////////////// Create relationship between nodes//////////////////////////////CREATE (user)-[:ORIGINATED]->(attempt)-[:USING]->(client),(client)<-[:USING]-(attempt)-[:TARGETED]->(asset),(user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(asset),(attempt)-[:OVER]->(asn)standingQueries:-name:sequence-attemptspattern:type:Cypherquery:|-////////////////////////////////////////////////////////// Subquery to sequence attempts (attempt)-[:NEXT]->(attempt)////////////////////////////////////////////////////////MATCH (client2)<-[:USING]-(attempt1)<-[:ORIGINATED]-(user)-[:ORIGINATED]->(attempt2)-[:USING]->(client1)RETURN DISTINCT id(attempt2) AS attempt2mode:DISTINCT_IDoutputs:-name:sequencepreEnrichmentTransformation:type:InlineDatadestinations:-type:CypherQueryquery:|-MATCH (client2)<-[:USING]-(attempt2)<-[:ORIGINATED]-(user)-[:ORIGINATED]->(attempt1 {clientSequence: (attempt2.clientSequence-1)})-[:USING]->(client1)WHERE id(attempt2) = $that.attempt2AND id(client1) = id(client2)CREATE (attempt2)<-[:NEXT]-(attempt1)parameter:that-name:detect-password-sprayingpattern:type:Cypherquery:|-///////////////////////////////////////////////////////////////////////////////////// Subquery to match 4 consecutive failed attempts followed by a successful attempt///////////////////////////////////////////////////////////////////////////////////MATCH (attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})-[:USING]->(client4)RETURN DISTINCT id(attempt1) AS attempt1mode:DISTINCT_IDoutputs:-name:alertpreEnrichmentTransformation:type:InlineDataresultEnrichment:query:|-MATCH (attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})WHERE id(attempt1)=$that.attempt1RETURN 'Password Spraying Attack: ' + 'http://localhost:8080/#' + text.urlencode('MATCH (user)-[:ORIGINATED]->(attempt1 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt2 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt3 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt4 {outcomeResult:"FAILURE"})-[:NEXT]->(attempt5 {outcomeResult:"SUCCESS"})-[:USING]->(client) WHERE id(attempt1)="' + toString(strId(attempt1)) + '" RETURN DISTINCT user,attempt1,attempt2,attempt3,attempt4,attempt5,client') AS QuineUILinkparameter:thatdestinations:-type:StandardOutnodeAppearances:# Attempt Appearance for FAILURE *******************-predicate:propertyKeys:-outcomeResultknownValues:outcomeResult:"FAILURE"dbLabel:attempticon:ion-log-incolor:"#F44336"size:label:type:Propertykey:timestampprefix:"attempt:"# Attempt Appearance for SUCCESS *******************-predicate:propertyKeys:-outcomeResultknownValues:outcomeResult:"SUCCESS"dbLabel:attempticon:ion-log-incolor:"#32a852"size:label:type:Propertykey:timestampprefix:"attempt:"# Client Type (computer/mobile/unknown) Appearance *******************-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"good"dbLabel:clienticon:ion-android-laptopcolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"warn"dbLabel:clienticon:ion-android-laptopcolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"computer"state:"alarm"dbLabel:clienticon:ion-android-laptopcolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"good"dbLabel:clienticon:ion-iphonecolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"warn"dbLabel:clienticon:ion-iphonecolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"mobile"state:"alarm"dbLabel:clienticon:ion-iphonecolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"good"dbLabel:clienticon:ion-helpcolor:"#32a852"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"warn"dbLabel:clienticon:ion-helpcolor:"#d68400"size:label:type:Propertykey:ipAddressprefix:"client:"-predicate:propertyKeys:-device-stateknownValues:device:"unknown"state:"alarm"dbLabel:clienticon:ion-helpcolor:"#cf151e"size:label:type:Propertykey:ipAddressprefix:"client:"# ASN Appearance *******************-predicate:propertyKeys:[]knownValues:{}dbLabel:asnicon:radio-wavescolor:size:48label:type:Propertykey:idprefix:"asn:"# User (USER/ADMIN/GUEST/CONTRACTOR) Appearance *******************-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"good"dbLabel:usericon:ion-ios-contact-outlinecolor:"#32a852"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"warn"dbLabel:usericon:ion-ios-contact-outlinecolor:"#d68400"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"User"state:"alarm"dbLabel:usericon:ion-ios-contact-outlinecolor:"#cf151e"size:label:type:Propertykey:idprefix:"user:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"good"dbLabel:usericon:ion-atcolor:"#32a852"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"warn"dbLabel:usericon:ion-atcolor:"#d68400"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Admin"state:"alarm"dbLabel:usericon:ion-atcolor:"#cf151e"size:label:type:Propertykey:idprefix:"admin:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"good"dbLabel:usericon:ion-hammercolor:"#32a852"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"warn"dbLabel:usericon:ion-hammercolor:"#d68400"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Contractor"state:"alarm"dbLabel:usericon:ion-hammercolor:"#cf151e"size:label:type:Propertykey:idprefix:"contractor:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"good"dbLabel:usericon:ion-social-octocatcolor:"#32a852"size:label:type:Propertykey:idprefix:"guest:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"warn"dbLabel:usericon:ion-social-octocatcolor:"#d68400"size:label:type:Propertykey:idprefix:"guest:"-predicate:propertyKeys:-type-stateknownValues:type:"Guest"state:"alarm"dbLabel:usericon:ion-social-octocatcolor:"#cf151e"size:label:type:Propertykey:idprefix:"guest:"# Zone (ONNET/OFFNET/VPN) Appearance *******************-predicate:propertyKeys:-idknownValues:id:"VPN"dbLabel:zoneicon:ion-lockedcolor:size:label:type:Propertykey:idprefix:"zone:"-predicate:propertyKeys:-idknownValues:id:"ONNET"dbLabel:zoneicon:ion-networkcolor:size:label:type:Propertykey:idprefix:"zone:"-predicate:propertyKeys:-idknownValues:id:"OFFNET"dbLabel:zoneicon:ion-android-globecolor:size:label:type:Propertykey:idprefix:"zone:"# Asset Appearance *******************-predicate:propertyKeys:-stateknownValues:state:"good"dbLabel:asseticon:ion-ios-briefcasecolor:"#32a852"size:label:type:Propertykey:idprefix:"asset:"-predicate:propertyKeys:-stateknownValues:state:"warn"dbLabel:asseticon:ion-ios-briefcasecolor:"#d68400"size:label:type:Propertykey:idprefix:"asset:"-predicate:propertyKeys:-stateknownValues:state:"alarm"dbLabel:asseticon:ion-ios-briefcasecolor:"#cf151e"size:label:type:Propertykey:idprefix:"asset:"# Period (year/month/day/hour/minute/second) Appearance *******************-predicate:propertyKeys:-periodknownValues:period:"second"dbLabel:icon:ion-clockcolor:size:22label:type:Propertykey:startprefix:"timestamp:"-predicate:propertyKeys:-periodknownValues:period:"minute"dbLabel:icon:ion-clockcolor:size:24label:type:Propertykey:startprefix:"timestamp:"-predicate:propertyKeys:-periodknownValues:period:"hour"dbLabel:icon:ion-clockcolor:size:32-predicate:propertyKeys:-periodknownValues:period:"day"dbLabel:icon:ion-android-calendarcolor:size:24-predicate:propertyKeys:-periodknownValues:period:"month"dbLabel:icon:ion-android-calendarcolor:size:32-predicate:propertyKeys:-periodknownValues:period:"year"dbLabel:icon:ion-android-calendarcolor:size:40sampleQueries:# Provide easy access to node types in the Exploration UI-name:Last 10 Nodesquery:CALL recentNodes(10)-name:Legendquery:MATCH (n) WHERE labels(n) IS NOT NULL WITH labels(n) AS kind, collect(n) AS legend RETURN legend[0]-name:One User Nodequery:MATCH (user:user) RETURN user LIMIT 1-name:One Asset Nodequery:MATCH (asset:asset) RETURN asset LIMIT 1-name:One ASN Nodequery:MATCH (asn:asn) RETURN asn LIMIT 1-name:One Attempt Nodequery:MATCH (attempt:attempt) RETURN attempt LIMIT 1-name:One Client Nodequery:MATCH (client:client) RETURN client LIMIT 1-name:Table of logins showing spraying attack (run with SHIFT/RETURN)query:MATCH (n) WHERE id(n) = idFrom('user', '8b2d78e3-6d4d-42f4-9221-4d91111fe62d') MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestamp-name:Table of logins showing spraying attack with only attacker (run with SHIFT/RETURN)query:MATCH (n) WHERE id(n) = idFrom('user', '8b2d78e3-6d4d-42f4-9221-4d91111fe62d') MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) WHERE o.ipAddress="217.21.4.61" RETURN m.timestamp AS Timestamp, m.userSequence AS Sequence, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestamp-name:Find Incoming NEXT Loopquery:MATCH (attempt1:attempt)-[:NEXT]->(attempt0:attempt)<-[:NEXT]-(attempt2:attempt) RETURN attempt0,attempt1,attempt2 LIMIT 1-name:Find Outgoing NEXT Loopquery:MATCH (attempt1:attempt)<-[:NEXT]-(attempt0:attempt)-[:NEXT]->(attempt2:attempt) RETURN attempt0,attempt1,attempt2 LIMIT 1-name:Dirty Attempts (SHIFT-ENTER)query:MATCH (attempt:attempt)-[r]-() WITH attempt, count(r) AS edgeCount WHERE edgeCount>7 RETURN count(attempt) AS OOGIE_NODESquickQueries:-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Node]AdjacentNodes"querySuffix:MATCH (n)--(m) RETURN DISTINCT msort:NODE-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Node]Refresh"querySuffix:RETURN nsort:NODE-predicate:propertyKeys:[]knownValues:{}quickQuery:name:"[Text]LocalProperties"querySuffix:RETURN id(n), properties(n)sort:TEXT-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]AllUserTypesthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) RETURN usersort:NODEedgeLabel:TARGETED_BY-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]AdminsthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Admin" RETURN usersort:NODEedgeLabel:TARGETED_BY_ADMIN-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]ContractorsthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Contractor" RETURN usersort:NODEedgeLabel:TARGETED_BY_CONTRACTOR-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]ContractorsthatFailedAuthenticationforAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Contractor" AND attempt.outcomeResult = "FAILURE" RETURN usersort:NODEedgeLabel:FAILED_AUTH_BY_CONTRACTOR-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]GueststhatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "Guest" RETURN usersort:NODEedgeLabel:TARGETED_BY_GUEST-predicate:propertyKeys:[]knownValues:{}dbLabel:assetquickQuery:name:"[Node]UsersthatTargetedAsset"querySuffix:MATCH (user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(n) WHERE user.type = "User" RETURN usersort:NODEedgeLabel:TARGETED_BY_USER-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]PreviousAttempt"querySuffix:MATCH (n)<-[:NEXT]-(attempt) RETURN attemptsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]NextAttempt"querySuffix:MATCH (n)-[:NEXT]->(attempt) RETURN attemptsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:attemptquickQuery:name:"[Node]ShowClientandASN"querySuffix:MATCH (n)-[:USING]->(m) MATCH (n)-[:OVER]->(o) RETURN DISTINCT n,m,osort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:clientquickQuery:name:"[Node]TargetedAssets"querySuffix:MATCH (n)<-[:USING]-(attempt)-[:TARGETED]->(asset:asset) RETURN assetsort:NODEedgeLabel:TARGETED-predicate:propertyKeys:[]knownValues:{}dbLabel:clientquickQuery:name:"[Text]Authenticationattemptsinchronologicalorder"querySuffix:MATCH (n)<-[:USING]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, n.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestampsort:TEXT-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]FailedPasswordAuthenticationAttempts"querySuffix:MATCH (n)-[:ORIGINATED]->(attempt {outcomeResult:"FAILURE"}) RETURN attemptsort:NODE-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]TargetedAssets"querySuffix:MATCH (n)-[:ORIGINATED]->(attempt)-[:TARGETED]->(asset:asset) RETURN assetsort:NODEedgeLabel:TARGETED-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Text]Authenticationattemptsinchronologicalorder"querySuffix:MATCH (n)-[:ORIGINATED]->(m)-[:USING]->(o) MATCH (n)-[:ORIGINATED]->(m) RETURN m.timestamp AS Timestamp, m.eventId AS Attempt, o.ipAddress AS Source, m.zone AS Zone, m.entityId AS Entity, m.outcomeResult AS Outcome ORDER BY m.timestampsort:TEXT-predicate:propertyKeys:[]knownValues:{}dbLabel:userquickQuery:name:"[Node]AttemptsTimeline"querySuffix:MATCH (n)-[:ORIGINATED]->(event)-[:NEXT]->(m) RETURN DISTINCT msort:NODE
In this scenario, Quine ingests password-based authentication logs modeled on the top IAM providers (hosted and on-prem) and generates a graph manifesting the following nodes:
attempt - transaction representing a password authentication attempt
user - user that originated the attempt
client - client (computer/mobile/unknown) from which user originated the attempt
asn - ASN from which user originated the attempt
asset - asset (server, service, etc.) that the user targeted
time - time of attempt
The first standing query uses the manifested graph structure to generate synthetic edges between sequential attempts for a user:
The second standing query looks for four consecutive failed attempts followed by a successful attempt from a user to trigger an alert with a link to the subgraph that represents a potential password spraying attack.
A metric is set for the number of times an event occurs within the client, user, and asset nodes. This counter is used later to calculate the attempt success/fail ratio for specific assets.
//////////////////////////////// Bucketing for counters//////////////////////////////CALLincrementCounter(client,"clientCount",1)YIELDcountASclientCountCALLincrementCounter(client,toLower($that.outcome.result),1)YIELDcountASclientOutcomeCountCALLincrementCounter(user,"userCount",1)YIELDcountASuserCountCALLincrementCounter(user,toLower($that.outcome.result),1)YIELDcountASuserOutcomeCountCALLincrementCounter(asset,"assetCount",1)YIELDcountASassetCountCALLincrementCounter(asset,toLower($that.outcome.result),1)YIELDcountASassetOutcomeCount
Each node is then filled with parameters derived from the event itself.
Create parameters for Client nodes
//////////////////////////////// Client//////////////////////////////SETclient.device=$that.client.device,client.ipAddress=$that.client.ipAddress,client.userAgent=$that.client.userAgent,client:client// Identify last time client seen across clients //SETclient.lastseen=coll.max([$that.timestamp,coalesce(client.lastseen,$that.timestamp)])// Percentage of success vs. failure //SETclient.successPercent=ceil(coalesce((client.success*1.0)/(client.count*1.0)*100.0,0.0))SETclient.failurePercent=floor(coalesce((client.failure*1.0)/(client.count*1.0)*100.0,0.0))SETclient.state=CASE// Set threshold ratios below for each of three cases //WHENclient.successPercent>=90THEN'good'WHENclient.successPercent>=75ANDclient.successPercent<90THEN'warn'WHENclient.successPercent<75THEN'alarm'ELSE'alarm'END
Create parameters for User nodes
//////////////////////////////// User//////////////////////////////SETuser.id=$that.user.id,user.alternateId=$that.user.alternateId,user.displayName=$that.user.displayName,user.type=$that.user.type,user:user// Identify last time user seen across users //SETuser.lastseen=coll.max([$that.timestamp,coalesce(user.lastseen,$that.timestamp)])// Percentage of success vs. failure //SETuser.successPercent=ceil(coalesce((user.success*1.0)/(user.count*1.0)*100.0,0.0))SETuser.failurePercent=floor(coalesce((user.failure*1.0)/(user.count*1.0)*100.0,0.0))SETuser.state=CASE// Set threshold ratios below for each of three cases //WHENuser.successPercent>=90THEN'good'WHENuser.successPercent>=75ANDuser.successPercent<90THEN'warn'WHENuser.successPercent<75THEN'alarm'ELSE'alarm'END
//////////////////////////////// Asset//////////////////////////////SETasset.id=$that.transaction.entityId,asset.detail=$that.client.requestUri,asset:asset// Percentage of success vs. failure //SETasset.successPercent=ceil(coalesce((asset.success*1.0)/(asset.count*1.0)*100.0,0.0))SETasset.failurePercent=floor(coalesce((asset.failure*1.0)/(asset.count*1.0)*100.0,0.0))SETasset.state=CASE// Set threshold ratios below for each of three cases //WHENasset.successPercent>=90THEN'good'WHENasset.successPercent>=75ANDasset.successPercent<90THEN'warn'WHENasset.successPercent<75THEN'alarm'ELSE'alarm'END
Finally, relationships are created for all of the nodes generated from the event.
//////////////////////////////// Create relationship between nodes//////////////////////////////CREATE(user)-[:ORIGINATED]->(attempt)-[:USING]->(client),(client)<-[:USING]-(attempt)-[:TARGETED]->(asset),(user)-[:ORIGINATED]->(attempt)-[:TARGETED]->(asset),(attempt)-[:OVER]->(asn)
The complete INGEST-1 ingest stream configuration processes the endpoints.json file:
{"name":"sequence-attempts","pattern":{"type":"Cypher","query":"MATCH (client2)<-[:USING]-(attempt1)<-[:ORIGINATED]-(user)-[:ORIGINATED]->(attempt2)-[:USING]->(client1)\nRETURN DISTINCT id(attempt2) AS attempt2","mode":"DISTINCT_ID"},"outputs":[{"name":"sequence","preEnrichmentTransformation":{"type":"InlineData"},"destinations":[{"type":"CypherQuery","query":"MATCH (client2)<-[:USING]-(attempt2)<-[:ORIGINATED]-(user)-[:ORIGINATED]->(attempt1 {clientSequence: (attempt2.clientSequence-1)})-[:USING]->(client1)\nWHERE id(attempt2) = $that.attempt2\n AND id(client1) = id(client2)\nCREATE (attempt2)<-[:NEXT]-(attempt1)","parameter":"that"}]}]}
A second standing query matches 4 consecutive failed attempts followed by a successful attempt and outputs a URL that can be copied and pasted into a browser to open the subgraph in Quine for exploration.
Quine will process the events looking for the subgraph pattern that we defined in STANDING-2. When it encounters the pattern, it will emit a link to the event in the Exploration UI.
Take time to explore the graph in the Quine Exploration UI. Start by right clicking on the contractor node and selecting the Attempts Timeline quick query to generate the attack timeline.
The recipe contains a number of additional quick queries to view events.