Giter VIP home page Giter VIP logo

nuvious / pam-duress Goto Github PK

View Code? Open in Web Editor NEW
1.3K 1.3K 37.0 95 KB

A Pluggable Authentication Module (PAM) which allows the establishment of alternate passwords that can be used to perform actions to clear sensitive data, notify IT/Security staff, close off sensitive network connections, etc if a user is coerced into giving a threat actor a password.

License: GNU Lesser General Public License v3.0

Makefile 10.19% C 89.81%

pam-duress's People

Contributors

5unr153 avatar camjn avatar cormacrelf avatar juergenhoetzel avatar nathanvanbakel avatar nuvious avatar pganguli avatar zakuarbor avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pam-duress's Issues

Script not running on arch linux

I have set up pam_duress, but when a duress password is used it doesn't seem to run the script and just logs in without doing anything.

blecc@toweringboi ~$ cat ~/.duress/hello.sh 
#!/bin/sh
echo "Hello World"

blecc@toweringboi ~$ duress_sign ~/.duress/hello.sh 
Password:  # 1234
Confirm:  # 1234
Reading .duress/hello.sh, 30...
Done
818034A48A634AEACAB6753A80E2160D42EE25C44791A083D5F8727E2B3D7A99
blecc@toweringboi ~$ chmod 400 ~/.duress/hello.sh.sha256 
blecc@toweringboi ~$ ls -l ~/.duress/
total 8
-r-x------ 1 blecc blecc 30 Sep 30 16:48 hello.sh
-r-------- 1 blecc blecc 32 Sep 30 16:51 hello.sh.sha256
blecc@toweringboi ~$ ssh blecc@localhost
blecc@localhost's password:  # 1234
< no hello world >
Last login: Fri Sep 30 16:46:36 2022 from ::1 
blecc@toweringboi ~$ 

Using an incorrect (non-duress) password still fails to log in at all, but no input causes the script to actually run. I have tried putting the script in /etc/duress.d instead of ~/.duress and using a non-outputting check (eg touching a file in /tmp/) with the same result.

Avoid casting malloc to char* explicitly

General comment on this codebase as a whole as requested.

In many instances this code uses the construct

char * foo = (char *) malloc(x); 

In this case the cast is not required as void* is implicitly promotable, and casting explicitly can create some subtle bugs if done wrongly. Instead the code should probably remove the cast such as:

char * foo = malloc(x); 

I would like to translate this for macOS

Hey :-)

Does anyone, especially the author have tried or has some opinion if this will work for macOS?, I have to study the differences when it comes to how PAM operates in each of this systems tho.

Thanks!

not work on Linux Mint 20.2

When trying to enter a "password under duress", we return to the password entry window. If you enter a regular password, authentication is normal.

In the same time:

sudo pam_test $ USER
Credentials accepted.
Password:
Account is valid.
Authenticated

Configuration /etc/pam.d/common-auth

auth    [success=2 default=ignore]      pam_unix.so nullok
auth    [success=1 default=ignore]      pam_duress.so nullok
auth    requisite                       pam_deny.so
auth    required                        pam_permit.so
auth    optional        pam_ecryptfs.so unwrap
auth    optional                        pam_cap.so

