<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: tktkkame</title>
    <description>The latest articles on DEV Community by tktkkame (@tokotokokame).</description>
    <link>https://dev.to/tokotokokame</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4187633%2F68d918a7-3a28-406f-b92f-14757137f157.jpg</url>
      <title>DEV Community: tktkkame</title>
      <link>https://dev.to/tokotokokame</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tokotokokame"/>
    <language>en</language>
    <item>
      <title>A file backdated to 2018 with touch, caught by one line</title>
      <dc:creator>tktkkame</dc:creator>
      <pubDate>Wed, 21 Oct 2026 01:46:16 +0000</pubDate>
      <link>https://dev.to/tokotokokame/a-file-backdated-to-2018-with-touch-caught-by-one-line-31na</link>
      <guid>https://dev.to/tokotokokame/a-file-backdated-to-2018-with-touch-caught-by-one-line-31na</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The scenario is &lt;strong&gt;fictional&lt;/strong&gt; (RFC 5737 IPs). Only run these on systems you own.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"This file was last modified in 2018, so it's old, ignore it." Have you ever reasoned like that? &lt;strong&gt;mtime is writable by anyone with &lt;code&gt;touch&lt;/code&gt;.&lt;/strong&gt; What attackers forget is the clock they can't write.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The three timestamps on a Linux file (mtime, ctime, birth)&lt;/li&gt;
&lt;li&gt;Which one &lt;code&gt;touch -d&lt;/code&gt; can forge, and which it can't&lt;/li&gt;
&lt;li&gt;A one-line detector, tested in a sandbox&lt;/li&gt;
&lt;li&gt;How to separate false positives (package updates, archive extraction)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The story at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;02:23&lt;/td&gt;
&lt;td&gt;A file is dropped on &lt;code&gt;web03&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02:23&lt;/td&gt;
&lt;td&gt;Its mtime is rewritten to 2018-04-02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02:24&lt;/td&gt;
&lt;td&gt;It's executed (&lt;code&gt;apache2 → sh → .update&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Investigation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;stat&lt;/code&gt; shows contradictory timestamps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Three clocks
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Modify: 2018-04-02 09:00:00   &amp;lt;- mtime (content), forgeable
Change: 2026-01-20 02:23:41   &amp;lt;- ctime (metadata), set by the kernel
 Birth: 2026-01-20 02:23:41   &amp;lt;- creation time (filesystem dependent)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rewriting mtime with &lt;code&gt;touch -d&lt;/code&gt; &lt;strong&gt;updates ctime to "now"&lt;/strong&gt;. The forger stamps their own forgery.&lt;/p&gt;

&lt;h2&gt;
  
  
  One-line detector
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find /dev/shm /tmp /var/tmp &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-printf&lt;/span&gt; &lt;span class="s1"&gt;'%C@ %T@ %p\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'$1-$2&amp;gt;86400*30 {print $3}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Flags files whose ctime is 30+ days newer than mtime. In my sandbox test, the forged file was flagged and a normal file was not (5/5 checks passed).&lt;/p&gt;

&lt;p&gt;For ext4, &lt;code&gt;debugfs -R "stat &amp;lt;inode&amp;gt;" /dev/sda1 | grep crtime&lt;/code&gt; shows creation time directly (from the book; not covered by the automated test).&lt;/p&gt;

&lt;h2&gt;
  
  
  False positives
&lt;/h2&gt;

