Repository: google/turbinia

Issue:
[Bug]: Jenkins Analysis Task unable to extract password
### What steps will reproduce the bug?

1. K8s Cluster running Jenkins Helm chart: https://github.com/jenkinsci/helm-charts
2. Running dftw recipe dftimewolf gcp_turbinia_disk_copy_ts
3. Disk containing Jenkins pod gets processed through Turbinia pipeline.
4. JenkinsAnalysisTask does not pick up on the Jenkins configuration.



### What is the expected behavior?

JenkinsAnalysisTask identifies a weak Jenkins password

### What do you see instead?

JenkinsAnalysisTask returns:

```
No Jenkins instance found
Jenkins version: Unknown
```

### Additional information

Checking inside the Jenkins pod
```
jenkins@jenkins-0:/$ find . -name config.xml
./var/jenkins_home/users/jenkins_10477391727943582798/config.xml
./var/jenkins_home/config.xml
```

Grepping for field in first file
```
jenkins@jenkins-0:/$ cat ./var/jenkins_home/config.xml  | grep password
No results...
```

Grepping for field in second file
```
jenkins@jenkins-0:/$ cat ./var/jenkins_home/users/jenkins_10477391727943582798/config.xml | grep password
              <password>{PW_STRING}</password>
      <passwordHash>#jbcrypt:$2a$10$/EH.PW_STRING</passwordHash>

jenkins@jenkins-0:/$ cat ./var/jenkins_home/users/jenkins_10477391727943582798/config.xml | grep fullName
  <fullName>Jenkins Admin</fullName>
```

I would have expected the JenkinsAnalysisTask return a weak password for the second field.


Discussion:
Verifying the regex used to get the artifact:
- it matches the filepath of the config.xml
-- ./var/jenkins_home/users/jenkins_10477391727943582798/config.xml
- regex: https://github.com/google/turbinia/blob/master/turbinia/workers/analysis/jenkins.py#L75

Currently k8s (GKE in this case) writes any secret that is needed in a file to a small disk (8GB). Please verify that you copied all disks to make sure you get the config.xml
It is present in the pod, but it is stored on a different disk that is mounted on the node (at least on GKE).
Updating per forgetting to add `--all_disks` flag to dftimewolf recipe.

Now it detects the Jenkins version but is unable to extract the password even though it was set as `jenkins:jenkins`

```
status": "Jenkins version 10 found with 1 credentials, but no issues detected",
```

