Link analysis with Obsidian
I’ve been experimenting with using Obsidian for investigation work such as OSINT cases or incident response. While it’s not a traditional link analysis tool like Maltego, with the right structure and plugins, Obsidian can be surprisingly effective. Check this if you do not know what Obsidian is.
Entities with templates
In Maltego, you work with entities like IP address or domain name that are placed onto a graph. In Obsidian, I replicate this by creating entity templates. Here’s an example template for an IP address:
---
tag: ip
entity: ip
---
#anything else that you want to include#
A domain
entity template might look like this:
---
tag: domain
entity: domain
---
## DNS
<some details>
## WHOIS
<content>
The key part here is the entity
field. The tag
is less important for this workflow, though I usually set it to the same value for consistency and compatibility with other features.
If you’re looking for inspiration, you can find some example templates online by searching for “Obsidian OSINT templates.” I may publish my own at some point once they’re cleaned up and fine-tuned.
I keep all my entity templates in a dedicated folder. This is pretty much required, since Obsidian’s core Templates plugin (and the popular Templater plugin) assume that kind of structure. I also use the Iconize plugin to add icons to each entity type. That way, they show up nicely in the file navigator, like this:
This can be done with templates by adding the icon
entry to YAML frontmatter. For example:
icon: LiRouter
Sample case
Let’s walk through a small sample case that mimics an investigation triggered by an IDS alert. Here’s the starting point:
[**] [1:2023456:3] ET POLICY Suspicious Outbound Connection to External Host [**]
[Classification: Potential Corporate Privacy Violation] [Priority: 2]
04/12-14:36:45.892345 192.168.10.25:53214 -> 203.0.113.77:8080
TCP TTL:64 TOS:0x0 ID:39485 IpLen:20 DgmLen:60 DF
***AP*** Seq: 0x8A32D5F7 Ack: 0x3F2C91A4 Win: 0x16D0 TcpLen: 32
TCP Options (3) => NOP NOP TS: 134562738
Preparation
One entity I like to use is of type event
, which in this case represents the starting point for the investigation. Of course, more events can be added later as the case develops.
I create an event note from the alert by making a new note and applying the event
template to it.
I’m using Templater plugin here because the event template contains Templater-specific syntax that automatically updates the filename. If you don’t need that kind of data manipulation during note creation, Obsidian’s core Templates plugin works just fine. Here’s how the note could look in Markdown.
---
entity: event
tags:
- event
icon: LiClock
---
# Description
[**] [1:2023456:3] ET POLICY Suspicious Outbound Connection to External Host [**]
[Classification: Potential Corporate Privacy Violation] [Priority: 2]
04/12-14:36:45.892345 192.168.10.25:53214 -> 203.0.113.77:8080
TCP TTL:64 TOS:0x0 ID:39485 IpLen:20 DgmLen:60 DF
***AP*** Seq: 0x8A32D5F7 Ack: 0x3F2C91A4 Win: 0x16D0 TcpLen: 32
TCP Options (3) => NOP NOP TS: 134562738
# Key Info
Host [[192.168.10.25]] made a suspicious connection to [[203.0.113.77]]:8080.
# Analysis
TBD
Note [[<ip>]]
syntax within the “Key Info” section. That creates a wikilink to another note. The link works even if the note doesn’t exist yet and note is autocreated when clicked.
Data collection
Suppose we don’t yet know what asset is associated with the local IP. Clicking the link in the event note creates an empty note with the IP address as its title.
Next we will apply the ip
entity template to this note which populates us entity related information.
As we collect information, we can add details to the note:
Using Obsidian’s Graph View, we can quickly see how notes are connected:
Grayed-out nodes in the graph represent linked notes that don’t exist yet. You can also group (color) nodes based on properties like tags.
Analyze with canvas
Now let’s take a look at the Canvas feature. Obsidian’s documentation describes it like this:
Canvas allows you to organize notes visually — an infinite space to research, brainstorm, diagram and lay out your ideas.
This feature is quite neat, but sometimes you want some automation with it. By default you need to add everything manually. There are some plugins like Link Exploder that does help with this. There were some small things why none of the plugins worked for my usecase, so I decided to do some own scripting.
I’m not interested on creating an actual plugin at the moment and Obsidian stores all notes as markdown files on the filesystem so those can be manipulated directly without Obsidian. My approach is a Python script that does the following:
- Takes path to a note as an argument.
- All wikilinks are parsed from the note.
- These steps are repeated recursively until all the related notes are parsed.
- Creates a canvas file where notes are nodes and parsed wikilinks are edges (connections).
- Edges are auto labeled based on the entity of the linked note.
For example, a note “foo” links to a note “bar” and “bar” has entity type of “domain”. Here the script would populate connection foo --domain--> bar
.
Let’s take a look how canvas populated with the sample case data looks like. First we launch the script:
python3 .meta/canvas.py -f Cases/CaseSample/310820250047\ -\ IDS\ alert.md -o Cases/CaseSample/case.canvas
Now we can open the “case.canvas” inside Obsidian.
Moving the nodes makes is it a bit easier to see the connections.
From here we can add information directly to the canvas. In addition to notes you can directly insert, for example, images or just text boxes.
Here’s the canvas.py
script:
Other considerations
De-cluttering
Instead of separate ip
and domain
entities, you might use a more general host
entity to cover both. This reduces the total number of notes and can make your canvas less cluttered.
Obsidian also has a built-in alias feature that comes in handy. For example, suppose you have an email
entity for john.doe@example.com
linked to the person
entity John Doe. Later, you discover another email, john.doe@domain.local
. You can add it as an alias in the existing email note’s frontmatter like this:
alias: john.doe@domain.local
The downside is that aliases don’t show up in Graph or Canvas views. Whether that’s an issue depends on your workflow.
Duplicate information
When writing content for each entity, it’s easy to accidentally duplicate information across multiple notes. If you just want to reference an entity, keep it simple: add a wikilink like [[some entity]]
to an otherwise empty note and move on.
Integrating the canvas script
There are plugins that allow command execution from within Obsidian. Using one of these, the canvas generation script could potentially be integrated directly into your workflow.
Naming limitations
Sometimes it could be useful to have :
in the note title. For example, with ip-port number combination. In the example case we could have had a note 203.0.113.77:8080
instead of 203.0.113.77
. Here some own naming schema is need like using just dash 203.0.113.77-8080-tcp
.