# T1021 - Remote Services

## SOC Recommendation
Review any remote-service logon that is unusual for the source host, destination host, account, or time — particularly an account authenticating to a system it does not normally access, or a workstation connecting directly to another workstation rather than through expected jump hosts.

## D3FEND Mappings
| D3FEND Technique | Relationship | Practical SOC Action |
|---|---|---|
| Remote Terminal Session Detection | Detect | Review remote logon events (RDP/SSH/WinRM/SMB) for unusual source-destination-account combinations. |
| Connection Attempt Analysis | Detect | Detect unusual internal RDP/SSH/SMB connection patterns indicating lateral movement. |
| Session Termination | Evict | Terminate the remote session and revoke the account's credentials/sessions if malicious. |
| Network Traffic Filtering | Isolate | Restrict remote-service access via firewall/segmentation to expected jump hosts only. |

## Investigation Steps
1. Check device inventory for both source and destination: age, OS, role, owner
2. If the source device was recently onboarded, this may be a baseline gap (not an attack)

## Evidence to Collect
- Source and destination host
- Account used
- Remote service/protocol (RDP/SSH/SMB/WinRM/VNC)
- Logon timestamp and type
- Activity performed on the destination after logon

## Response Actions
| Action | Risk | Automation Safe | Approval Required |
|---|---|---|---|
| Isolate both source and destination devices | Medium | No | Yes |
| Reset the abused account's password (and all other accounts | Medium | No | Yes |
| Purge Kerberos tickets on both devices: klist purge via Li | Medium | No | Yes |
| Search for further lateral movement hops from the destinatio | Low | No | Yes |
| Enable Credential Guard on all affected devices post-rebuild | Medium | No | Yes |
| Disable NTLMv1 organisation-wide via Group Policy | Medium | No | Yes |

## KQL
```kql
let lookback = 1d;
let hostThreshold = 6;
DeviceLogonEvents
| where TimeGenerated >= ago(lookback)
| where ActionType == "LogonSuccess"
| where LogonType in~ ("RemoteInteractive", "Network")
| where isnotempty(AccountUpn) and isnotempty(DeviceName)
| summarize
    StartTime = min(TimeGenerated),
    EndTime = max(TimeGenerated),
    DistinctHosts = dcount(DeviceName),
    Hosts = make_set(DeviceName, 100),
    SourceIPs = make_set(RemoteIP, 50)
    by AccountUpn
| where DistinctHosts >= hostThreshold
| extend AccountName = tostring(split(AccountUpn, "@")[0]),
         UPNSuffix = tostring(split(AccountUpn, "@")[1]),
         HostCustomEntity = tostring(Hosts[0])
| project StartTime, EndTime, AccountUpn, AccountName, UPNSuffix,
          DistinctHosts, Hosts, SourceIPs, HostCustomEntity
| extend timestamp = StartTime, AccountCustomEntity = AccountUpn
| order by DistinctHosts desc
```
```kql
let SuspiciousServiceNames = dynamic([
    "PSEXESVC",
    "PAExec",
    "RemComSvc",
    "WinExeSvc"
]);
DeviceProcessEvents
| where TimeGenerated >= ago(5m)
| where FileName in~ ("services.exe", "sc.exe")
| where ProcessCommandLine has_any (SuspiciousServiceNames) or InitiatingProcessCommandLine has_any (SuspiciousServiceNames)
| project
    TimeGenerated,
    DeviceName,
    DeviceId,
    AccountName,
    FileName,
    ProcessCommandLine,
    InitiatingProcessFileName,
    InitiatingProcessCommandLine,
    InitiatingProcessAccountName,
    InitiatingProcessRemoteSessionDeviceName
| extend timestamp = TimeGenerated,
         HostCustomEntity = DeviceName,
         AccountCustomEntity = AccountName
| order by TimeGenerated desc
```
```kql
let lookback = 1d;
let hostThreshold = 5;
DeviceNetworkEvents
| where TimeGenerated >= ago(lookback)
| where ActionType == "ConnectionSuccess"
| where RemotePort == 445
| where isnotempty(InitiatingProcessAccountName) and isnotempty(RemoteIP)
| summarize
    StartTime = min(TimeGenerated),
    EndTime = max(TimeGenerated),
    DistinctHosts = dcount(RemoteIP),
    Hosts = make_set(RemoteIP, 100),
    SourceDevices = make_set(DeviceName, 50)
    by InitiatingProcessAccountName
| where DistinctHosts >= hostThreshold
| extend HostCustomEntity = tostring(SourceDevices[0])
| project StartTime, EndTime, InitiatingProcessAccountName, DistinctHosts, Hosts, SourceDevices, HostCustomEntity
| extend timestamp = StartTime, AccountCustomEntity = InitiatingProcessAccountName
| order by DistinctHosts desc
```

## Escalation Criteria
- Privileged account used for the remote logon.
- Destination host holds sensitive data or is a domain controller.
- Logon pattern matches known lateral-movement tooling (PsExec-style admin share usage).
- Activity followed recent credential access or discovery on the source host.

## False Positive Considerations
- New workstation — baseline gap — Verify device age in asset inventory; if < 30 days old, this is expected — add to allowlist
- Recently changed hostname — Same device, different name — add old/new pair to baseline
- IT admin NTLM to new server — Verify with IT admin; add approved management server pairs to exclusions watchlist

Generated by SOC Response Atlas by Basyrix.
