Monthly Archives: July 2023

Kusto Detective Agency Season 2: Case 5 – Blast into the past

Challenges

Click for challenges

  • Onboarding: Here
  • Challenge 1: Here
  • Challenge 2: Here
  • Challenge 3: Here
  • Challenge 4: Here
  • Challenge 5: This article
  • Challenge 6: Here
  • Challenge 7: Here
  • Challenge 8: Here
  • Challenge 9: Coming soon
  • Challenge 10: Coming soon

Prof. Smoke is back and so is the mysterious El Puente but an interview on a new secret KQL technology has gone missing! Luckily we have all the tools to track down the missing file.

General advice

I must say the clues this time around felt very fluffy, it seems that they are intended to be vague, and the meat of the tips will be presented in the “train me” content. I feel like this is a step in the wrong direction as it’s sometimes helpful to have a better logic clue then just training.

Challenge: Case 5

Case 5 challenge text

Hey there!

It’s been a while since we talked, my friend. I’ve been keeping an eye on your remarkable detective work, and you never cease to amaze me. Well done! Just a friendly word of caution, though—I have a feeling you’re getting closer to the edge. Are you ready to take the leap?

Anyway, I’ve got an offer that’ll pique your interest. I’ve stumbled upon some seriously valuable intel about Kuanda org, and I know it could be a game-changer for the case you’re currently tackling. But before I speak, I need your assistance with a little something.

You know about Scott Hanselman’s incredible video podcasts, right? The guy’s a legend! He usually drops them every week, and they rack up thousands of views. But here’s the twist: Something mysterious went down with his 900th episode, slated for release a few weeks back. Rumor has it that Prof. Smoke, the renowned Big Data expert, spilled the beans on some top-secret functionality during the interview. The thing is, he had a change of heart and insisted the video be scrapped, so it never saw the light of day.

Now, hold on to your detective hat, because this is where it gets intriguing. I have a hunch that the interview was actually published before its deletion. Everyone assumed it was gone for good, but I managed to sneak my way into the public archive logs. These logs have all the juicy details—creation and replication timestamps, access records, and even deletion operations. My hope is that the deletions weren’t fully synced into the archive, leaving behind some remnants of that epic interview.

So, here’s the deal, my friend. If you can crack this case and unearth the elusive interview link, I promise to tell everything about Kuanda.org. Believe me, the information I have is pure gold.

Are you ready to dive headfirst into this thrilling challenge? Find that elusive video URL, and I’ll do everything in my power to dig up every last detail about Kuanda.org.

Let me know if you’re in, and let the game begin, detective!

Cheers,
El Puente

This case is awesome in that there are several different routes to arrive at the answer depending on your logical approach.

Query Hint

Depending on how you go about solving this puzzle you can make use of several different KQL commands ones I found useful were parse, let and split and found it very helpful to tackle this challenge in stages.

Solution – Spoilers below

Chatting to the community on this one has been awesome, I’ve seen 4 completely unique approaches to solving this puzzle and I’m going to talk about mine and one other below.

Query Case 5

//I tackled this challenge in a set of logical steps, looking for blobs that had no views, which had been deleted but not completely deleted. Using this in combination with hosts that had a dip in usage got to several suspect files one of which was the missing interview. Not elegant by any means but it got the job done.

let hosts=
   StorageArchiveLogs
| parse EventText with TransactionType ” blob transaction: ‘” BlobURI “‘” *
| parse EventText with * “(” Reads:long “reads)” *
| extend Host = tostring(parse_url(BlobURI).Host)
| summarize Deletes=countif(TransactionType  == ‘Delete’),
        Creates =countif(TransactionType == ‘Create’),
        Reads=sumif(Reads, TransactionType == ‘Read’) by Host
