kylemanna / docker-openvpn

🔒 OpenVPN server in a Docker container complete with an EasyRSA PKI CA
https://hub.docker.com/r/kylemanna/openvpn/
MIT License
8.73k stars 2.39k forks source link

TOTP auth accepts code from any TOTP-user #272

Open ChessSpider opened 7 years ago

ChessSpider commented 7 years ago

Hi,

I noticed that OpenVPN correctly uses the Certificate CN as username for authentication. However the TOTP-plugin seem to use the user-provided username. An 'disgruntled employee' attacker could steal someone's certificate and use his personal TOTP-code to authenticate. I believe in the logs it will still show 'ChessSpider' as username, making this an impersonating attack. (Unless someone notices the used totp username)

E.g. imagine disgruntled_employee stole my certificate, he can login using his own totp.

openvpn    | Tue May 30 15:08:18 2017 us=171846 [ip]:65211 VERIFY OK: depth=1, CN=vpn.example.com
openvpn    | Tue May 30 15:08:18 2017 us=175749 [ip]:65211 VERIFY OK: depth=0, CN=ChessSpider
.......
openvpn    | AUTH-PAM: BACKGROUND: received command code: 0
openvpn    | AUTH-PAM: BACKGROUND: USER: disgruntled_emplyee

I believe the script /etc/pam.d/openvpn needs to be this:

bash-4.3# cat /etc/pam.d/openvpn 
# Uses google authenticator library as PAM module using a single folder for all users tokens
# User root is required to stick with an hardcoded user when trying to determine user id and allow unexisting system users
# See https://github.com/google/google-authenticator/tree/master/libpam#secretpathtosecretfile--usersome-user
auth required pam_google_authenticator.so secret=/etc/openvpn/otp/${COMMON_NAME}.google_authenticator user=root

# Accept any user since we're dealing with virtual users there's no need to have a system account (pam_unix.so)
account sufficient pam_permit.so

The current version uses ${USER} , and I changed it to ${COMMON_NAME} (on line 5). I haven't been able to test this yet, but it should be a valid variable after finding out all environment variables by starting openvpn with verb 7 in server-side ```openvpn.conf```` .

I'll continue looking at it tomorrow, Cheers,

ChessSpider commented 7 years ago

As a side-note, I have also been looking at a way to make openvpn check if the certificate CN matches the user-supplied username. But I haven't found that yet.

If it does by default then this attack is mitigated (I so far only theorized this attack). But yeah, it doesn't seem so as it goes to the PAM-step after verifying the certificate.

kylemanna commented 7 years ago

The bypass sounds plausible based on what you described, but I don't use TOTP directly to comment any more beyond that. I would be interested in seeing a test case (see the /test/tests/otp directory) for this bypass.

ChessSpider commented 7 years ago

Yeah no worries. I also wonder what would happen if the user chooses as username: foobar secret=None nullok # or something similar, which should expand to:

auth required pam_google_authenticator.so secret=/etc/openvpn/otp/foobar secret=None nullok #.google_authenticator user=root

