A collection of small helpers for incident response workflows.
Enrich a list of suspicious mails across Exchange Online mailboxes using Microsoft Graph.
This script is intended for incident response after a Microsoft 365 user has been suspected or confirmed as compromised and mailbox content needs to be scoped quickly. The usual first step is to identify which mails were accessed for a given user. This tool takes that initial list of InternetMessageId`s and enriches it with additional mailbox context, such as:
- Subject
- Sender
- Recipients
- Sent timestamp
- Received timestamp
- Folder location
- Whether the message exists in the targeted mailbox
That makes it easier to turn a raw "message was accessed" list into a timeline that an analyst can use for triage, scoping, and reporting.
-
Prepare Message IDs: Create a file (e.g.,
message_ids.txt) containing the Internet Message IDs you want to enrich, one per line.- The script deduplicates repeated values automatically.
- Empty lines are ignored.
-
Register App: Run the helper script to create an Azure AD app with the Microsoft Graph application permissions required for mailbox-wide lookup.
./mailreadappcreate.sh
Use
./mailreadappcreate.sh --helpto see the full help page with requirements, flags, and examples. If an app namedGraph-Mail-IR-Exporteralready exists, the helper removes it first and creates a fresh registration. This helper creates:- An app registration named
Graph-Mail-IR-Exporter - A service principal
- Microsoft Graph application permissions for
Mail.Read,User.Read.All, andDirectory.Read.All - Tenant-wide admin consent
- A client secret for the app
- Client-credential access for Graph API lookups, so the Python script can run without interactive user sign-in
Optional helper flags:
--app-name: Override the app registration display name--secret-name: Override the client secret display name
Treat the output as sensitive. Note the Tenant ID, Client ID, and Client Secret from the output and store them securely.
- An app registration named
Execute the Python script using the credentials from the setup step:
python3 graph_mail_ir.py \
--tenant-id <tenant-id> \
--client-id <client-id> \
--client-secret "<client-secret>" \
--input message_ids.txt \
--output mail_timeline.csvUse python3 graph_mail_ir.py --help to view all flags, required inputs, output details, and incident-response examples directly from the script.
By default the script searches all users returned by Microsoft Graph and tries to match each Internet Message ID against every mailbox it can access.
Use --user when you already know which mailbox or mailboxes should be searched. This is the recommended mode during incident response because it reduces lookup time and keeps the scope focused on the affected user or users.
Examples:
# Search one mailbox
python3 graph_mail_ir.py \
--tenant-id <tenant-id> \
--client-id <client-id> \
--client-secret "<client-secret>" \
--input message_ids.txt \
--output mail_timeline.csv \
--user alice@contoso.com
# Search multiple specific mailboxes
python3 graph_mail_ir.py \
--tenant-id <tenant-id> \
--client-id <client-id> \
--client-secret "<client-secret>" \
--input message_ids.txt \
--output mail_timeline.csv \
--user alice@contoso.com \
--user bob@contoso.com
# Search a comma-separated list or a file of UPNs
python3 graph_mail_ir.py \
--tenant-id <tenant-id> \
--client-id <client-id> \
--client-secret "<client-secret>" \
--input message_ids.txt \
--output mail_timeline.csv \
--user users.txtNotes:
--usercan be repeated.--useralso accepts comma-separated values.- If the value points to a file, each non-empty line is treated as a mailbox UPN.
- The legacy
--upnflag is still accepted for compatibility, but--useris the documented interface and should be used for new workflows.
The script writes a semicolon-delimited CSV with one row per Internet Message ID.
Columns:
Mailbox: The mailbox that contained the message, if foundMessageId: The Internet Message ID from the input fileSubject: Message subjectFrom: Sender addressTo: Recipient addresses joined as a stringSentTime: Message sent timestampReceivedTime: Message received timestampFolder: Mail folder name, when it can be resolvedExistsInMailbox:Trueif the message was found in the targeted mailbox scope, otherwiseFalse
- A suspicious mailbox is identified during incident response.
- The responder extracts the relevant Internet Message IDs from audit data, mailbox evidence, or another source of mailbox activity.
graph_mail_ir.pyis run against those IDs.- The output CSV is used to enrich the initial evidence set with message metadata.
- The resulting dataset can then be used to build a timeline, identify affected mail, and support scoping or reporting.
This script does not try to prove compromise by itself. Its purpose is to enrich evidence that already points to mailbox access or message interaction.
- If you omit
--user, the script searches all accessible users and may take longer on large tenants. - Results are returned in the same order as the input file.
- The script skips duplicate Internet Message IDs to avoid repeated lookups.
- For each Internet Message ID, the script reports the first mailbox match it finds in the selected scope.
Checks a list of IPs for VPN indicators using ipapi.is.
- Install
jqandcurl. - Set your API key in
/Tools/apikeys.txt:export ipapisisapi="YOUR_API_KEY"
- Create an input file
ips.txt(or specify another with-i).
./vpnchecker.sh -i ips.txtFlags IPs as MALICIOUS (VPN detected), OK (Not VPN), or UNKNOWN.
Checks a suspicious URL with a compact SOC-friendly verdict. The script first follows redirects to the final destination, then resolves that host and enriches the first resolved IP, then checks URL reputation, then checks the host and root domain.
Install jq, dig, curl, base64, and python3.
API keys are loaded from /root/Tools/apikeys.txt. The script verifies that the required key variables exist before starting. Cloudflare Radar is verified with /user/tokens/verify; if the token is invalid during runtime, that source is skipped and the other checks continue.
./urlir.sh https://example.com/path
./urlir.sh -file URLs.txtCloudflare Radar is automatic: the script first searches for an existing URL Scanner result, uses it when available, and only creates a new scan when no usable result exists. When a URL redirects, the final destination is the one that is checked and the redirect hop count is shown in the output. File mode reads one URL per non-empty line and ignores lines starting with #.
The output is intentionally short:
- Resolved IP context
- Redirected final destination, when applicable
- URL reputation
- Host and root-domain reputation
- Traffic-light verdict: CLEAN, SUSPICIOUS, or MALICIOUS
- One-line source errors when an API is blocked, rate-limited, or unavailable
Finds distinct current and historical hostnames related to an IP address by querying multiple OSINT sources (Shodan, VirusTotal, Passive DNS, etc.).
-
Python 3 and curl must be installed.
-
API Keys: Many sources require API keys. The script checks:
/root/Tools/apikeys.txt./apikeys.txt- Environment variables
Supported keys include:
shodan,virustotal,otx,riskiq_user,riskiq_key,urlscan,securitytrails,censys,netlas,fofa_key,criminalip,whoisjson,ipinfo, andipapisis.
Note: If no API keys are provided, it will still use local resolution, Google DoH, crt.sh, and HackerTarget.
Single IP (Full Analysis):
python3 iptohost.py -ip 1.2.3.4Batch (File or CIDR Range):
python3 iptohost.py -f ips.txt
python3 iptohost.py -r 1.2.3.0/24JSON Output:
python3 iptohost.py -ip 1.2.3.4 --json- Active Resolutions: Hostnames that currently resolve to the target IP.
- Historical Findings: Passive DNS, certificate transparency records, and historical OSINT database entries.
- VT Score: Displays VirusTotal malicious detection ratio for the IP.