| where Deletes > 0
| where Creates > 0
| where Creates > Deletes
| order by Reads
| take 50;
let Scott =
StorageArchiveLogs
| parse EventText with TransactionType ” blob transaction: ‘” BlobURI “‘” *
| parse EventText with * “(” Reads:long “reads)” *
| extend Host = tostring(parse_url(BlobURI).Host)
| where Host in~ (hosts)
| make-series Count=sumif(Reads, TransactionType == ‘Read’) default=0 on Timestamp step 12h by Host
| project series_periods_validate(Count,14,10), Host, Count
| extend Score = tostring(series_periods_validate_Count_scores[0])
| where Score != “0.0”
| project Host;
let deletes=
StorageArchiveLogs
| parse EventText with TransactionType ” blob transaction:” *
| parse EventText with * “blob transaction: ‘” BlobURI “‘” *
| where EventText has “completely”
| distinct BlobURI;
let uri=
StorageArchiveLogs
| parse EventText with TransactionType ” blob transaction:” *
| parse EventText with “‘ read access (” ReadCount:long ” reads) were detected on the origin”
| parse EventText with * “blob transaction: ‘” BlobURI “‘” *
| extend Host = tostring(parse_url(BlobURI).Host)
| where Host in (Scott)
| project EventText, BlobURI, TransactionType, ReadCount, Host
| summarize Deletes=countif(TransactionType  == ‘Delete’),
        Creates =countif(TransactionType == ‘Create’),
        Reads=sumif(ReadCount, TransactionType == ‘Read’) by Host, BlobURI
| sort by Reads asc
| where Deletes > 0
| where Creates > 0
| distinct BlobURI;
StorageArchiveLogs
| parse EventText with TransactionType ” blob transaction:” *
| parse EventText with * “blob transaction: ‘” BlobURI “‘” *
| where BlobURI in~ (uri) and BlobURI !in (deletes)
| where EventText has_all (“backup”,”create”)
| distinct EventText
| extend SplitAll=split(EventText, ‘ ‘)
| extend Backup=tostring(SplitAll[8])
| project-away  EventText, SplitAll
| where Backup contains “mp4”

//A far neater approach by my colleague Nabeel Prior