The nullok option allows users to login without having setup totp ( see https://github.com/google/google-authenticator-libpam )

but again just thinking out loud. I'll do some testing tomorrow.

ChessSpider commented 7 years ago

Hi, just reporting back.

I think my complete TOTP bypass I described in my previous comment is not possible. This is because ${USER} is being replaced by the username inside the module as opposed to (when parsing) the service-definition. So that's a good thing! I guess it may be possible use usernames like .../.../whatever but there doesn't seem to be a '.google_authenticator' file on the system which accepts any and all verification codes. Maybe there's stuff possible with inserting null bytes but at the end of the day the bug needs to be fixed.

I managed to fix the problem by changing the openvpn.conf to: plugin /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so "openvpn login COMMONNAME verification PASSWORD"

This overrides the default 'login' to return COMMONNAME instead of USERNAME. See https://github.com/OpenVPN/openvpn/blob/master/src/plugins/auth-pam/README.auth-pam:

For example: plugin openvpn-auth-pam.so "login login USERNAME password PASSWORD" tells auth-pam to (a) use the "login" PAM module, (b) answer a "login" query with the username given by the OpenVPN client, and (c) answer a "password" query with the password given by the OpenVPN client.

The /etc/pam.d/openvpn file can stay the same.

I'll have a quick look at the tests too, but I also want to get this VPN up and running as I do like the image you made 👍 Quick, to the point and properly documented

ChessSpider commented 7 years ago

Steps to reproduce:

Setup basic infra

  1. docker run --net=none --rm -t -i --log-driver=none -v `pwd`/data:/etc/openvpn --rm kylemanna/openvpn ovpn_genconfig -u udp://vpn.example.com -2 -C 'AES-256-CBC' -a 'SHA384'
  2. docker run -e EASYRSA_ALGO=ec -e EASYRSA_CURVE=secp384r1 -e EASYRSA_KEY_SIZE=4096 -e EASYRSA_CRL_DAYS=3650 -v /dev/random:/dev/random -v /dev/urandom:/dev/urandom -v `pwd`/data:/etc/openvpn --rm -it kylemanna/openvpn ovpn_initpki

Create user ChessSpider

  1. docker run --log-driver=none -e EASYRSA_ALGO=ec -e EASYRSA_CURVE=secp384r1 -e EASYRSA_KEY_SIZE=4096 -v /dev/random:/dev/random -v /dev/urandom:/dev/urandom -vpwd/data:/etc/openvpn --rm -it kylemanna/openvpn easyrsa build-client-full chessspider
  2. docker run --log-driver=none -vpwd/data:/etc/openvpn --rm -t kylemanna/openvpn ovpn_otp_user chessspider
  3. docker run --log-driver=none -vpwd/data:/etc/openvpn --rm kylemanna/openvpn ovpn_getclient chessspider > chessspider.ovpn

Create user disguntled_employee

  1. docker run --log-driver=none -e EASYRSA_ALGO=ec -e EASYRSA_CURVE=secp384r1 -e EASYRSA_KEY_SIZE=4096 -v /dev/random:/dev/random -v /dev/urandom:/dev/urandom -vpwd/data:/etc/openvpn --rm -it kylemanna/openvpn easyrsa build-client-full disgruntled_employee
  2. docker run --log-driver=none -vpwd/data:/etc/openvpn --rm -t kylemanna/openvpn ovpn_otp_user disgruntled_employee
  3. docker run --log-driver=none -vpwd/data:/etc/openvpn --rm kylemanna/openvpn ovpn_getclient disgruntled_employee > disgruntled_employee.ovpn

Start & Connect

  1. Start the OpenVPN service with the above config
  2. Connect w/ certificate of ChessSpider and in the login-box of OpenVPN enter disgruntled_employee and its generated TOTP code.
  3. You should see something like the output below

debug log before changes:

openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 VERIFY OK: depth=1, CN=vpn.example.com
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 VERIFY OK: depth=0, CN=chessspider
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_VER=2.4.2
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_PLAT=win
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_PROTO=2
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_NCP=2
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_LZ4=1
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_LZ4v2=1
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_LZO=1
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_COMP_STUB=1
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_COMP_STUBv2=1
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_TCPNL=1
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 peer info: IV_GUI_VER=OpenVPN_GUI_11
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 PLUGIN_CALL: POST /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so/PLUGIN_AUTH_USER_PASS_VERIFY status=0
openvpn    | Wed May 31 11:16:23 2017 [ip]:59081 TLS: Username/Password authentication succeeded for username 'disgruntled_employee' 
openvpn    | Wed May 31 11:16:24 2017 [ip]:59081 Control Channel: TLSv1.2, cipher TLSv1/SSLv3 ECDHE-ECDSA-AES256-GCM-SHA384
openvpn    | Wed May 31 11:16:24 2017 [ip]:59081 [chessspider] Peer Connection Initiated with [AF_INET][ip]:59081
openvpn    | Wed May 31 11:16:24 2017 MULTI: new connection by client 'chessspider' will cause previous active sessions by this client to be dropped.  Remember to use the --duplicate-cn option if you want multiple clients u

Fix

Change the openvpn.conf line on the server-side to:

plugin /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so "openvpn login COMMONNAME verification PASSWORD"

Debug log after fix (note it still shows disgruntled_employee but will in fact use the commonname):

openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 VERIFY OK: depth=1, CN=vpn.example.com
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 VERIFY OK: depth=0, CN=chessspider
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_VER=2.4.2
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_PLAT=win
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_PROTO=2
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_NCP=2
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_LZ4=1
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_LZ4v2=1
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_LZO=1
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_COMP_STUB=1
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_COMP_STUBv2=1
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_TCPNL=1
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 peer info: IV_GUI_VER=OpenVPN_GUI_11
openvpn    | AUTH-PAM: BACKGROUND: user 'disgruntled_employee' failed to authenticate: Authentication failure
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 PLUGIN_CALL: POST /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so/PLUGIN_AUTH_USER_PASS_VERIFY status=1
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 PLUGIN_CALL: plugin function PLUGIN_AUTH_USER_PASS_VERIFY failed with status 1: /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so
openvpn    | Wed May 31 11:19:54 2017 [ip]:61002 TLS Auth Error: Auth Username/Password verification failed for peer

I'm not really sure how to set this up in a test-case that suits your requirements.

FernandoMiguel commented 6 years ago

I can confirm this wrong behaviour. Yesterday I generated a common test users with otp and by accident entered the wrong one while connecting once, and was surprised by the authentication working. What would be the best way to minimise this risk?

ChessSpider commented 6 years ago

please see the comment above yours and the instructions underneath the "Fix" header to fix this problem :)

FernandoMiguel commented 6 years ago

@ChessSpider thanks for the solution. since I'm merely using the -2 parameter, and avoiding touching the container config elsewhere, it would be preferable to fix this upstream. Hopefully it will happen. thanks for the comment

FernandoMiguel commented 6 years ago

seems to be this line https://github.com/kylemanna/docker-openvpn/blob/master/bin/ovpn_genconfig#L382 I can do a PR if it helps

