Finding and scope
In a selected source-aware Redis command cohort from 2026-10-24 through 2026-11-06 UTC, 49 qualifying sessions from 8 source IPs across 3 sensors contained 193 matching SAVE requests and 1189 command records. The finding is the ordered mechanism: the submitted commands used SET to place scheduled-downloader text in Redis, CONFIG SET dir and CONFIG SET dbfilename to choose a requested file destination, then SAVE within 10 minutes in the same connection.
The sequence matters because it attempts to turn Redis database persistence into placement of cron material. The filesystem destination and ordered relationship between content, configuration and SAVE distinguish the activity from an isolated administrative CONFIG command.
What varied across the Redis sequences
The selected cohort split across equal UTC windows as follows:
- 2026-10-24 through 2026-10-30: 19 qualifying sessions and 76 matching SAVE requests.
- 2026-10-31 through 2026-11-06: 30 qualifying sessions and 117 matching SAVE requests.
Captured cron destinations included /var/spool/cron/, /var/spool/cron/crontabs, /etc/cron.d/ and /etc/ with filename crontab. Submitted schedules used 2-, 3-, 4- and 5-minute intervals. The sequences also requested a backup filename before clearing keys and writing scheduled-command text, which helps explain why several SAVE requests can occur inside one session as the commands try different placements.
MITRE ATT&CK describes Cron as Scheduled Task/Job sub-technique T1053.003 and notes that cron is a time-based job scheduler for Unix-like operating systems used for scheduled execution and persistence. That context is relevant to the captured SET, CONFIG SET and SAVE sequence because the requested destinations are cron-relevant locations and the content relationship is part of the detected mechanism. Primary-source document
Local sequence hunt
The tested helper below expects command records from exactly one already-grouped connection. Each record uses ts, event_id and payload fields; payload contains command and list-valued args. It sorts by timestamp and event ID, detects scheduled downloader text, tracks directory and filename state, clears content state on FLUSHALL and returns SAVE event IDs within 600 seconds of matching SET content. Treat it as request-sequence hunting logic and inspect the full connection context before action.
def redis_persistence(commands):
"""Pass records from one connection; return matching SAVE event IDs."""
import re
from datetime import datetime
directory = filename = content_kind = None
content_at = None
matches = []
for row in sorted(commands, key=lambda r: (r['ts'], r['event_id'])):
p = row['payload']
verb = str(p.get('command', '')).lower()
args = p.get('args') or []
if not isinstance(args, list):
continue
timestamp = datetime.fromisoformat(row['ts'])
if verb == 'flushall':
content_kind = content_at = None
elif verb == 'set' and len(args) >= 2:
value = str(args[1]).lower()
if re.search(r'(?m)^\s*(?:[\d*/,-]+\s+){4}[\d*/,-]+\s+', value) and any(v in value for v in ('wget', 'curl', 'http://', 'https://')):
content_kind, content_at = 'cron', timestamp
elif verb == 'config' and len(args) == 3 and str(args[0]).lower() == 'set':
if str(args[1]).lower() == 'dir':
directory = str(args[2]).rstrip('/')
elif str(args[1]).lower() == 'dbfilename':
filename = str(args[2])
elif verb == 'save' and content_at and 0 <= (timestamp-content_at).total_seconds() <= 600:
cron_target = directory in ('/var/spool/cron', '/var/spool/cron/crontabs', '/etc/cron.d') or (directory == '/etc' and filename == 'crontab')
if filename and content_kind == 'cron' and cron_target:
matches.append(row['event_id'])
return matches
Triage and false-positive checks
Group telemetry by tenant, sensor, service, source and connection identity before applying the function. Useful retained fields are connection identity, request time, command verb, ordered arguments, event ID, destination directory, filename and Redis service identity. The comparison unit should be the session or connection, not a standalone SAVE count, because repeated SAVE requests can represent placement attempts inside one connection.
For triage, review the caller, the workload owner, the requested operation and the approved change record. A normal backup location fails this target predicate, while a recognized administration identity does not explain writing cron paths by itself. Where host telemetry is available, correlate the request time with unexpected cron entries and Redis service-account writes, then review permission to change runtime configuration and restrict untrusted Redis access.