// get the blob accounts that had 1 new creation each week for 4 weeks
let weeklyCreates =
StorageArchiveLogs
| where EventText startswith “Create blob”
| project
    createTS=Timestamp,
    blobAccount=substring(EventText, indexof(EventText, “https://”), indexof(EventText, “/”, 1, 1000, 3) – indexof(EventText, “https://”))
| summarize accountCreatesPerWeek = count() by blobAccount, week_of_year(createTS)
| where accountCreatesPerWeek == 1
| summarize count() by blobAccount
| where count_ >= 4;
// get all the creates for the above blob accounts
let allCreates =
StorageArchiveLogs
| where EventText startswith “Create blob”
| project
    blobAccount=substring(EventText, indexof(EventText, “https://”), indexof(EventText, “/”, 1, 1000, 3) – indexof(EventText, “https://”)),
    createUrl=substring(EventText, indexof(EventText, “https://”), indexof(EventText, “‘ backup is created on “) – indexof(EventText, “https://”)),
    backupUrl=tostring(split(replace_string(EventText, “Create blob transaction: ‘”, “”), “‘ backup is created on “, 1))
| join kind=inner weeklyCreates on $left.blobAccount == $right.blobAccount;
// for all creates above, find those that were partially deleted
StorageArchiveLogs
| where EventText startswith “Delete blob”
| extend deleteUrl=substring(EventText, indexof(EventText, “https://”), indexof(EventText, “‘ backup is”) – indexof(EventText, “https://”))
| extend deleteAction=iif(EventText contains “completely”, 1, 0)
| join kind=inner allCreates on $left.deleteUrl == $right.createUrl
| summarize completelyDeleted=sum(deleteAction), firstDelete=min(Timestamp), lastDelete=max(Timestamp), numberOfDeletes=countif(deleteAction == 0) by deleteUrl, backupUrl
| where completelyDeleted==0;

This weeks puzzle was great fun, and I really enjoyed the discussions about the various approaches to solving it. Well done detectives and kep an eye out for El Puente because we don’t know where they may strike next!

Loading

Kusto Detective Agency Season 2: Case 4 – Triple trouble!

Challenges

Click for challenges

  • Onboarding: Here
  • Challenge 1: Here
  • Challenge 2: Here
  • Challenge 3: Here
  • Challenge 4: This article
  • Challenge 5: Here
  • Challenge 6: Here
  • Challenge 7: Here
  • Challenge 8: Here
  • Challenge 9: Coming soon
  • Challenge 10: Coming soon

Uh oh, the mayor is in trouble, and it seems like there’s a mastermind behind all of the nefarious ongoings. These cases are getting more interesting, and the difficulty is starting to ramp up significantly.

General advice

The training helps significantly with this case, there is only one thing that I would change, which I will mention in the query hint section. I also feel like a cybersecurity background would make this challenge significantly easier but that’s just my personal opinion.

Challenge: Case 4

Case 4 challenge text

Dear Kusto Detective Agent,

It’s me, Gaia Budskott, the mayor of Digitown… I am writing to you because I am in desperate need of your help.

Recently, I have been caught up in not one, not two, but three different police investigations. The police suspect that I am behind a series of crimes that I have nothing to do with. My personal electricity and billing account was apparently undercharged for the past few months, and phishing calls were made from numbers associated with my office. To top it off, secret documents that belong to me were found in a garage where stolen cars were placed.

I am at my wit’s end and I don’t know who to turn to. I can’t ask the police for help because they think I’m the one behind all of this. That’s why I’m turning to you – perhaps you can help me figure out who is really behind all of this.

I suspect that someone has hacked into the Digitown municipality system and stolen these documents. Our system is a known data hub and hosts various information about the town itself, real-time monitoring systems of the city, tax payments, etc. It serves as a real-time data provider to many organizations around the world, so it receives a lot of traffic.

Unfortunately, I don’t have much data to give you. All I have is a 30-day traffic statistics report captured by the Digitown municipality system network routers.

I am hoping that your expertise and knowledge in big data analytics can help shed some light on who is behind these crimes and clear my name.

Please, can you help me?

Sincerely,
Gaia

There is a lot to unpack to crack this case and several interesting learnings about the capability of KQL along the way.

Query Hint

Understanding what this case wants you to do can be a bit tricky, I found the training to be very useful this time around except for the focus on series_decompose_forecast, none of the solutions I’ve seen make use of this command, so I feel in this instance it is a little misleading. Instead check out these KQL commands make-series and series_decompose_anomalies.

Solution – Spoilers below

There are several different ways to get to the result in this case but they all lead to the same conclusion.

Query Case 4

//One of the great things about these challenges is the community collaboration that comes from them. Big thanks to the master Matt Zorich for giving me a hand with this one as my boxing match with the series_decompose_anomalies syntax did not end in my favour.

let cidr=
IpInfo;
let potentials=
NetworkMetrics
| make-series Clients=sum(BytesSent) on Timestamp step 12h by ClientIP
| extend outlier=series_decompose_anomalies(Clients)
| mv-expand outlier
| where outlier == 1
| distinct ClientIP
| evaluate ipv4_lookup(cidr, ClientIP, IpCidr)
| summarize count()by Info
| top 10 by count_
| distinct Info;
NetworkMetrics
| evaluate ipv4_lookup(cidr, ClientIP, IpCidr)
| where Info in (potentials)
| make-series Bytes=sum(BytesSent) default=0 on Timestamp step 12h by Info
| render timechart

//I’d also suggest checking out Lizel Hughs solution for a very elegant approach and bonus use of render anomalychart Season 2 Case 4: Triple trouble! | Liesel’s Tech Ramblings Blog (lieselhughes.com)

I wonder how long before we discover who is behind this sudden rise in crime, stay vigilant detectives!

Loading