Repository: google/turbinia

Issue:
Linux Account Analysis Task does not support yescrypt hash (Default for Debian 11+)
The default password hashing for debian 11+  is now `yescrypt` (https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html#pam-default-password). The Linux Account Analysis task use hashcat to try and crack the hash, but hashcat does not yet support `yescrypt`.  

John the ripper however is able to crack the `yescrypt` hash with the `crypt` format. The following argument should be supplied  `--format=crypt`


Discussion:

Resolving patch:
diff --git a/turbinia/config/turbinia_config_tmpl.py b/turbinia/config/turbinia_config_tmpl.py
--- a/turbinia/config/turbinia_config_tmpl.py
+++ b/turbinia/config/turbinia_config_tmpl.py
@@ -193,7 +193,7 @@
     'timeout': 1200
 }, {
     'job': 'LinuxAccountAnalysisJob',
-    'programs': ['hashcat'],
+    'programs': ['hashcat', 'john'],
     'docker_image': None,
     'timeout': 1200
 }, {
diff --git a/turbinia/lib/utils.py b/turbinia/lib/utils.py
--- a/turbinia/lib/utils.py
+++ b/turbinia/lib/utils.py
@@ -185,14 +185,22 @@ def bruteforce_password_hashes(
   if not os.path.isfile(password_list_file_path):
     raise TurbiniaException('No password list available')
 
-  cmd = ['hashcat', '--force', '-a', '1']
-  if extra_args:
-    cmd = cmd + extra_args.split(' ')
-  cmd = cmd + ['--potfile-path={}'.format(pot_file)]
-  cmd = cmd + [
-      password_hashes_file_path, password_list_file_path,
-      password_list_file_path
-  ]
+  if '$y$' in ''.join(password_hashes):
+    cmd = [
+        'john', '--format=crypt',
+        '--wordlist={}'.format(password_list_file_path),
+        password_hashes_file_path
+    ]
+    pot_file = os.path.expanduser('~/.john/john.pot')
+  else:
+    cmd = ['hashcat', '--force', '-a', '1']
+    if extra_args:
+      cmd = cmd + extra_args.split(' ')
+    cmd = cmd + ['--potfile-path={}'.format(pot_file)]
+    cmd = cmd + [
+        password_hashes_file_path, password_list_file_path,
+        password_list_file_path
+    ]
 
   with open(os.devnull, 'w') as devnull:
     try:


Test patch:
diff --git a/test_data/deb11_shadow b/test_data/deb11_shadow
new file mode 100644
--- /dev/null
+++ b/test_data/deb11_shadow
@@ -0,0 +1 @@
+root:$y$j9T$Ju9QNI0mUsapuQPOXH4Ie0$o7RT.ZY25GXZkBd2EguKLGXNeSVmIN6KWrZkWl5PoL9:19239:0:99999:7:::
diff --git a/turbinia/workers/analysis/linux_acct_test.py b/turbinia/workers/analysis/linux_acct_test.py
--- a/turbinia/workers/analysis/linux_acct_test.py
+++ b/turbinia/workers/analysis/linux_acct_test.py
@@ -25,12 +25,17 @@ class LinuxAccountAnalysisTaskTest(unittest.TestCase):
   """Tests for LinuxAccountAnalysisTask Task."""
 
   SHADOW_FILE = None
+  YESCRYPT_SHADOW_FILE = None
   EXPECTED_CREDENTIALS = {
       '*':
           'root',
       '$6$NS6w5Q6yjrlZiw7s$5jeyNS.bsw2p4nlbbMRI5H8oZnSbbwKs0Lsw94xCouqn/y/yQpKNA4vdPSr/wdA0isyUmq3BD..ZcirwOVNPF/':
           'testuser'
   }
+  YESCRYPT_EXPECTED_CREDENTIALS = {
+      '$y$j9T$Ju9QNI0mUsapuQPOXH4Ie0$o7RT.ZY25GXZkBd2EguKLGXNeSVmIN6KWrZkWl5PoL9':
+          'root'
+  }
   SHADOW_REPORT = """#### **Shadow file analysis found 1 weak password(s)**
 * **1 weak password(s) found:**
     * User 'testuser' with password 'test'"""
@@ -41,6 +46,10 @@ def setUp(self):
     test_data = os.path.join(filedir, '..', '..', '..', 'test_data', 'shadow')
     with open(test_data, 'r') as data:
       self.SHADOW_FILE = data.readlines()
+    test_data = os.path.join(
+        filedir, '..', '..', '..', 'test_data', 'deb11_shadow')
+    with open(test_data, 'r') as data:
+      self.YESCRYPT_SHADOW_FILE = data.readlines()
 
   def test_extract_linux_credentials(self):
     """Tests the extract_linux_credentials method."""
@@ -62,6 +71,17 @@ def test_analyse_shadow_file(self):
     self.assertEqual(priority, 10)
     self.assertEqual(summary, 'Shadow file analysis found 1 weak password(s)')
 
+  def test_analyse_yescrypt_shadow(self):
+    """Tests the analyse_shadow_file method with a yescrypt credential."""
+    config.LoadConfig()
+    task = linux_acct.LinuxAccountAnalysisTask()
+
+    (report, priority, summary) = task.analyse_shadow_file(
+        self.YESCRYPT_SHADOW_FILE, self.YESCRYPT_EXPECTED_CREDENTIALS)
+    self.assertRegexpMatches(report, "User 'root' with password 'password'")
+    self.assertEqual(priority, 10)
+    self.assertEqual(summary, 'Shadow file analysis found 1 weak password(s)')
+
 
 if __name__ == '__main__':
   unittest.main()