&lt;p&gt;Package updates, tar/zip extraction (preserves mtime, so ctime = extraction time) and restores all trigger it. &lt;strong&gt;Never conclude from this alone.&lt;/strong&gt; Correlate with execution records and location (&lt;code&gt;/dev/shm&lt;/code&gt;, &lt;code&gt;/tmp&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Fact vs. inference
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fact: mtime 2018-04-02, ctime 2026-01-20 02:23.&lt;/li&gt;
&lt;li&gt;Fact: executed at 02:24 (auditd).&lt;/li&gt;
&lt;li&gt;Inference: mtime was forged (basis: ctime/birth contradiction).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;touch&lt;/code&gt; forges mtime/atime, not ctime or birth.&lt;/li&gt;
&lt;li&gt;ctime &amp;gt; mtime by a wide margin is the marker. Correlate before concluding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next: persistence, one key and one cron line.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from chapters 28 and 30 of my Japanese book on server defense for SOC, CSIRT and forensics.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>forensics</category>
      <category>incidentresponse</category>
    </item>
    <item>
      <title>One SSH key, one cron line: a 5-minute persistence audit</title>
      <dc:creator>tktkkame</dc:creator>
      <pubDate>Wed, 21 Oct 2026 01:46:15 +0000</pubDate>
      <link>https://dev.to/tokotokokame/one-ssh-key-one-cron-line-a-5-minute-persistence-audit-i99</link>
      <guid>https://dev.to/tokotokokame/one-ssh-key-one-cron-line-a-5-minute-persistence-audit-i99</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;The scenario is &lt;strong&gt;fictional&lt;/strong&gt; (RFC 5737 IPs). Detection only; no attack steps.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You found the intrusion, killed the process, changed the password. Done? &lt;strong&gt;Attackers plan to be locked out.&lt;/strong&gt; Within minutes of getting in they build a second and third way back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The two classic persistence spots: SSH keys and cron&lt;/li&gt;
&lt;li&gt;Baseline diffs that find them in minutes&lt;/li&gt;
&lt;li&gt;How to tell admin changes from attacker changes&lt;/li&gt;
&lt;li&gt;Why you preserve before you clean&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The story at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;02:20&lt;/td&gt;
&lt;td&gt;Root login succeeds after brute force&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02:26&lt;/td&gt;
&lt;td&gt;An unknown key appears in root's &lt;code&gt;authorized_keys&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02:27&lt;/td&gt;
&lt;td&gt;A cron entry runs every minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Later&lt;/td&gt;
&lt;td&gt;Password reset and process kill don't help&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Keys: diff against a saved baseline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find / &lt;span class="nt"&gt;-name&lt;/span&gt; authorized_keys &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /var/lib/keys_base.txt   &lt;span class="c"&gt;# normal times&lt;/span&gt;
find / &lt;span class="nt"&gt;-name&lt;/span&gt; authorized_keys &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /var/lib/keys_now.txt    &lt;span class="c"&gt;# investigation&lt;/span&gt;
diff /var/lib/keys_base.txt /var/lib/keys_now.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my sandbox test only root's file showed up in the diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cron: new files and fetch-and-execute
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find /etc/cron.d /var/spool/cron &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;sha256sum&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt; | &lt;span class="nb"&gt;sort
grep&lt;/span&gt; &lt;span class="nt"&gt;-REl&lt;/span&gt; &lt;span class="s1"&gt;'curl[^|]*\|[[:space:]]*(ba)?sh'&lt;/span&gt; /etc/cron.d /etc/crontab /var/spool/cron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The grep flagged the fake malicious cron and ignored a legit backup job. Legit tools sometimes use &lt;code&gt;curl | bash&lt;/code&gt;, so check whether the destination is yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Normal vs. attack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Normal&lt;/th&gt;
&lt;th&gt;Attack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Changed by&lt;/td&gt;
&lt;td&gt;config management (ansible), uid 0&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;www-data&lt;/code&gt; (uid 33) with euid 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;change ticket exists&lt;/td&gt;
&lt;td&gt;from the web server process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When&lt;/td&gt;
&lt;td&gt;maintenance window&lt;/td&gt;
&lt;td&gt;minutes after intrusion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Catch it live: auditd (from the book, not sandbox-tested)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-w /root/.ssh/authorized_keys -p wa -k ssh_key_change
-w /etc/crontab -p wa -k cron_persist
-w /etc/cron.d/ -p wa -k cron_persist
-w /var/spool/cron/ -p wa -k cron_persist
-w /etc/systemd/system/ -p wa -k systemd_persist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Preserve first, clean later
&lt;/h2&gt;

&lt;p&gt;Copy files, hash, record &lt;code&gt;stat&lt;/code&gt; times and who changed them. Only then remove the key line and the cron file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;Assume redundancy: keys, cron, systemd, profile.d, other users' &lt;code&gt;~/.ssh&lt;/code&gt;. Save the baseline &lt;em&gt;before&lt;/em&gt; you need it.&lt;/p&gt;

&lt;p&gt;Next: SSH that skipped the bastion.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from chapters 26 and 9 of my Japanese book on server defense.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>linux</category>
      <category>ssh</category>
      <category>incidentresponse</category>
    </item>
  </channel>
</rss>