`

Feature request: Protect the duress script itself

Hi there,

I think there are chances that a duress password could be the utmost simple one, which means it could be a dictionary word or easily being brute force and guessed it out. An adversary could also use PII or related information from the victim, to potentially guess what is the password(either the correct one or the duress code).

SHA256 is great, and it does the job on checksum, but not when it comes to the case that we should also protect the duress script itself.

I suggest introducing an option to add cryptographically signed duress script, ideally would be PGP signed, on top of the sha256 hashed script. So that the user can always know if the file has been tampered with, and failover to the sha256 hashed script if the validation failed.

Regards,
Ivan

Errors in pushover tutorial.

The curl command in the pushover walkthrough is missing expire and retry parameters required when priority is set to 2. Additionally it should not be assumed that the user has curl installed. When run the script does not run and when executed manually either fails due to an bash comment included in the curl command before the url or gets an error from the pushover api regarding the expire/retry parameters not being included.

KDE plasma lockscreen (kscreenlocker) support

Please give support on how to set up pam-duress for KDE lockscreen (kscreenlocker).
I successfully set up pam-duress as described and it is working as expected for login but not on the lockscreen of KDE plasma.

Please be so kind and support to get this running on lockscreen for KDE plasma as well.

Thank you in advance

System: Debian 12.2 / Plasma 5.27.5
No /etc/pam.d/kde config available

Potential code injection vulnerability submitted via hacker news user 'wowaname'

Below is the full text of the email sent with the patch. Part of the implementation is already in the PR #12. Remainder of the fix is to use setuid/setgid/setenv as apposed to using system() which potentially provides a code-injection avenue.

(Untested) attempt to fix patch
#2 and remove avenues for improper shell quoting and unexpected code injection.

Implement our own analogue to system() which sets environ for PAMUSER and drops privs without parsing any of the user-modifiable values, guarding against unsafe input. As a side effect, this provides granular error handling for failed setenv, setuid, or setgid as opposed to overloading one system() call for it.

---
 CHANGELOG.md  |  4 +++-
 src/duress.c  | 43 +++++++++--------------------------
 src/util.c    | 62 +++++++++++++++++++++++++++++++++++++++++++++------
 src/version.h |  4 ++--
 4 files changed, 71 insertions(+), 42 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md index 88bed64..b68edc9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,4 +2,6 @@
  - 1.0.0
    - Initial commit of prototype tested on Debian 10.
  - 1.0.1
-   - Fixed some potential memory leaks, linted, and adjusted documentation.
\ No newline at end of file
+   - Fixed some potential memory leaks, linted, and adjusted documentation.
+ - 1.1.0
+   - Fixed privilege escalation vulnerability that could allow an unprivileged user to run commands as root.
\ No newline at end of file
diff --git a/src/duress.c b/src/duress.c index 3252922..ca0b4b5 100644
--- a/src/duress.c
+++ b/src/duress.c
@@ -192,22 +192,6 @@ int is_valid_duress_file(const char *filepath, const char *pam_pass)  #endif //DEBUG
       unsigned char *duress_hash = sha_256_sum(pam_pass, strlen(pam_pass), file_bytes, st.st_size);
 
-#ifdef DEBUG
-      syslog(LOG_INFO, "Loaded Hash: ");
-      for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
-      {
-            syslog(LOG_INFO, "%02X", hash[i]);
-      }
-      syslog(LOG_INFO, "\n");
-      // Output the hash
-      syslog(LOG_INFO, "Computed Hash: ");
-      for (int i = 0; i < SHA256_DIGEST_LENGTH; i++)
-      {
-            syslog(LOG_INFO, "%02X", duress_hash[i]);
-      }
-      syslog(LOG_INFO, "\n");
-#endif //DEBUG
-
       int result = 1;
       if (memcmp(hash, duress_hash, SHA256_DIGEST_LENGTH))
       {
@@ -224,7 +208,7 @@ int is_valid_duress_file(const char *filepath, const char *pam_pass)
       return result;
 }
 
-int process_dir(const char *directory, const char *pam_user, const char *pam_pass)
+int process_dir(const char *directory, const char *pam_user, const char 
+*pam_pass, const char* run_as_user)
 {
       int ret = 0;
       struct dirent *de;
@@ -249,20 +233,15 @@ int process_dir(const char *directory, const char *pam_user, const char *pam_pas
             if (is_valid_duress_file(fpath, pam_pass))
             {
                   syslog(LOG_INFO, "File is valid.\n");
-                  char *cmd = (char *) malloc(strlen(pam_user) + strlen(SHELL_CMD) + strlen(fpath) + 21);
-                  if (sprintf(cmd, "export PAMUSER=%s; %s %s", pam_user, SHELL_CMD, fpath) < 0)
-                  {
-                        syslog(LOG_ERR, "Failed to format command. %s %s\n", SHELL_CMD, fpath);
-                  }
-                  else
-                  {
 #ifdef DEBUG
-                        syslog(LOG_INFO, "Running command %s\n", cmd);
-#endif //DEBUG
-                        system(cmd);
-                        ret = 1;
-                  }
+                  char *cmd = (char *) malloc(
+                        strlen(pam_user) * 2 +
+                        strlen(SHELL_CMD) +
+                        strlen(fpath) + 29);
+                  syslog(LOG_INFO, "Running command %s\n", cmd);
                   free(cmd);
+#endif //DEBUG
+                  ret = run_shell_as(pam_user, run_as_user, fpath);
             }
             free(fpath);
       }
@@ -273,8 +252,8 @@ int process_dir(const char *directory, const char *pam_user, const char *pam_pas
 
 int execute_duress_scripts(const char *pam_user, const char *pam_pass)  {
-      int global_duress_run = process_dir(GLOBAL_CONFIG_DIR, pam_user, pam_pass);
-      int local_duress_run = process_dir(get_local_config_dir(pam_user), pam_user, pam_pass);
+      int global_duress_run = process_dir(GLOBAL_CONFIG_DIR, pam_user, pam_pass, "root");
+      int local_duress_run = 
+ process_dir(get_local_config_dir(pam_user), pam_user, pam_pass, 
+ pam_user);
 
       if (global_duress_run || local_duress_run)
             return PAM_SUCCESS;
@@ -309,4 +288,4 @@ int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)  int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)  {
       return (PAM_SUCCESS);
-}
\ No newline at end of file
+}
diff --git a/src/util.c b/src/util.c
index cdc1738..22907cc 100644
--- a/src/util.c
+++ b/src/util.c
@@ -91,16 +91,64 @@ char *get_local_config_dir(const char *user_name)
     memcpy(config_dir + strlen(home_dir), LOCAL_CONFIG_DIR_SUFFIX, strlen(LOCAL_CONFIG_DIR_SUFFIX));
     config_dir[final_path_len - 1] = 0;
 
-    // Output the hash
-    for (size_t i = 0; i <= strlen(config_dir); i++)
-    {
-        syslog(LOG_INFO, "%02X", config_dir[i]);
-    }
-    syslog(LOG_INFO, "\n");
-
     return config_dir;
 }
 
+pid_t run_shell_as(const char *pam_user, const char *run_as_user, const 
+char *script) {
+    pid_t pid = fork();
+    switch (pid) {
+        case 0: {
+            char* const argv[] = { SHELL, "-c", script, NULL };
+            struct passwd *run_as_pw = getpwnam(run_as_user);
+
+            if (setenv("PAMUSER", pam_user, 1)) { #ifdef DEBUG
+                syslog(LOG_ERR, "Could not set environment for PAMUSER 
+to %s, %d.\n", pam_user, errno); #endif //DEBUG
+                goto child_failed;
+            }
+
+            if (!run_as_pw) {
+#ifdef DEBUG
+                syslog(LOG_ERR, "Could not getpwnam %s, %d.\n", 
+run_as_user, errno); #endif //DEBUG
+                goto child_failed;
+            }
+
+            if (setuid(run_as_pw->pw_uid)) { #ifdef DEBUG
+                syslog(LOG_ERR, "Could not setuid, %d.\n", errno); 
+#endif //DEBUG
+                goto child_failed;
+            }
+            if (setgid(run_as_pw->pw_gid)) { #ifdef DEBUG
+                syslog(LOG_ERR, "Could not setgid, %d.\n", errno); 
+#endif //DEBUG
+                goto child_failed;
+            }
+
+            execv(SHELL_CMD, argv);
+
+child_failed:
+#ifdef DEBUG
+            syslog(LOG_ERR, "Could not run script %s, %d.\n", script, 
+errno); #endif //DEBUG
+            _exit(1);
+            break;
+        }
+        case -1:
+#ifdef DEBUG
+            syslog(LOG_ERR, "Could not fork for script %s, %d\n", 
+script, errno); #endif //DEBUG
+            break;
+        default:
+            break;
+    }
+    return pid;
+}
+
 unsigned char *sha_256_sum(const char *payload, size_t payload_size, const char *salt, size_t salt_size)  {
     unsigned char salt_hash[SHA256_DIGEST_LENGTH]; diff --git a/src/version.h b/src/version.h index 689e2fc..6a6e0df 100644
--- a/src/version.h
+++ b/src/version.h
@@ -2,7 +2,7 @@
 #define SOURCE_VERSION_H
 
 #define VERS_MAJOR 1
-#define VERS_MINOR 0
-#define VERS_REVISION 1
+#define VERS_MINOR 1
+#define VERS_REVISION 0
 
 #endif
\ No newline at end of file
--
2.31.1

Some PAM config files looking very different from the example

I was interested in testing this package out on openSUSE so I managed (I think) to put in OBS in a very bare bones way. (https://build.opensuse.org/package/show/home:lilfroggy/pam-duress).
It seems to install properly, however my pam config file doesn't really seem to have the same syntax as the one in the example and also says it won't except any modifications to it:

/etc/pam.d/common-auth:

#%PAM-1.0
#
# This file is autogenerated by pam-config. All manual
# changes will be overwritten!
#
# The pam-config configuration files can be used as template
# for an own PAM configuration not managed by pam-config:
#
# for i in account auth password session; do \
#      rm -f common-$i; sed '/^#.*/d' common-$i-pc > common-$i; \
# done
#
# Afterwards common-{account, auth, password, session} can be
# adjusted. Never edit or delete common-*-pc files!
#
# WARNING: changes done by pam-config afterwards are not
# visible to the PAM stack anymore!
#
# WARNING: self managed PAM configuration files are not supported,
# will not see required adjustments by pam-config and can become
# insecure or break system functionality through system updates!
#
#
# Authentication-related modules common to all services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the
# traditional Unix authentication mechanisms.
#
auth	required	pam_env.so	
auth	optional	pam_gnome_keyring.so
auth	required	pam_unix.so	try_first_pass

Any ideas on how to make it work with such a setup?

Privilege escalation vulnerability.

The initial implementation does not run user scripts as the target user; nor checks to see if the script being run is owned by the user; only if it's readable and executable by the "owner". Then all scripts are run as root, even the local ones. This could allow and attacker to use a non-privileged account to execute root level commands if the PAM Duress module is employed.

Repro steps:
image

pam_test does not function properly in Arch

Issu #29 uncovered an issue with Arch where pam_test would return authentication failures while ssh localhost and sudo su - USER was triggering the appropriate scripts. Determine if/why the pam_test isn't function in Arch and/or determine if it's worth trying to maintain that tester vs advising users to use ssh localhost or sudo su - USER to test properly.

Duress password does not unlock keyring

After logging in with a duress password, the keyring notification window pops up and says the login password no longre matches the keyring password and asks for the password

Account not available error when running scripts in /etc/duress.d

Getting "This account is currently not available" message when running scripts out of /etc/duress.d/

image

This is likely due to the execution command being su - USER -c "/bin/sh COMMAND". That is sufficient when running commands out of ~/.duress for local user duress scripts but if a root account is disabled, this results in the account being unavailable message. Easy fix is just to change the execution command to /bin/sh COMMAND when running duress scripts out of /etc/duress.d

Make ~/.duress directory a toggleable feature.

Some admins may not trust their users to create duress scripts and want full control to only have the ones in /etc/duress.d run when duress password is used. Modify the module such that it reads in a configuration file /etc/duress.conf to see if the administrator wants to enable ~/.duress for users and create a group that controls which users have their ~/.duress files parsed during login.

/etc/duress.d scripts should run after ~/.duress scripts

/etc/duress.d scripts should be run after ~/.duress script to allow for a script to be implemented that removes pam-duress itself as a cleanup action. In the current implementation one would have to write a delayed-action script to remove pam-duress system-wide which if misconfigured may allow an attacker to see the modules presence after the attacker has dropped to a user shell.

Lack of unit tests.

At the moment there are not unit tests to ensure compatibility with linux, freebsd, etc. Also negative testing should be added to ensure that the module doesn't permit authentication attacks such as impersonation or privilege escalation. Finally there should be tests to ensure that scripts are only run if owned by the user or group the user; both positive tests and negative. Here's a list of the desired positive and negative test to implement.

  • Positive test for scripts in ~/.duress/*
    • Test with 500, 510, 550, 700, 710 & 750 # NOTE: Add support for 510 and 710 permissions.
  • Positive test for scripts in /etc/duress.d/*
  • Negative tests for improper permissions on duress scripts.
  • Docker files for all tests to run them under different distributions; ideally Debian, Ubuntu, BSD, CentOS/Redhat, etc.

How to make it work on arch linux

I have trouble configuring duress on arch linux. First of all I am not even sure if using /etc/pam.d/system-auth is ok since there is no common-auth. The default content of system-auth is:

#%PAM-1.0

auth       required                    pam_faillock.so      preauth
# Optionally use requisite above if you do not want to prompt for the password
# on locked accounts.
-auth      [success=2 default=ignore]  pam_systemd_home.so
auth       [success=1 default=bad]     pam_unix.so          try_first_pass nullok
auth       [default=die]               pam_faillock.so      authfail
auth       optional                    pam_permit.so
auth       required                    pam_env.so
auth       required                    pam_faillock.so      authsucc
# If you drop the above call to pam_faillock.so the lock will be done also
# on non-consecutive authentication failures.

-account   [success=1 default=ignore]  pam_systemd_home.so
account    required                    pam_unix.so
account    optional                    pam_permit.so
account    required                    pam_time.so

-password  [success=1 default=ignore]  pam_systemd_home.so
password   required                    pam_unix.so          try_first_pass nullok shadow sha512
password   optional                    pam_permit.so

-session   optional                    pam_systemd_home.so
session    required                    pam_limits.so
session    required                    pam_unix.so
session    optional                    pam_permit.so

I have tried replacing

-auth      [success=2 default=ignore]  pam_systemd_home.so
auth       [success=1 default=bad]     pam_unix.so          try_first_pass nullok
auth       [default=die]               pam_faillock.so      authfail
auth       optional                    pam_permit.so

with

-auth      [success=3 default=ignore]  pam_systemd_home.so
auth       [success=2 default=bad]     pam_unix.so          try_first_pass nullok
auth       [success=1 default=ignore]  pam_duress.so
auth       [default=die]               pam_faillock.so      authfail
auth       optional                    pam_permit.so

When I run a test with regular password I get:

~ $ sudo pam_test $USER                                                                                                                                             [master]
[sudo] password for dusan:
Credentials accepted.
Not Authenticated

And when I use a pass I've set for a script I just get "sorry, try again" and pass prompt

Example Code May Not Work On Fresh Ubuntu Systems

I installed a new image of Ubuntu to test the project and kept encountering an error where the code would not execute:

Jun 24 01:38:49 zaku pam_test: File is valid.
Jun 24 01:38:49 zaku pam_test: Processing /etc/duress.d.
Jun 24 01:38:49 zaku pam_test: Could not open directory /etc/duress.d, No such file or directory.
Jun 24 01:38:49 zaku pam_test: Executing /home/zaku/.duress/hello.sh.
Jun 24 01:38:49 zaku pam_test: Could not run script /home/zaku/.duress/hello.sh, Exec format error.

I resolved the issue by specifying on top of the shell script that it's a shell script

echo -e '#!/bin/sh\necho "Hello World"'
#!/bin/sh
echo "Hello World"

This is what we want to indicate that this is a shell script

How can I get it working on gentoo?

Hello, first of all can I just say thank you for this project as it has worked well on other systems. However, on my gentoo install I am running into a few problems.
Firstly, the system-auth vs common-auth issue, I followed the guidance for arch users however I do not know if it has worked for reasons I will state later
Next, the library was placed in /lib/security and was not being detected. I moved it to /lib64/security and su stopped spitting out an error saying it could not find the library
However, it still did not work. I attempted to log the PAM while ssh'ing in with and without my duress password. The only difference between the two was this line:
pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=::1 user=max
This suggests pam_duress.so is not even being loaded so that's why I believe that the system-auth might not be valid
I am not knowledgeable at all with PAM so im sure there is an easy answer that I am just missing.
Any help is appreciated! Max

Failed to allocate buffer for getpwnam_r

This is thrown from

    size_t buffer_len = sysconf(_SC_GETPW_R_SIZE_MAX) * sizeof(char);
    char *buffer = (char *) malloc(buffer_len);
    if (buffer == NULL)
    {
        syslog(LOG_INFO, "Failed to allocate buffer for getpwnam_r.\n");
        exit(2);
    }
    getpwnam_r(user_name, pwd, buffer, buffer_len, &pwd);

in src/util.c, since for some reason, sysconf(_SC_GETPW_R_SIZE_MAX) yields -1 on that system (alpine linux 3.13). Any more infos needed?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.