Build a custom SQL detection rule in Agentic SOC using Simple Log Service (SLS) to identify and alert on logins from dormant accounts in real time.
Background and objectives
Target scenario: Detect logins from dormant accounts.
Method: Run scheduled SQL queries that compare recent logins against a historical baseline to surface users who appeared recently but were absent from historical records.
Core logic
The detection SQL consists of three parts:
-
Define recent activity: Query login events from the last 20 minutes.
-
Define a historical baseline: Query users who logged in to a host in the last 24 hours, excluding the most recent 20 minutes.
-
Compare the datasets: Join recent activity with the historical baseline. A user present in recent activity but absent from the baseline indicates an abnormal login.
Define recent activity
(
select
user_id,
src_ip,
username,
uuid,
start_time
from
log
where
cast(start_time as bigint) >= cast(to_unixtime (current_timestamp) as bigint) - 20 * 60
and cast(start_time as bigint) < cast(to_unixtime (current_timestamp) as bigint)
) a
-
Syntax analysis:
-
from log: Queries data from thelogtable. -
to_unixtime(current_timestamp): Returns the current Unix timestamp (seconds). -
cast(... as bigint): Converts the timestamp tobigintfor arithmetic operations and comparisons. -
where ...: Defines a 20-minute sliding window ending at the current time.-
>= ... - 20 * 60:start_timemust be at or after current time minus 20 minutes. -
< ...:start_timemust be before the current time.
-
-
-
Semantic analysis:
-
Purpose: Defines a result set named
afor "recent activity" — the primary objects of analysis. -
Result: A temporary result set of users active in the last 20 minutes, including
user_idandsrc_ip. -
Key point: The "recent" period is a 20-minute sliding window ending at the current time.
-
Define a historical baseline
(
select
user_id,
username,
uuid
from
log
where
schema='HOST_LOGIN_ACTIVITY' and
cast(start_time as bigint) >= cast(to_unixtime (current_timestamp) as bigint) - 24 * 3600
and cast(start_time as bigint) < cast(to_unixtime (current_timestamp) as bigint) - 20 * 60
) b
-
Syntax analysis:
-
schema='HOST_LOGIN_ACTIVITY': Filters to host login activity logs only, improving baseline accuracy. -
where ...: Defines a time range from 24 hours ago to 20 minutes ago.-
>= ... - 24 * 3600: Start time must be at or after current time minus 24 hours. -
< ... - 20 * 60: Start time must be before current time minus 20 minutes.
-
-
-
Semantic analysis:
-
Purpose: Builds a "historical baseline" set named
bas a reference for identifying abnormal activity. -
Result: A temporary result set of users who logged in within the last 24 hours, excluding the most recent 20 minutes.
-
Key point: The window spans from 24 hours ago to 20 minutes ago, ensuring no overlap with recent activity data and preventing self-comparison errors.
-
Use LEFT JOIN for difference comparison
... a
left join
... b on a.username = b.username and a.uuid = b.uuid and a.user_id = b.user_id
-
Syntax analysis:
-
LEFT JOIN: Uses the left table (a, recent users) as the base and matches each record against the right table (b, historical users). -
on a.username = b.username and a.uuid = b.uuid and a.user_id=b.user_id: Joins onusername,uuid, anduser_idtogether to uniquely identify a user entity.
-
-
Semantic analysis:
-
Purpose: Correlates "recent activity" (
a) with the "historical baseline" (b) to find historical login records for each recent user. -
Result: A combined result set with all records from table
aand matched records from tableb. -
Key point: A
LEFT JOINis asymmetric: if a user from tableahas no match in tableb, all columns from tablebreturnNULL. This is the core mechanism for identifying new activity.
-
Filter the final result
where
(
b.username is null
or b.username = ''
)
-
Syntax analysis:
-
b.username is null: Leverages theLEFT JOINbehavior. When a recent user (tablea) has no match in the historical baseline (tableb),b.usernameisNULL. -
or b.username = '': A defensive condition for cases where a log field is an empty string''instead ofNULL.
-
-
Semantic analysis:
-
Purpose: Filters the joined results to isolate activities that appeared recently but have no historical record.
-
Result: Only unmatched records from the
LEFT JOINremain — the abnormal events you are looking for. -
Key point:
b.username is nullis the core decision point of the detection logic. It uses theNULLvalues from the join to separate "present recently, absent historically" records from the dataset.
-
SELECT DISTINCT: Output alerts
select distinct
a.user_id,
a.src_ip,
a.username,
a.uuid
-
Syntax analysis:
-
SELECT DISTINCT: Selects the specified fields and removes duplicates, ensuring one alert per abnormal event.
-
-
Semantic analysis:
-
Purpose: Formats and outputs a final list of abnormal events for alerting.
-
Result: A deduplicated alert list. Each record contains the core tracing information: user ID and source IP.
-
Key point:
DISTINCTdeduplicates results so the same user's abnormal behavior within the 20-minute detection window triggers only one alert.
-
Complete solution
Final SQL query
*|set session mode=scan;
select distinct
a.user_id,
a.src_ip,
a.username,
a.uuid
from
(
select
user_id,
src_ip,
username,
uuid,
start_time
from
log
where
cast(start_time as bigint) >= cast(to_unixtime (current_timestamp) as bigint) -20 * 60
and cast(start_time as bigint) < cast(to_unixtime (current_timestamp) as bigint)
) a
left join (
select
user_id,
username,
uuid
from
log
where
schema='HOST_LOGIN_ACTIVITY' and
cast(start_time as bigint) >= cast(to_unixtime (current_timestamp) as bigint) - 24 * 3600
and cast(start_time as bigint) < cast(to_unixtime (current_timestamp) as bigint) -20 * 60
) b on a.username = b.username
and a.uuid = b.uuid
and a.user_id=b.user_id
where
(
b.username is null
or b.username = ''
)
Configure the rule in Agentic SOC
-
Purchase and enable Agentic SOC
Refer to Purchase and enable for purchasing options. To access all custom threat detection features, we recommend purchasing both Log Ingestion Traffic and Log Storage Capacity.
-
Log on to the console and navigate to the Create Custom Rule page
-
Log on to the .
-
In the left-side navigation pane, choose . In the upper-left corner of the Console, select the Region where your assets are located: Chinese Mainland or Outside Chinese Mainland.
-
On the Custom tab, click Create Custom Rule.
-
-
Configure the alert generation rule
-
In the Create Custom Rule panel, on the Basic Information tab, enter a rule name and description, then click Next to go to the Alert Settings page.
-
Configure the SQL detection rule with the following settings:
Parameter
Value
Rule Body
SQL
Log Scope
Logon Logs - Host Logon Success Log.
SQL Query
Copy the code from the Final SQL query section.
Scheduling Interval
Fixed Interval - 20 minutes.
SQL Time Window
24 hours.
Start Time
When the rule is enabled.
Generation Structure
Other Alert Logs.
Alarm Metric
Abnormal Logon.
Alert Severity
Medium.
ATT&CK Tactic
Persistence - T1136 Create Account.
Entity Mapping
-
Network Address
-
is_malware: 1 -
ip:$src_ip -
net_connect_dir: in
-
-
Host
-
is_asset: 1 -
uuid:$uuid
-
-
-
-
Configure the Incident generation rule
-
On the Alert Settings page, after you complete the configuration, click Next to go to the Incident Generation Settings page.
-
Configure the time rule with the following settings:
-
Generate Event: Yes.
-
Incident Generation Method: Aggregate by type.
-
Aggregation Window: 20 minutes.
-
-
-
Validate the rule
New rules are Disabled by default. Test them first to evaluate effectiveness. During testing, the system automatically calibrates alert fields. Use the calibration suggestions to optimize the rule's SQL or playbook before enabling.
-
Change the Enabling Status of the target rule to Testing.
-
In the actions column for the target rule, click View Alert Test Result.
-
View the alert trend chart and the list of generated alerts on the test result details page.
-
In the Actions column for an alert, click Details to view its calibration results.
-
-
Enable the custom rule
After the rule passes testing, set its Enabling Status to
Enabled.ImportantTest the rule before enabling it.
Risk assessment
-
False positive: Users logging in after a long vacation or business trip may be flagged. Reduce false positives by extending the
dormant_hoursthreshold (for example, to 72 hours) or configuring a user whitelist. -
False negative: Log collection interruptions or non-standard field formats may cause miscalculated time intervals and missed detections. Ensure log data integrity and consistency.
In the Security Center console, choose Rule Management in the left-side navigation pane and click the Custom tab. The page displays rule statistics (enabled rules, testing rules, and rule templates). Click the target rule (such as Abnormal IP Login) to open a details panel showing the alert trend chart and test results, including alert count, calibration results, and occurrence times.