TUCoPS :: Linux :: General :: lnx5579.htm

util-linux chfn local root vulnerability
30th Jul 2002 [SBWID-5579]
COMMAND

	util-linux chfn local root vulnerability

SYSTEMS AFFECTED

	Red Hat Linux 7.3 and previous; potentially many other distributions  up
	to date that use util-linux to provide chfn and chsh utilities.
	

	Systems that ship chfn within  the  shadow-utils  package  (for  example
	SuSE) are not vulnerable.
	

	See CERT VU#405955 for more information

PROBLEM

	In Michal Zalewski RAZOR [http://razor.bindview.com] advisory :
	

	The vulnerability is present within the shared code  of  the  util-linux
	package.  For  the  purpose  of  this  discussion,  we  use  the  setuid
	application 'chfn' as an attack vector. We used  the  "Fenris"  utility,
	available  at  http://razor.bindview.com/tools/fenris,  to  analyze  the
	issue.
	

	The purpose of the 'chfn' utility is to allow users to  modify  personal
	information stored within the system-wide  password  file,  /etc/passwd.
	In order to modify this file, this application must be installed  setuid
	root. It is a part of the base installation of most Linux systems.
	

	Under certain commonly met  conditions  (see  "Mitigating  Factors"),  a
	carefully crafted attack sequence can be performed to exploit a  complex
	file locking and modification race present in this utility,  and,  as  a
	result, alter /etc/passwd to escalate privileges in the system.
	

	Password file locking and modification code can be found  in  util-linux
	within the login-utils/setpwnam.c file. The general logic used for  this
	process is as follows:
	

	 1. /etc/ptmptmp file is opened with O_WRONLY|O_CREAT, 0644 perms

	 2. the file is linked to /etc/ptmp, exit on failure

	 3. /etc/ptmptmp is removed

	

	Later, the descriptor  obtained  in  step  1  is  used  for  writing  to
	construct the new /etc/passwd contents. This is done line  by  line,  by
	calling the fputs() routine. When the new  file  is  ready,  three  more
	steps are taken:
	

	 4. /etc/passwd.OLD is removed

	 5. /etc/passwd is linked to /etc/passwd.OLD

	 6. /etc/ptmp is renamed to /etc/passwd

	

	At first sight, this logic is not flawed. There is no O_EXCL in step  1,
	but step 2  works  as  an  accurate  exclusive  locking  mechanism,  and
	prevents other instances of this  application  from  making  any  writes
	until the procedure is completed (step 6). Also, step 3 ensures that  no
	process will work on the hardlink of /etc/passwd after the procedure  is
	completed. This, while is not a standard way of  handling  file  locking
	on Linux, is an adequate protection against race  conditions.  The  only
	concern is that if chfn, chsh, or any other process that uses  /etc/ptmp
	is terminated abnormally (e.g. never exits because of a system crash  or
	is killed with SIGKILL), the lock has to be removed  manually  in  order
	for applications that rely on this signaling method  to  work  properly.
	This includes utilities such as chsh, chfn, useradd,  userdel,  adduser,
	vipw and other software. Most of seasoned Linux administrators have  had
	to remove this lock file at least once, and many  utilities  offer  help
	troubleshooting this issue - for example, adduser refuses  to  run  when
	/etc/ptmp is present and terminates with the following message:
	

	# adduser

	vipw lockfile (/etc/ptmp) is present!

	

	This is exactly what causes the  problem.  Because  this  dangling  lock
	file makes it impossible to perform regular user  management  tasks,  it
	is trivial to force the administrator to remove it. This is  a  risk  if
	chfn is stopped (by SIGSTOP) between steps 2 and 3 - which can be  done.
	There is a very small window of opportunity here, but it is possible  to
	fit into it. Later, the application can be resumed with SIGCONT.
	

	Note that there is no easy way to diagnose  who  created  /etc/ptmp  and
	for what reason they created it. The file is owned by root  and  has  no
	additional information whatsoever. The attack relies  on  small  windows
	of  opportunity,  making  one-shot  attempts  not  feasible  on  certain
	systems, it is reasonable to expect  that  if  the  attacker  repeatedly
	runs chfn or chsh just to create dangling lock  files  several  times  a
	day - and to make user management and maintenance tasks repeatedly  fail
	because of this lockfile - the administrator will most  likely  add  "rm
	-f /etc/ptmp" or equivalent to his  crontab,  instead  of  putting  much
	more effort into tracing the problem that  cannot  be  easily  diagnosed
	using standard Linux tools - there  is  no  log  entry  associated  with
	successful authentication for chfn service, ownership  of  the  file  is
	set to root, the file is empty.
	

	As  soon  as  this   or   a   similar   counter-measure   is   deployed,
	trial-and-error  attacks  become  much  simpler.  None  of   the   above
	administrative tasks is to be viewed as  requiring  social  engineering.
	Such tasks are a part of standard  system  maintenance  and  operations,
	and it is safe to assume that  almost  all  system  administrators  will
	follow this or a similar procedure to address the problem.
	

	Once the attacker has a stopped chfn process in between steps 2  and  3,
	and the /etc/ptmp file has been removed, he can easily  execute  another
	instance of chfn. It will open the already existing  /etc/ptmptmp  (this
	file is not considered a lock by any software, and most likely won't  be
	deleted). The second instance runs and step 2 succeeds because there  is
	no lock file present. The file is then written.  As  soon  as  /etc/ptmp
	has the  projected  size  (or  /etc/passwd.OLD  appears),  the  instance
	should be killed to avoid renaming to  /etc/passwd  (this  is  a  timing
	concern for this attack, and we will address it later).
	

	The First instance of chfn  is  still  holding  an  open  descriptor  to
	/etc/ptmptmp, which later became /etc/ptmp - and, if we send SIGCONT  to
	this process, will be renamed to /etc/passwd. Step 3 will  fall  through
	because there is no error checking, and new information will be  written
	into a descriptor that will de facto become /etc/passwd.
	

	Copying the file should go smoothly  up  to  the  point  where  attacker
	account information is stored. Let's analyze what  is  going  to  happen
	past this  point  if  the  second  instance  of  chfn  was  called  with
	significantly longer GECOS parameters than first (minimum  GECOS  length
	set by chfn is zero, maximum is  260  characters).  Keep  in  mind  that
	there is no call to ftruncate() before fclose(), so the new  file  would
	have a "tail" consisting of the last characters of  the  longer  version
	of /etc/passwd. If 'Write 1' is the first write to file,  or  the  write
	made by the second instance of  chfn,  'Write  1  is  the  second  write
	(first instance), and  '|'  represents  a  record  separator  (newline),
	then:
	

	Write 1: user 1 | user 2 | ATTACKER LONG | user 3 | user 4 |

	Write 2: user 1 | user 2 | ATTACKER | user 3 | user 4 |

	------------------------------------------------------------

	Result:  user 1 | user 2 | ATTACKER | user 3 | user 4 |r 4 |

	

	As a result, /etc/passwd has now a new line at  the  end,  a  "leftover"
	part from last username. This, itself, can be considered  an  annoyance,
	but is hardly a full-blown security issue. There is, however,  something
	that makes this problem more serious - that is, GNU libc.
	

	As  we  mentioned  earlier,  records  are  being  written   to   wannabe
	/etc/passwd line by line, using fputs().  It  is  important  to  realize
	that the text-file functions such as fputs() are  buffered  -  that  is,
	lines are being actually written to disk in chunks, when internal  cache
	is full. Until this point, the OS's idea of the file and  the  process's
	idea of the file are not synchronized. Cache block size, by default,  is
	four kilobytes. This means that if the password file has more than  four
	kilobytes (which is very often true on systems where local  attacks  are
	performed, see "Mitigating Factors"), and the attacker's account is  not
	in the last cached that is going  to  be  written  before  fclose(),  it
	should be possible to "cut" the  file  on  four  kilobytes  boundary  by
	killing the first chfn process as soon as /etc/passwd  has  the  desired
	contents:
	

	 <--- attacker's account  4 kB boundary

	 |

	Write 1: ...ser1:x:640:640:Foo Bar:/bin/bash\nuser2:x:641:641:Foo...

	Write 2: ...user1:x:640:640:Foo Bar:/bin/bash\nuser2: ***CUT***

	--------------------------------------------------------------------

	Result:  ...\nuser2::641:641:Foo...

	

	Note that both the data written in "Write 1" and in  "Write  2"  can  be
	padded within a 260 byte range, which is  typically  much  more  than  a
	typical /etc/passwd line length, so it is possible  to  pad  the  record
	exactly in the  way  described  above  -  which  happens  to  erase  the
	password for user2.  All  it  takes  is  to  determine  the  appropriate
	initial padding, then invoke the first chfn with one more  character  in
	GECOS than in the second chfn.
	

	It is also possible to create root accounts or other  privileged  access
	accounts that effectively lead  to  privilege  escalation  (kmem,  mail,
	etc). A root account with no password can be  generated  in  two  steps,
	first truncate the UID ending with  '0'  to  the  last  digit,  and  the
	second step is to remove the password  as  described  above.  Note  that
	even if there is no account with UID  0  anywhere  close  to  the  4  kB
	boundary, data can  be  moved  over  much  longer  distance  by  several
	subsequent attack cycles where the first instance  is  called  with  the
	maximum length GECOS field, and the second with minimum.
	

	The question of timing required for trimming is essential in  evaluating
	the feasibility of this attack -  but,  fortunately  for  the  attacker,
	timing  is  on  his  side.  First  of  all,  the  copying  procedure  is
	essentially pretty slow,  being  done  in  a  read-compare-write  cycle.
	Then, the results are cached so the output  appears  in  larger  chunks,
	but less frequently, giving the attacker plenty of CPU cycles.  Finally,
	the  passwd  file  is  world-readable,  which  gives  the   attacker   a
	tremendous advantage - it can be easily  monitored  e.g.  by  mapping  a
	single byte at  the  boundary  to  the  memory  space  of  the  exploit.
	Additionally, for files that consist of more than one segment  past  the
	attacker's account, the  problem  of  careful  timing  needed  to  avoid
	renaming /etc/ptmp to /etc/passwd becomes less of an issue, because  the
	process can be killed before reaching the end of the  file,  just  after
	the desired boundary is modified.
	

	Mitigating factors:

	

	In  order  to  successfully  exploit  the  vulnerability   and   perform
	privilege escalation, the following additional requirements have  to  be
	met:
	

	 1. There is a need for a minimal administrator interaction that falls

	 into the category of standard system maintenance and operations.

	

	 2. The password file, /etc/passwd, must be over 4 kilobytes. This

	 requirement is typically satisfied on systems with approximately

	 50 accounts or more - that is, on a vast majority of systems that

	 where unprivileged accounts are being compromised / used by

	 malicious attackers.

	

	 3. Assume that /etc/passwd is divided into 4 kB chunks. The

	 attacker's account record in this file must NOT be located in the

	 last chunk. That is, if /etc/passwd has 9 kB and we have three

	 chunks, 4 kB, 4 kB and 1 kB, and attacker's account must be

	 located within the first or second chunk and cannot be located

	 further than 8th kilobyte of the file.

	

	Other  mitigating  factors  are  similar  to  ones   for   other   local
	vulnerabilities in setuid binaries.

SOLUTION

	 Workaround / fix :

	 ================

	

	The fast workaround is to remove setuid  flags  from  /usr/bin/chfn  and
	/usr/bin/chsh. A more  appropriate  fix  is  to  patch  the  sources  as
	follows:
	

	--- util-linux-2.11n-old/login-utils/setpwnam.c Mon Jul 31 08:50:39 2000

	+++ util-linux-2.11n/login-utils/setpwnam.c     Wed Jun 12 21:37:12 2002

	@@ -98,7 +98,8 @@

	     /* sanity check */

	     for (x = 0; x < 3; x++) {

	        if (x > 0) sleep(1);

	-       fd = open(PTMPTMP_FILE, O_WRONLY|O_CREAT, 0644);

	+        // Never share the temporary file.

	+       fd = open(PTMPTMP_FILE, O_WRONLY|O_CREAT|O_EXCL, 0644);

	        if (fd == -1) {

	            umask(oldumask);

	            return -1;

	

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH