Behavior-based detection engineering
Rare Chunked Curl Upload on macOS
Detects first-seen curl PUT uploads on macOS using binary transfer and recurring chunk parameters, then confirms matching same-process network activity.
Behavior
What it detects
A macOS device with no recent matching curl-upload history performs binary HTTP PUT activity with upload session and chunk parameters, confirmed by network telemetry from the same process.
Engineering decision
Why this detection
curl is a standard macOS utility, and HTTP PUT is not inherently suspicious. The detection therefore requires a much narrower sequence: a current Mac with no recent matching history performs binary PUT with all recurring chunk parameters, and endpoint network telemetry confirms the same stable curl process.
The domain is deliberately not part of the primary requirement. That choice mirrors the research finding that MacSync infrastructure rotates faster than its request and execution behavior.
Signal chain
Detection logic
- Restrict the candidate population to current macOS endpoints.
- Collect curl processes using HTTP PUT, binary upload, and all three recurring chunk parameters.
- Build a thirty-day per-device baseline for the same high-specificity curl upload behavior.
- Keep devices without prior matching behavior.
- Require matching network telemetry from the same stable curl process identity.
- Require network activity within two minutes of the upload process event.
- Preserve user, device, command line, destination, parent, and hash context for triage.
Primary analytic
Query
Q-01Detection logicRare chunked curl upload on macOS
What this checks
Detect a macOS curl process performing chunked HTTP PUT upload behavior with the recurring MacSync request parameters, then confirm matching network activity from the same stable process identity.
KQL
let current_window = 1d;
let baseline_window = 30d;
let mac_devices =
DeviceInfo
| where Timestamp >= ago(7d)
| summarize arg_max(Timestamp, DeviceName, OSPlatform) by DeviceId
| where OSPlatform startswith "macOS"
| project DeviceId, MacDeviceName=DeviceName, OSPlatform;
let historical_uploads =
DeviceProcessEvents
| where Timestamp between (ago(baseline_window) .. ago(current_window))
| where FileName =~ "curl"
| where ProcessCommandLine has_all ("-X PUT", "--data-binary", "upload_id=", "chunk_index=", "total_chunks=")
| summarize by DeviceId;
let upload_processes =
DeviceProcessEvents
| where Timestamp >= ago(current_window)
| where FileName =~ "curl"
| where ProcessCommandLine has_all ("-X PUT", "--data-binary", "upload_id=", "chunk_index=", "total_chunks=")
| project
ProcessTime=Timestamp,
DeviceId,
DeviceName,
AccountUpn,
ProcessUniqueId,
ProcessCommandLine,
FolderPath,
SHA1,
SHA256,
InitiatingProcessFileName,
InitiatingProcessFolderPath,
InitiatingProcessCommandLine;
let upload_network =
DeviceNetworkEvents
| where Timestamp >= ago(current_window)
| where InitiatingProcessFileName =~ "curl"
| where RemoteUrl has_all ("upload_id=", "chunk_index=", "total_chunks=")
| project
NetworkTime=Timestamp,
DeviceId,
InitiatingProcessUniqueId,
RemoteUrl,
RemoteIP,
RemotePort,
Protocol,
NetCommandLine=InitiatingProcessCommandLine;
upload_processes
| join kind=inner mac_devices on DeviceId
| join kind=leftanti historical_uploads on DeviceId
| join kind=inner upload_network on $left.DeviceId == $right.DeviceId and $left.ProcessUniqueId == $right.InitiatingProcessUniqueId
| where NetworkTime between (ProcessTime - 2m .. ProcessTime + 2m)
| project
ProcessTime,
NetworkTime,
DeviceName,
OSPlatform,
AccountUpn,
ProcessCommandLine,
RemoteUrl,
RemoteIP,
RemotePort,
Protocol,
InitiatingProcessFileName,
InitiatingProcessFolderPath,
InitiatingProcessCommandLine,
SHA1,
SHA256
| order by ProcessTime descSPL
(
index=<endpoint_process_index> sourcetype=<process_events_sourcetype> earliest=-30d
)
OR
(
index=<endpoint_network_index> sourcetype=<endpoint_network_events_sourcetype> earliest=-30d
)
OR
(
index=<device_inventory_index> sourcetype=<device_info_sourcetype> earliest=-7d
)
| eval
device=coalesce(device,DeviceName,host),
device_id=coalesce(device_id,DeviceId),
os=coalesce(os,OSPlatform),
process_name=lower(coalesce(process_name,FileName,InitiatingProcessFileName)),
process_uid=coalesce(process_uid,ProcessUniqueId,InitiatingProcessUniqueId),
cmd=coalesce(process_command_line,ProcessCommandLine,InitiatingProcessCommandLine),
remote_url=coalesce(remote_url,RemoteUrl),
event_type=case(
isnotnull(OSPlatform),"device_info",
process_name="curl"
AND like(cmd,"%-X PUT%")
AND like(cmd,"%--data-binary%")
AND like(cmd,"%upload_id=%")
AND like(cmd,"%chunk_index=%")
AND like(cmd,"%total_chunks=%"),"upload_process",
isnotnull(remote_url)
AND process_name="curl"
AND like(remote_url,"%upload_id=%")
AND like(remote_url,"%chunk_index=%")
AND like(remote_url,"%total_chunks=%"),"upload_network",
true(),"other"
),
is_current=if(_time>=relative_time(now(),"-1d"),1,0)
| where event_type!="other"
| eventstats
latest(eval(if(event_type="device_info",os,null()))) as os_platform
by device_id
| where like(lower(os_platform),"macos%")
| eventstats
count(eval(event_type="upload_process" AND is_current=0)) as historical_matching_uploads
by device_id
| stats
min(_time) as first_time
max(_time) as last_time
max(eval(if(event_type="upload_process" AND is_current=1,1,0))) as has_upload_process
max(eval(if(event_type="upload_network" AND is_current=1,1,0))) as has_upload_network
values(eval(if(event_type="upload_process",cmd,null()))) as process_commands
values(eval(if(event_type="upload_network",remote_url,null()))) as remote_urls
values(RemoteIP) as remote_ips
values(RemotePort) as remote_ports
values(AccountUpn) as users
values(InitiatingProcessFileName) as parents
by device_id device process_uid os_platform historical_matching_uploads
| where historical_matching_uploads=0 AND has_upload_process=1 AND has_upload_network=1
| where last_time-first_time<=120
| convert ctime(first_time) ctime(last_time)
| sort - last_timeWhat to look for
A macOS device with no recent matching curl-upload baseline performs binary PUT activity carrying upload session and chunk parameters, with matching outbound network telemetry.
Technical details
Tested signal
First-seen curl PUT with binary upload and chunk identifiers, joined to same-process network activity.
Assumptions
- DeviceInfo contains current OSPlatform values.
- ProcessUniqueId and InitiatingProcessUniqueId are populated for the endpoint.
- Thirty days is an initial baseline for similar curl upload behavior.
Data requirements and relevant fields
- endpoint
Device inventory used to restrict analytics to current macOS endpoints.
TimestampDeviceIdDeviceNameOSPlatform
- process
Endpoint process creation telemetry with stable process identity, path, command line, parent, and user context.
TimestampDeviceIdDeviceNameFileNameFolderPathProcessIdProcessUniqueIdProcessCommandLineAccountNameAccountUpnSHA1SHA256InitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessIdInitiatingProcessUniqueIdInitiatingProcessAccountNameInitiatingProcessAccountUpn
- network
Endpoint network telemetry with destination URL, port, initiating process, user, and stable process identity.
TimestampDeviceIdDeviceNameRemoteUrlRemoteIPRemotePortProtocolLocalIPLocalPortInitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessAccountNameInitiatingProcessAccountUpnInitiatingProcessUniqueId
KQL schema
Validate Defender for Endpoint coverage on macOS, table availability, field population, and retention before operational use.
SPL schema
Replace index/sourcetype placeholders and map device, process, file, and network fields to local telemetry.
Limitations
- Developers, backup tools, or internal automation can legitimately use curl PUT and binary uploads.
- The exact URI path can evolve; the primary logic requires the more durable upload parameter combination.
KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.
Analyst workflow
What the analyst should look for
- Is the device a managed macOS endpoint?
- Does the user or application legitimately perform scripted curl PUT uploads?
- Is the curl process launched from an interactive shell, AppleScript, automation agent, or approved backup client?
- Do the request parameters contain upload session and chunk semantics?
- Was there earlier curl payload retrieval or decode/unpack activity?
- Did osascript participate in shell, copy, cleanup, or retrieval behavior?
- Was sensitive data staged under temporary paths and archived before the upload?
- Were temporary artifacts removed after the transfer?
Expected result
A macOS device with no recent matching curl-upload baseline performs binary PUT activity carrying upload session and chunk parameters, with matching outbound network telemetry.
Investigation pivots
Drilldowns
View query — Review shell-to-curl payload retrieval
Q-02DrilldownReview shell-to-curl payload retrieval
What this checks
Determine whether the candidate macOS device used an interactive Unix shell to retrieve payload content through the recurring retrieval path.
KQL
let target_device="<DEVICE_NAME>";
DeviceNetworkEvents
| where Timestamp >= ago(7d)
| where DeviceName =~ target_device
| where InitiatingProcessFileName =~ "curl"
| where RemoteUrl has "/curl/"
| project
Timestamp,
DeviceName,
RemoteUrl,
RemoteIP,
RemotePort,
InitiatingProcessFileName,
InitiatingProcessFolderPath,
InitiatingProcessCommandLine,
InitiatingProcessAccountUpn,
InitiatingProcessUniqueId
| order by Timestamp ascSPL
index=<endpoint_network_index> sourcetype=<endpoint_network_events_sourcetype> earliest=-7d
| eval
device=coalesce(device,DeviceName,host),
process_name=lower(coalesce(process_name,InitiatingProcessFileName)),
remote_url=coalesce(remote_url,RemoteUrl),
cmd=coalesce(cmd,InitiatingProcessCommandLine)
| where device="<DEVICE_NAME>" AND process_name="curl" AND like(remote_url,"%/curl/%")
| table _time device process_name cmd RemoteIP RemotePort remote_url InitiatingProcessAccountUpn InitiatingProcessUniqueId
| sort 0 _timeWhat to look for
curl retrieval activity from a user shell, especially when the request path matches the recurring payload-retrieval shape.
Technical details
Tested signal
zsh or shell context followed by curl reaching a retrieval URI path.
Assumptions
- The candidate device and investigation window are known from Q-01.
Data requirements and relevant fields
- process
Endpoint process creation telemetry with stable process identity, path, command line, parent, and user context.
TimestampDeviceIdDeviceNameFileNameFolderPathProcessIdProcessUniqueIdProcessCommandLineAccountNameAccountUpnSHA1SHA256InitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessIdInitiatingProcessUniqueIdInitiatingProcessAccountNameInitiatingProcessAccountUpn
- network
Endpoint network telemetry with destination URL, port, initiating process, user, and stable process identity.
TimestampDeviceIdDeviceNameRemoteUrlRemoteIPRemotePortProtocolLocalIPLocalPortInitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessAccountNameInitiatingProcessAccountUpnInitiatingProcessUniqueId
KQL schema
Validate Defender for Endpoint coverage on macOS, table availability, field population, and retention before operational use.
SPL schema
Replace index/sourcetype placeholders and map device, process, file, and network fields to local telemetry.
Limitations
- Administrative scripts can legitimately use shell-launched curl.
KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.
View query — Inspect AppleScript-assisted shell activity
Q-03DrilldownInspect AppleScript-assisted shell activity
What this checks
Find osascript activity using shell or native utilities observed in the post-execution chain.
KQL
let target_device="<DEVICE_NAME>";
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where DeviceName =~ target_device
| where FileName =~ "osascript"
| where ProcessCommandLine has_any ("sh -c", "cp ", "rm ", "curl ", "mkdir ", "killall", "dscl")
| project
Timestamp,
DeviceName,
AccountName,
AccountUpn,
FileName,
FolderPath,
ProcessCommandLine,
ProcessUniqueId,
SHA1,
SHA256,
InitiatingProcessFileName,
InitiatingProcessCommandLine
| order by Timestamp ascSPL
index=<endpoint_process_index> sourcetype=<process_events_sourcetype> earliest=-7d
| eval
device=coalesce(device,DeviceName,host),
process_name=lower(coalesce(process_name,FileName)),
cmd=coalesce(cmd,ProcessCommandLine)
| where device="<DEVICE_NAME>" AND process_name="osascript"
AND (
like(lower(cmd),"%sh -c%")
OR like(lower(cmd),"%cp %")
OR like(lower(cmd),"%rm %")
OR like(lower(cmd),"%curl %")
OR like(lower(cmd),"%mkdir %")
OR like(lower(cmd),"%killall%")
OR like(lower(cmd),"%dscl%")
)
| table _time device AccountName AccountUpn process_name cmd InitiatingProcessFileName InitiatingProcessCommandLine
| sort 0 _timeWhat to look for
osascript activity that quickly chains native utilities into network, staging, or cleanup behavior.
Technical details
Tested signal
AppleScript-assisted shell, copy, removal, retrieval, directory, process, or user-discovery commands.
Assumptions
- The candidate device is known.
Data requirements and relevant fields
- process
Endpoint process creation telemetry with stable process identity, path, command line, parent, and user context.
TimestampDeviceIdDeviceNameFileNameFolderPathProcessIdProcessUniqueIdProcessCommandLineAccountNameAccountUpnSHA1SHA256InitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessIdInitiatingProcessUniqueIdInitiatingProcessAccountNameInitiatingProcessAccountUpn
KQL schema
Validate Defender for Endpoint coverage on macOS, table availability, field population, and retention before operational use.
SPL schema
Replace index/sourcetype placeholders and map device, process, file, and network fields to local telemetry.
Limitations
- AppleScript automation is common on managed Macs and must be interpreted with user/application context.
KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.
View query — Check temporary staging and archive creation
Q-04DrilldownCheck temporary staging and archive creation
What this checks
Search the candidate device for temporary sync staging and compression activity preceding the upload.
KQL
let target_device="<DEVICE_NAME>";
let archive_processes =
DeviceProcessEvents
| where Timestamp >= ago(7d)
| where DeviceName =~ target_device
| where FileName in~ ("zip","ditto","tar","gzip")
| where ProcessCommandLine has "/tmp/"
| project
Timestamp,
DeviceName,
Signal="archive-process",
FileName,
FolderPath,
ProcessCommandLine,
AccountName,
InitiatingProcessFileName,
InitiatingProcessCommandLine;
let staged_files =
DeviceFileEvents
| where Timestamp >= ago(7d)
| where DeviceName =~ target_device
| where FolderPath startswith "/tmp/sync"
or FolderPath startswith "/tmp/"
| where FileName endswith ".zip"
| project
Timestamp,
DeviceName,
Signal="staged-file",
FileName,
FolderPath,
ProcessCommandLine=InitiatingProcessCommandLine,
AccountName=InitiatingProcessAccountName,
InitiatingProcessFileName,
InitiatingProcessCommandLine;
union archive_processes, staged_files
| order by Timestamp ascSPL
(
index=<endpoint_process_index> sourcetype=<process_events_sourcetype> earliest=-7d
)
OR
(
index=<endpoint_file_index> sourcetype=<file_events_sourcetype> earliest=-7d
)
| eval
device=coalesce(device,DeviceName,host),
process_name=lower(coalesce(process_name,FileName,InitiatingProcessFileName)),
folder=coalesce(folder,FolderPath),
cmd=coalesce(cmd,ProcessCommandLine,InitiatingProcessCommandLine),
signal=case(
process_name IN ("zip","ditto","tar","gzip") AND like(cmd,"%/tmp/%"),"archive-process",
like(folder,"/tmp/sync%"),"staged-file",
like(folder,"/tmp/%") AND like(lower(FileName),"%.zip"),"staged-file",
true(),"other"
)
| where device="<DEVICE_NAME>" AND signal!="other"
| table _time device signal process_name folder FileName cmd AccountName InitiatingProcessFileName
| sort 0 _timeWhat to look for
Staging under temporary sync paths or temporary ZIP archive creation close to the upload window.
Technical details
Tested signal
Temporary collection directories followed by archive utility execution in the temporary filesystem.
Assumptions
- Process and file telemetry cover the pre-exfiltration window.
Data requirements and relevant fields
- process
Endpoint process creation telemetry with stable process identity, path, command line, parent, and user context.
TimestampDeviceIdDeviceNameFileNameFolderPathProcessIdProcessUniqueIdProcessCommandLineAccountNameAccountUpnSHA1SHA256InitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessIdInitiatingProcessUniqueIdInitiatingProcessAccountNameInitiatingProcessAccountUpn
- file
Endpoint file telemetry used to scope temporary staging, archive creation, and cleanup.
TimestampDeviceIdDeviceNameActionTypeFileNameFolderPathSHA1SHA256FileSizeInitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessAccountNameInitiatingProcessAccountUpnInitiatingProcessUniqueId
KQL schema
Validate Defender for Endpoint coverage on macOS, table availability, field population, and retention before operational use.
SPL schema
Replace index/sourcetype placeholders and map device, process, file, and network fields to local telemetry.
Limitations
- Backup, software packaging, and developer workflows can legitimately archive data under temporary paths.
KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local field mapping.
Legitimate resemblance
What the analyst should confirm
A developer or operations engineer uses curl to upload chunked artifacts to an internal or approved external API.
The destination, command template, automation owner, device role, authentication method, and historical behavior match an approved engineering workflow.A backup or data-transfer utility invokes curl with binary PUT semantics and temporary archives.
The parent process, destination, schedule, archive source, software owner, and change documentation match the approved backup or migration workflow.A security-research Mac reproduces MacSync behavior in an isolated lab.
The endpoint is a named research asset, the exercise window is documented, and no production credentials or data are involved.
Confirmed match
Action after a confirmed match
- Isolate the affected Mac when unauthorized collection or exfiltration is confirmed.
- Terminate malicious shell, curl, AppleScript, and related child processes.
- Preserve temporary staging, archive, process, network, and command-line evidence before cleanup.
- Reset or rotate credentials exposed from Keychain, browsers, SSH material, cloud credentials, or local files.
- Block confirmed malicious MacSync infrastructure while continuing behavior-based hunting for rotated domains.
- Search all managed Macs for the same retrieval, staging, chunked upload, and cleanup behavior.
- Review Terminal and ClickFix-resistant platform controls and user-awareness coverage.
Threat hunt
Could this be happening elsewhere?
Hunt for this behavior across the environment.Technical boundary
Telemetry and limitations
- Endpoint
Device inventory used to restrict analytics to current macOS endpoints.
Required fieldsTimestampDeviceIdDeviceNameOSPlatform
- Process
Endpoint process creation telemetry with stable process identity, path, command line, parent, and user context.
Required fieldsTimestampDeviceIdDeviceNameFileNameFolderPathProcessIdProcessUniqueIdProcessCommandLineAccountNameAccountUpnSHA1SHA256InitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessIdInitiatingProcessUniqueIdInitiatingProcessAccountNameInitiatingProcessAccountUpn
- Network
Endpoint network telemetry with destination URL, port, initiating process, user, and stable process identity.
Required fieldsTimestampDeviceIdDeviceNameRemoteUrlRemoteIPRemotePortProtocolLocalIPLocalPortInitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessAccountNameInitiatingProcessAccountUpnInitiatingProcessUniqueId
- File
Endpoint file telemetry used to scope temporary staging, archive creation, and cleanup.
Required fieldsTimestampDeviceIdDeviceNameActionTypeFileNameFolderPathSHA1SHA256FileSizeInitiatingProcessFileNameInitiatingProcessFolderPathInitiatingProcessCommandLineInitiatingProcessAccountNameInitiatingProcessAccountUpnInitiatingProcessUniqueId
Blind spots
- The actor can replace curl with another HTTP client while preserving the broader collection and staging behavior.
- A device with legitimate historical matching curl uploads can fail the first-seen baseline despite compromise.
- Stable process identity might not be populated consistently on every endpoint sensor version.
- Request parameters or URI paths can rotate, so the hunt should retain collection, staging, and cleanup pivots.
Behavior mapping
MITRE ATT&CK
T1041· Exfiltration Over C2 ChannelThe primary analytic targets curl-based data transfer to attacker-controlled infrastructure over the active command-and-control path.
T1020· Automated ExfiltrationThe observed upload behavior is automated through curl with binary transfer and repeated upload parameters.
T1030· Data Transfer Size LimitsChunk index and total chunk parameters reflect staged data being transferred in multiple pieces.
T1071.001· Web ProtocolsThe upload uses web protocols and recurring URI/request traits while infrastructure rotates.
Mappings describe the behavior examined by this analytic. They do not prove attribution, deployment, or technique-wide coverage.
Review boundary
Sources and limits
- External sources
- 4
Exact fields, retention, and operational thresholds remain environment-specific.