my_make_scrambled_password() is not a replacement for make_scrambled_password()

Bug #1574911 reported by OwN
10
This bug affects 1 person
Affects Status Importance Assigned to Milestone
pam-mysql (Ubuntu)
Fix Released
High
Andreas Hasenack
vsftpd (Ubuntu)
Invalid
Undecided
Unassigned

Bug Description

artful libpam-mysql-0.8.0-1

TL;DR

pam_mysql in artful will in the best case scenario just fail to authenticate users whose passwords were hashed with the server-side PASSWORD() SQL function. There is a buffer overflow happening, but it doesn't trigger a crash for some reason.

Detailed explanation follows.

pam_mysql, when crypt=2 is set in its configuration, it expects the password to be hashed according to the server-side PASSWORD() SQL function. From its README:

2 (or "mysql") = Use MySQL PASSWORD() function. It is possible that the encryption function used by PAM-MySQL is different from that of the MySQL server, as PAM-MySQL uses the function defined in MySQL's C-client API instead of using PASSWORD() SQL function in the query.

pam_mysql is indeed using an incorrect hash function: it's using my_make_scrambled_password() as a replacement for make_scrambled_password() to locally hash the given password and compare it with what is stored in the database:

  char buf[42];
  my_make_scrambled_password(buf, passwd, strlen(passwd));
  vresult = strcmp(row[0], buf);

row[0] is the result of the SQL query that fetches the user's password hash

There are two problems with this:
a) my_make_scrambled_password() writes CRYPT_MAX_PASSWORD_SIZE bytes to buf, and that's way more than 42. From the mysql source code:
#define CRYPT_SALT_LENGTH 20
#define CRYPT_MAGIC_LENGTH 3
#define CRYPT_PARAM_LENGTH 13
#define SHA256_HASH_LENGTH 43
#define CRYPT_MAX_PASSWORD_SIZE (CRYPT_SALT_LENGTH + \
                                 SHA256_HASH_LENGTH + \
                                 CRYPT_MAGIC_LENGTH + \
                                 CRYPT_PARAM_LENGTH)

42 is the length of the hexified hash produced by make_scrambled_password(), not my_make_scrambled_password().

b) the output of my_make_scrambled_password() is not a hex string like "*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19", but something like "$5$9Ws#033Q.TZtI4^?X#026y@@{e2$OxTGgW3PiJUVZ/AChiJgAdIWQ2u2B8kA/hHZgqNj.y.". So even if buf had the correct size, the comparison would never match what's produced by PASSWORD() on the server side. As the documentation admitted could happen.

If my_make_scrambled_password() is not found in the system mysqlclient library, pam_mysql will reimplement it, and funnily enough this reimplementation actually mimicks the desired behavior of make_scrambled_password() and produces an hexified hash compatible with the server's PASSWORD() function and with the right length of 42 bytes.

So, if mysqlclient doesn't export my_make_scrambled_password(), pam_mysql will work because it will use its own implementation. But in the ubuntu case, my_make_scrambled_password() is exported and used, and leads to this bug.

To reproduce this problem, setup mysql, vsftpd and libpam-mysql on artful as explained in bug #1574900.

I cannot explain why vsftpd doesn't crash in this scenario in artful: gcc's stack protector isn't triggered, nor is a segfault. In debugging I can see the buf variable getting way more than 42 bytes written to it, and if I add another stack variable next to it, it gets corrupted. But no crashes, just an authentication error.

Revision history for this message
OwN (own3mall) wrote :

Nevermind, this is caused by the broken libpam-mysql package. Close bug.

Changed in vsftpd (Ubuntu):
status: New → Invalid
Revision history for this message
Sergey (mymail333g) wrote :

I had the same problem.
I just installed libpam-mysql(libpam-mysql_0.7~RC1-4ubuntu1_i386.deb) from the repository 15.10. MariaDB - Vsftpd - Pam worked.

Revision history for this message
OwN (own3mall) wrote :

This bug needs to be re-examined and re-opened now that libpam-mysql has been fixed:

libpam-mysql fixes:

https://bugs.launchpad.net/ubuntu/+source/pam-mysql/+bug/1574900

Even with the latest libpam-mysql fix, VSFTPD still crashes:

USER test
331 Please specify the password.
PASS xxxx
*** stack smashing detected ***: /usr/sbin/vsftpd terminated
500 OOPS: priv_sock_get_result
Disconnecting from site localhost

Contents of /etc/pam.d/vsftpd:

auth required pam_mysql.so user={DBUSERHERE} passwd={DBPASSHERE} host=localhost db={DBNAMEHERE} table=ftpaccounts usercolumn=ftpusername passwdcolumn=password crypt=2
account required pam_mysql.so user={DBUSERHERE} passwd={DBPASSHERE} host=localhost db={DBNAMEHERE} table=ftpaccounts usercolumn=ftpusername passwdcolumn=password crypt=2

Changed in vsftpd (Ubuntu):
status: Invalid → New
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

Hi OwN,
I guess this is expected as the bug that was fixed in libpam-mysql is the one you before patched manually (following your bug description) and since you ran into the stack smashing with it it is not unexpected to still do so.

Also please note that the other bug is only in proposed so far, did you run your test with the fixed libpam-mysql against proposed?
If not you'd either have to set up proposed on your system or wait for it to fully pass proposed-migration [1].

Yet I agree that it is time to re-examine as now one should be able to reproduce just out of archive right?

[1]: https://wiki.ubuntu.com/ProposedMigration

Revision history for this message
Christian Ehrhardt  (paelzer) wrote :
Download full text (5.9 KiB)

I installed all as outlined in the bug and first was able to verify that the fix in proposed fixes the make_scrambled_password issue.

Lacking an ehcp setup I created a trvial DB for auth like this:

/etc/pam.d/vsftpd (please don't mind the nons ecure setup on my test env):
auth required pam_mysql.so user=root passwd="" host=localhost db=test table=ftpaccounts usercolumn=ftpusername passwdcolumn=password crypt=2
 account required pam_mysql.so user=root passwd="" host=localhost db=test table=ftpaccounts usercolumn=ftpusername passwdcolumn=password crypt=2

SQL via mysql:
create database test;
use test;
create table ftpaccounts (
    -> user_id int(6) not null auto_increment,
    -> password varchar(16) not null,
    -> primary key (user_id),
    -> key user_id (user_id) );
insert into ftpaccounts(ftpusername, password) values ('ubuntu', encrypt('ubuntu'));

First I was trying a wrong PW, to see what happens then:
Mar 20 14:08:59 xenial-test vsftpd[6447]: pam_mysql - MySQL error (Access denied for user 'ubuntu'@'localhost' (using password: YES))

But then with that in place use ftp to connect.
$ ftp 10.0.4.174
Connected to 10.0.4.174.
220 Welcome to vsFTPd Server
Name (10.0.4.174:paelzer): ubuntu
331 Please specify the password.
Password:
*** stack smashing detected ***: /usr/sbin/vsftpd terminated
Login failed.

Now this is reported on the client, but the main vsftp process is just fine.
Maybe it spawns a process on each login.

No report on the vsftpd host in journal at all.
I "normalized" myself step by step.

First I replaced the custom vsftpd.conf in the description with the default one from the package.
=> Same issue.

Resetting the /etc/pam.d/vsftpd made it working - at least that.

I also made a try keeping the content of the default pam, but then added the pam_mysql lines at the end.

Eventually I found that of the two lines the "auth" one is the killing one.
Is working ok:
account required pam_mysql.so user=root passwd="" host=localhost db=test table=ftpaccounts usercolumn=ftpusername passwdcolumn=password crypt=2
Triggers the bug:
auth required pam_mysql.so user=root passwd="" host=localhost db=test table=ftpaccounts usercolumn=ftpusername passwdcolumn=password crypt=2

Then I was installing vsftpd-dbg and attached gdb to it.
As I expected it to fork its childs per connection I set
(gdb) set follow-fork-mode child

It seems it spawns two childs, and the second one is the one dying badly (not the first).
Ok, then we try:
(gdb) set detach-on-fork off
With that on it is a bit of juggling processes but I finally reached this:

That still looks like a breakage in /lib/security/pam_mysql.so more than anything else.
Add "libpam-mysql-dbgsym" on top and look again:

#0 0x00007f31168d5428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007f31168d702a in __GI_abort () at abort.c:89
#2 0x00007f31169177ea in __libc_message (do_abort=do_abort@entry=1, Reading symbols from /usr/lib/debug/lib/x86_64-linux-gnu/libcrypt-2.23.so...done.
Reading symbols from /usr/lib/debug/.build-id/4d/7f52f335dc9665c2dcf308ce6514a6ae86dede.debug...done.
Reading symbols from /usr/lib/debug/lib/x86_64-linux-gnu/librt-2.23.so...done.
Re...

Read more...

Changed in vsftpd (Ubuntu):
status: New → Confirmed
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

Next:
- Driving to devs of libpam-mysql?
- Proper testing on newer releases?
- debug with valrgind and such.

I'm out for now but I hope that helped to get much closer to the issue.

Revision history for this message
OwN (own3mall) wrote :

Wow, thank you so much for helping us look further into the issue! Should I file a bug in libpam-mysql referencing this issue?

I did install and test the proposed package, and the stack smashing was still there.

Let me know. I'd love for this to get fixed soon.

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

I posted some debugging in https://bugs.launchpad.net/ubuntu/+source/pam-mysql/+bug/1574900/comments/27

TL;DR
- pam_mysql.c buf in pam_mysql_check_passwd() is overflowing
- my_make_scrambled_password() is NOT returning content that can be compared to what is stored in the mysql DB when using PASSWORD().
- my_make_scrambled_password_sha1() seems to be the right one to use, as it returns a string of hex values, but it's not exported

Not sure where this should continue, here or there :)

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

pure-ftpd sorted this out by reimplementing make_scrambled_password() if it's not exported:

https://github.com/jedisct1/pure-ftpd/commit/2db6b50c7b7c638104bd9639994f0574e8f4813c

I don't know when make_scrambled_password() stopped being exported in libmysqlclient, but libmysqlclient's my_make_scrambled_password() is NOT a replacement for it. The right replacement for it is my_make_scrambled_password_sha1(), and currently make_scrambled_password() is a wrapper around my_make_scrambled_password_sha1(), but neither are exported in libmysqlclient:
/*
  Wrapper around my_make_scrambled_password() to maintain client lib ABI
  compatibility.
  In server code usage of my_make_scrambled_password() is preferred to
  avoid strlen().
  SYNOPSIS
    make_scrambled_password()
    buf OUT buffer of size 2*SHA1_HASH_SIZE + 2 to store hex string
    password IN NULL-terminated password string
*/

void make_scrambled_password(char *to, const char *password)
{
  my_make_scrambled_password_sha1(to, password, strlen(password));
}

So pam_mysql should probably reimplement my_make_scrambled_password_sha1() in order to support passwords hashed with the server PASSWORD() function (the crypt=2 option in pam_mysql).

Changed in pam-mysql (Ubuntu):
status: New → Confirmed
Changed in vsftpd (Ubuntu):
status: Confirmed → Invalid
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Submitted an issue against one of the forks of pam_mysql: https://github.com/NigelCunningham/pam-MySQL/issues/29

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Also submitted an issue against pure-ftpd, because it suffers from the same problem: https://github.com/jedisct1/pure-ftpd/issues/58

Revision history for this message
Andreas Hasenack (ahasenack) wrote :
summary: - vsftpd 500 oops stack smashing detected - Ubuntu 16.04
+ my_make_scrambled_password() is not a replacement for
+ make_scrambled_password()
description: updated
description: updated
description: updated
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Debian stretch isn't affected. There, libmariadbclient.so.18 exports a my_make_scrambled_password() that produces the correct/expected hexified hash. Which I wonder if it's what libmysqlclient.so.18 did (artful is at .20).

Revision history for this message
Andreas Hasenack (ahasenack) wrote :

Opened upstream bug against mysql explaining the situation.

https://bugs.mysql.com/bug.php?id=86357

Changed in pam-mysql (Ubuntu):
assignee: nobody → Andreas Hasenack (ahasenack)
status: Confirmed → In Progress
importance: Undecided → High
Revision history for this message
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package pam-mysql - 0.8.0-1ubuntu1

---------------
pam-mysql (0.8.0-1ubuntu1) artful; urgency=medium

  * debian/patches/fix-mysql-password-hash.patch: Calculate the correct
    hash for mysql PASSWORD(). (LP: #1574911, LP: #1574900)

 -- Andreas Hasenack <email address hidden> Wed, 06 Sep 2017 19:22:51 -0300

Changed in pam-mysql (Ubuntu):
status: In Progress → Fix Released
Revision history for this message
Andreas Hasenack (ahasenack) wrote :

I have a branch with a patch for xenial, but I'm afraid the pam-mysql source package is very much broken there. The build process de-applies the patches, then builds the binaries, then applies the patches, and finishes. Not even the two existing patches are applied in that package, much less my third patch.

The xenial branch is at https://code.launchpad.net/~ahasenack/ubuntu/+source/pam-mysql/+git/pam-mysql/+ref/xenial-pam-mysql-scrambled-1574911 if someone wants to take over. The patch is backported in there but untested.

To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.