Resolving patch:
diff --git a/turbinia/lib/utils.py b/turbinia/lib/utils.py
--- a/turbinia/lib/utils.py
+++ b/turbinia/lib/utils.py
@@ -171,6 +171,8 @@ def bruteforce_password_hashes(
 
   pot_file = os.path.join((tmp_dir or tempfile.gettempdir()), 'hashcat.pot')
   password_list_file_path = os.path.expanduser('~/password.lst')
+  password_rules_file_path = os.path.expanduser(
+      '~/turbinia-password-cracking.rules')
 
   # Fallback
   if not os.path.isfile(password_list_file_path):
@@ -180,6 +182,12 @@ def bruteforce_password_hashes(
   if not os.path.isfile(password_list_file_path):
     raise TurbiniaException('No password list available')
 
+  # Does rules file exist? If not make a temp one
+  if not os.path.isfile(password_rules_file_path):
+    with tempfile.NamedTemporaryFile(delete=False, mode='w+') as rf:
+      password_rules_file_path = rf.name
+      rf.write('\n'.join([':', 'd']))
+
   if '$y$' in ''.join(password_hashes):
     cmd = [
         'john', '--format=crypt', f'--wordlist={password_list_file_path}',
@@ -187,14 +195,13 @@ def bruteforce_password_hashes(
     ]
     pot_file = os.path.expanduser('~/.john/john.pot')
   else:
-    cmd = ['hashcat', '--force', '-a', '1']
+    # Ignore warnings & plain word list attack (with rules)
+    cmd = ['hashcat', '--force', '-a', '0']
     if extra_args:
       cmd = cmd + extra_args.split(' ')
     cmd = cmd + [f'--potfile-path={pot_file}']
-    cmd = cmd + [
-        password_hashes_file_path, password_list_file_path,
-        password_list_file_path
-    ]
+    cmd = cmd + [password_hashes_file_path, password_list_file_path]
+    cmd = cmd + ['-r', password_rules_file_path]
 
   with open(os.devnull, 'w') as devnull:
     try:
diff --git a/turbinia/workers/analysis/jenkins.py b/turbinia/workers/analysis/jenkins.py
--- a/turbinia/workers/analysis/jenkins.py
+++ b/turbinia/workers/analysis/jenkins.py
@@ -37,7 +37,7 @@ class JenkinsAnalysisTask(TurbiniaTask):
   TASK_CONFIG = {
       # This is the length of time in seconds that the collected passwords will
       # be bruteforced.
-      'bruteforce_timeout': 300
+      'bruteforce_timeout': 600
   }
 
   def run(self, evidence, result):
@@ -72,7 +72,7 @@ def run(self, evidence, result):
 
     jenkins_artifacts = []
     jenkins_re = re.compile(
-        r'^.*jenkins[^\/]*(\/home)?(\/users\/[^\/]+)*\/config\.xml$')
+        r'^.*?jenkins[^\/]*(\/home)?(\/users)?(\/.*?)\/config\.xml$')
     for collected_artifact in collected_artifacts:
       if re.match(jenkins_re, collected_artifact):
         jenkins_artifacts.append(collected_artifact)


Test patch:
diff --git a/turbinia/workers/analysis/postgresql_acct_test.py b/turbinia/workers/analysis/postgresql_acct_test.py
--- a/turbinia/workers/analysis/postgresql_acct_test.py
+++ b/turbinia/workers/analysis/postgresql_acct_test.py
@@ -15,6 +15,7 @@
 """Tests for the PostgreSQL account analysis task."""
 
 import os
+import mock
 import unittest
 
 from turbinia import config
@@ -35,7 +36,7 @@ class PostgresAcctAnalysisTaskTest(TestTurbiniaTaskBase):
 
   POSTGRES_REPORT = """#### **PostgreSQL analysis found 1 weak password(s)**
 * **1 weak password(s) found:**
-    * User 'postgres' with password 'password'"""
+    * User 'postgres' with password 'postgres'"""
 
   def setUp(self):
     super(PostgresAcctAnalysisTaskTest, self).setUp()
@@ -72,22 +73,34 @@ def test_extract_scram_creds(self):
     _, hashes = task._extract_creds(['/scram_database'], self.evidence)
     self.assertDictEqual(hashes, self.EXPECTED_SCRAM_CREDENTIALS)
 
-  def test_analyse_md5_postgres_creds(self):
+  @mock.patch(
+      'turbinia.workers.analysis.postgresql_acct.bruteforce_password_hashes')
+  def test_analyse_md5_postgres_creds(self, bruteforce_mock):
     """Tests the _analyse_postgres_creds method."""
     config.LoadConfig()
     task = postgresql_acct.PostgresAccountAnalysisTask()
 
+    bruteforce_mock.side_effect = [
+        [(list(self.EXPECTED_MD5_CREDENTIALS.keys())[0], 'postgres')], []
+    ]
+
     (report, priority, summary) = task._analyse_postgres_creds(
         self.EXPECTED_MD5_CREDENTIALS, {})
     self.assertEqual(report, self.POSTGRES_REPORT)
     self.assertEqual(priority, 10)
     self.assertEqual(summary, 'PostgreSQL analysis found 1 weak password(s)')
 
-  def test_analyse_scram_postgres_creds(self):
+  @mock.patch(
+      'turbinia.workers.analysis.postgresql_acct.bruteforce_password_hashes')
+  def test_analyse_scram_postgres_creds(self, bruteforce_mock):
     """Tests the _analyse_postgres_creds method."""
     config.LoadConfig()
     task = postgresql_acct.PostgresAccountAnalysisTask()
 
+    bruteforce_mock.side_effect = [
+        [], [(list(self.EXPECTED_SCRAM_CREDENTIALS.keys())[0], 'postgres')]
+    ]
+
     (report, priority, summary) = task._analyse_postgres_creds(
         {}, self.EXPECTED_SCRAM_CREDENTIALS)
     self.assertEqual(report, self.POSTGRES_REPORT)