FernandoMiguel commented 6 years ago

i've created PR https://github.com/kylemanna/docker-openvpn/pull/338

ChessSpider commented 6 years ago

So has/will this ever be fixed in master? :)

kylemanna commented 6 years ago

So has/will this ever be fixed in master? :)

When someone read the CONTRIBUTING.md file and sends an appropriate pull request.

ChessSpider commented 6 years ago

Seriously? It's a security risk with an elaborate description and a one line fix, open for a year, with a pull request pending, and this is your response? Just say thanks for the free security research and fix it.

On Mon, 11 Jun 2018, 18:42 Kyle Manna notifications@github.com wrote:

So has/will this ever be fixed in master? :)

When someone read the CONTRIBUTING.md file and sends an appropriate pull request.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/kylemanna/docker-openvpn/issues/272#issuecomment-396307755, or mute the thread https://github.com/notifications/unsubscribe-auth/ACEWNKB1ENzd9KJJ6xWVmxG_lmYDPfedks5t7p4DgaJpZM4NqdJn .

kylemanna commented 6 years ago

Seriously?

Yes, seriously, TOTP is a feature I've never used and has caused issues throughout the years. My fix would be remove it and simply be done.

When I get time, maybe I'll get around to it. In the meantime, if anyone wants to do what's necessary, concise and well documented PRs are always welcome.

lgg42 commented 4 years ago

So has/will this ever be fixed in master? :)

When someone read the CONTRIBUTING.md file and sends an appropriate pull request.

If I take @FernandoMiguel PR and recreate it with a new PR following the contribution guidelines as you stated, could it be merged if all tests are passed? We (company) would really like to use OTP+certificate password. But of course, we need the OTP to function correctly.

nayneyT commented 4 years ago

@ChessSpider @FernandoMiguel

With the response from Kyle that you guys had I understand if you've washed you hands of this project. However, I am hoping that you can help me. I have made the change to my openvpn.conf file (commented out the old line and inserted your new line):

# Enable OTP+PAM for user authentication
# plugin /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn
plugin /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so "openvpn login COMMONNAME verification PASSWORD"
reneg-sec 0

However, I can still authenticate with any username that I use in openvpn client.

Wed Aug 12 13:19:12 2020 71.255.234.229:8254 VERIFY OK: depth=1, CN=ca.myvpn.host
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 VERIFY OK: depth=0, CN=user1
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_VER=3.git:released:3e56f9a6:Release
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_PLAT=android
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_NCP=2
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_TCPNL=1
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_PROTO=2
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_LZO_STUB=1
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_COMP_STUB=1
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_COMP_STUBv2=1
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_GUI_VER=net.openvpn.connect.android_3.2.2-5027
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 peer info: IV_SSO=openurl
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 PLUGIN_CALL: POST /usr/lib/openvpn/plugins/openvpn-plugin-auth-pam.so/PLUGIN_AUTH_USER_PASS_VERIFY status=0
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 TLS: Username/Password authentication succeeded for username 'joebloggs'
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 Control Channel: TLSv1.3, cipher TLSv1.3 TLS_AES_256_GCM_SHA384, 2048 bit RSA
Wed Aug 12 13:19:12 2020 71.255.234.229:8254 [user1] Peer Connection Initiated with [AF_INET]71.255.234.229:8254
Wed Aug 12 13:19:12 2020 user1/71.255.234.229:8254 MULTI_sva: pool returned IPv4=192.168.255.6, IPv6=(Not enabled)
Wed Aug 12 13:19:12 2020 user1/71.255.234.229:8254 MULTI: Learn: 192.168.255.6 -> user1/71.255.234.229:8254
Wed Aug 12 13:19:12 2020 user1/71.255.234.229:8254 MULTI: primary virtual IP for user1/71.255.234.229:8254: 192.168.255.6
Wed Aug 12 13:19:12 2020 user1/71.255.234.229:8254 PUSH: Received control message: 'PUSH_REQUEST'
Wed Aug 12 13:19:12 2020 user1/71.255.234.229:8254 SENT CONTROL [user1]: 'PUSH_REPLY,block-outside-dns,dhcp-option DNS 8.8.8.8,dhcp-option DNS 8.8.4.4,route 192.168.255.1,topology net30,ping 10,ping-restart 60,ifconfig 192.168.255.6 192.168.255.5,peer-id 0,cipher AES-256-GCM' (status=1)
Wed Aug 12 13:19:12 2020 user1/71.255.234.229:8254 Data Channel: using negotiated cipher 'AES-256-GCM'
Wed Aug 12 13:19:12 2020 user1/71.255.234.229:8254 Outgoing Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key
Wed Aug 12 13:19:12 2020 user1/71.255.234.229:8254 Incoming Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key

Any suggestions?

FernandoMiguel commented 4 years ago

I've honestly not touched this project in 3 years. if you didnt mention me, i wouldnt even have notice the comments.

adeweever commented 3 years ago

I am facing the same error as @nayneyT, were you ever able to solve this issue?