A little bit of background for those not familiar with chfn…
“chfn (change finger) is used to change your finger information. This information is stored in the /etc/passwd file and is displayed by the finger program. The Linux finger command will display four pieces of information that can be changed by chfn; your real name, your work room and phone, and your home phone.” (from https://man7.org/linux/man-pages/man1/chfn.1.html)
Now for some history. Two years ago, I picked out chfn as a candidate to be reviewed for security bugs. Why chfn I hear you ask? (Thanks for asking.) It is one of a small number of Set owner User ID (SUID) programs loaded with Linux which means it runs with the permissions of the ‘root’ user regardless of the user who executes it, for it needs to modify the /etc/passwd file to do its job. A vulnerability in such a program would mean local privilege escalation, for any command or action we get to inject gets executed in the context of ‘root’. As a normal user, we wouldn’t be able to directly save any changes made to /etc/passwd, but via chfn we can, in a controlled and restricted way – well that’s the plan.
We can change our room number to be “41414141” by either using chfn interactively or using it from the command line with parameters and values. For example: chfn -r 41414141
After executing this command, if we then look at /etc/passwd it will show the following:
We could also change our full name (-f), home phone (-h) and work phone (-w) with other parameters which would change other details within those comma separators, which are referred to as General Electric Computer Operating System (GECOS) fields.
Keep the above in mind while we look under the hood.
I found chfn is part of two Linux packages; util-linux and shadow. In Ubuntu and other Debian-based Linux implementations it is the shadow package which is responsible for the resultant chfn binary and is vulnerable to what I’m about to discuss.
Unwrapping this package and looking at /shadow-4.5/src/chfn.c you’ll find the following treasure inside the check_fields function, namely on lines 585 to 592 with regards to setting the room number field:
We can see that the user input to “roomno” is sent to the valid_field function together with some characters. We can assume that this is likely a blacklist of characters, but let’s go down the valid_field rabbit hole just to confirm.
Digging into /shadow-4.5/lib/fields.c, at the valid_field function, on lines 52 to 81:
So, our assumptions were correct. Each character we provide to “roomno” (or any other GECOS field) is compared against a list of illegal characters and if there is a match then -1 is returned back to “err” which exits out of the chfn program with the invalid room number message.
Two things stick out to me here. First, a bunch of things are not being mentioned in that illegal list of characters (blacklists are not great, who knew?!) and secondly, the handling of non-printable characters and the “1” being returned for them along with a non-exiting warning message – importantly, chfn does not quit. If we look back at the check_fields and valid_field functions, chfn only quits out if “err” is less than 0 - we die, we do not collect $200 and we go directly to jail. But if “err” is greater than 0 we get that red rope unclipped from the silver pole at the front of the chfn club and walked straight on through to our table. Good times.
With this knowledge, I pulled ‘echo’ (with -e) out of the toolbox and started to feed control characters into the roomno (-r) parameter of chfn to see what was going down because these don’t feature in that blacklist. I had lots of fun (I’m really great at parties), but the main takeaway is that the control character CARRIAGE RETURN represented by \r or \xD (in hexadecimal) gave the biggest impact. Let me show you the bottom of /etc/passwd before any madness goes on. The ubuntu user is normal, all good - nothing to see here.
Figure 1: Contents of /etc/password before injection
However, we make the injection with the \r control character into the roomno parameter of our user (ubuntu) and then we view the /etc/passwd file again. WHAAAAAA? Where we go?! roominjection has entered the building!
Figure 2: Modified contents of /etc/password after injection
Fast forward some hours and minutes later and spoiler alert here - I was not able to circumvent the “\n” from the illegal list. The ultimate goal/dream here would have been to start a new line and add another root level user, but I was denied.
I thought, well that was cool. I was curious though, was this blacklist always there? Back to the future we go - 2006 to be exact, to shadow-4.0.13 and guess what? …the “\n” in the blacklist was missing! However, the “:” was still present which stops any new user adding fun as we need to make use of this delimiter. This also applied to full name, home and work phone number, etc. but for illustrative purposes I have just shown the roomno as an example, which is what we’ve been targeting throughout.
chfn.c (in Ubuntu) from back in the day in 2006 (shadow-4.0.13) with “\n” missing:
I went back a further two years to 2004 (shadow-4.0.3) and that “:” is still messing up our plans for adding new users:
It was at this point of digging that I came across this bad boy from 2005 in SuSE Linux; CVE-2005-3503, a local privilege escalation in chfn! (https://www.exploit-db.com/exploits/1299). The exploit happily uses both “\n” and “:” to add a new root user, exactly what I set out to do at the start of this adventure.
I left the research there, poured myself another cup of tea and got on with my life.
Two years went by and one morning while I was eating my toast, I saw a tweet from @trailofbits about exploiting a logic bug in a SUID program. Interesting, tell me more… and they do, with a blog post. (https://blog.trailofbits.com/2023/02/16/suid-logic-bug-linux-readline/)
It’s about chfn of course! This time chfn is loading (and loading badly I might add) an environment variable, resulting in an arbitrary read access bug. This blog post gave me the kick I needed to revisit my old friend chfn.
Two years older/wiser, I picked up my old research in chfn to try a few more things. Still no success in weaponising it (e.g., the holy grail of adding a new user, 2005 SuSE style) but I stand back and try to think about how it can be abused in other ways. In a way it isn’t really an issue, but in another it is a little bit concerning, concerning that I’m able to mangle the /etc/passwd file and misrepresent it to an administrator perhaps. This made me think of red teaming and how we put together pretexts in our phishing tests to convince victims to click on links or do things for us. If I can’t use this to add a new user, can I use it to fake adding a new user? Stay with me on this.
The majority of work I’ve done over the last two decades has been application security testing, the kind of testing where we need to encode all the things, double encode, find alternative representations of characters, etc. in order to circumvent input validation rules, evade WAFs and ultimately get that alert box with the number 1 popping up. This got me thinking. I can’t put in an actual colon due to that blacklist in chfn, but we got a free pass with non-printables. We can use the CARRIAGE RETURN control character “\r” to start the line again and then be a bit creative and use the Unicode character “\ua789” to get as close a match to a colon as possible, all while injecting into the roomno parameter.
And that’s exactly what I did next.
Spot the difference?
I wanted to support such a pretext where we’ve managed to compromise a local account on the machine, but that we want to pretend to the administrator that we actually have a root privileged user account, so we tell them to check the /etc/passwd file and see for themselves. Maybe this being believed would then invoke another action like them having to log into another box/invoke an IR process which we have other tools waiting for this – Responder, Mimikatz, etc. to grab hashes, tokens, etc. something like that.
Attacker making the injection:
Administrator does a ‘cat /etc/passwd’:
Here are some screenshots also so you can get a sense of believability of the colon replacement on the screen.
The /etc/passwd before:
Figure 3: /etc/passwd before injection
The injection with the subsequent “hacked” account at the bottom of /etc/passwd, with ‘root’ privileges:
Figure 4: /etc/passwd after injection of the ‘hacked’ root user
That brings us to the end of our story, well nearly.
Remember at the start where I said chfn can be found in util-linux and shadow packages? Well strangely enough, the util-linux version blocks control characters via “iscntrl” in ch-common.c. In addition, it adds the “ character into the blacklist, so perhaps there is a story and some research behind that one too?
From util-linux-2.37.2/login-utils/ch-common.c (used by chfn.c), lines 17 to 34:
Prior to this blog post being published, I got in touch with the shadow package maintainers and showed them the proof of concept. I then followed up with a pull request on GitHub with the following commit which they have now merged into the main shadow repository.
Now, if control characters are detected then -1 is returned to err (quitting out of chfn), treating them the same as the illegal characters. This little bug has been assigned CVE-2023-29383.
Thanks for reading!
Reference
TWSL2023-004: Improper input validation in shadow-utils package utility chfn
A little bit of background for those not familiar with chfn…
“chfn (change finger) is used to change your finger information. This information is stored in the /etc/passwd file and is displayed by the finger program. The Linux finger command will display four pieces of information that can be changed by chfn; your real name, your work room and phone, and your home phone.” (from https://man7.org/linux/man-pages/man1/chfn.1.html)
Now for some history. Two years ago, I picked out chfn as a candidate to be reviewed for security bugs. Why chfn I hear you ask? (Thanks for asking.) It is one of a small number of Set owner User ID (SUID) programs loaded with Linux which means it runs with the permissions of the ‘root’ user regardless of the user who executes it, for it needs to modify the /etc/passwd file to do its job. A vulnerability in such a program would mean local privilege escalation, for any command or action we get to inject gets executed in the context of ‘root’. As a normal user, we wouldn’t be able to directly save any changes made to /etc/passwd, but via chfn we can, in a controlled and restricted way – well that’s the plan.
We can change our room number to be “41414141” by either using chfn interactively or using it from the command line with parameters and values. For example: chfn -r 41414141
After executing this command, if we then look at /etc/passwd it will show the following:
We could also change our full name (-f), home phone (-h) and work phone (-w) with other parameters which would change other details within those comma separators, which are referred to as General Electric Computer Operating System (GECOS) fields.
Keep the above in mind while we look under the hood.
I found chfn is part of two Linux packages; util-linux and shadow. In Ubuntu and other Debian-based Linux implementations it is the shadow package which is responsible for the resultant chfn binary and is vulnerable to what I’m about to discuss.
Unwrapping this package and looking at /shadow-4.5/src/chfn.c you’ll find the following treasure inside the check_fields function, namely on lines 585 to 592 with regards to setting the room number field:
We can see that the user input to “roomno” is sent to the valid_field function together with some characters. We can assume that this is likely a blacklist of characters, but let’s go down the valid_field rabbit hole just to confirm.
Digging into /shadow-4.5/lib/fields.c, at the valid_field function, on lines 52 to 81:
So, our assumptions were correct. Each character we provide to “roomno” (or any other GECOS field) is compared against a list of illegal characters and if there is a match then -1 is returned back to “err” which exits out of the chfn program with the invalid room number message.
Two things stick out to me here. First, a bunch of things are not being mentioned in that illegal list of characters (blacklists are not great, who knew?!) and secondly, the handling of non-printable characters and the “1” being returned for them along with a non-exiting warning message – importantly, chfn does not quit. If we look back at the check_fields and valid_field functions, chfn only quits out if “err” is less than 0 - we die, we do not collect $200 and we go directly to jail. But if “err” is greater than 0 we get that red rope unclipped from the silver pole at the front of the chfn club and walked straight on through to our table. Good times.
With this knowledge, I pulled ‘echo’ (with -e) out of the toolbox and started to feed control characters into the roomno (-r) parameter of chfn to see what was going down because these don’t feature in that blacklist. I had lots of fun (I’m really great at parties), but the main takeaway is that the control character CARRIAGE RETURN represented by \r or \xD (in hexadecimal) gave the biggest impact. Let me show you the bottom of /etc/passwd before any madness goes on. The ubuntu user is normal, all good - nothing to see here.
Figure 1: Contents of /etc/password before injection
However, we make the injection with the \r control character into the roomno parameter of our user (ubuntu) and then we view the /etc/passwd file again. WHAAAAAA? Where we go?! roominjection has entered the building!
Figure 2: Modified contents of /etc/password after injection
Fast forward some hours and minutes later and spoiler alert here - I was not able to circumvent the “\n” from the illegal list. The ultimate goal/dream here would have been to start a new line and add another root level user, but I was denied.
I thought, well that was cool. I was curious though, was this blacklist always there? Back to the future we go - 2006 to be exact, to shadow-4.0.13 and guess what? …the “\n” in the blacklist was missing! However, the “:” was still present which stops any new user adding fun as we need to make use of this delimiter. This also applied to full name, home and work phone number, etc. but for illustrative purposes I have just shown the roomno as an example, which is what we’ve been targeting throughout.
chfn.c (in Ubuntu) from back in the day in 2006 (shadow-4.0.13) with “\n” missing:
I went back a further two years to 2004 (shadow-4.0.3) and that “:” is still messing up our plans for adding new users:
It was at this point of digging that I came across this bad boy from 2005 in SuSE Linux; CVE-2005-3503, a local privilege escalation in chfn! (https://www.exploit-db.com/exploits/1299). The exploit happily uses both “\n” and “:” to add a new root user, exactly what I set out to do at the start of this adventure.
I left the research there, poured myself another cup of tea and got on with my life.
Two years went by and one morning while I was eating my toast, I saw a tweet from @trailofbits about exploiting a logic bug in a SUID program. Interesting, tell me more… and they do, with a blog post. (https://blog.trailofbits.com/2023/02/16/suid-logic-bug-linux-readline/)
It’s about chfn of course! This time chfn is loading (and loading badly I might add) an environment variable, resulting in an arbitrary read access bug. This blog post gave me the kick I needed to revisit my old friend chfn.
Two years older/wiser, I picked up my old research in chfn to try a few more things. Still no success in weaponising it (e.g., the holy grail of adding a new user, 2005 SuSE style) but I stand back and try to think about how it can be abused in other ways. In a way it isn’t really an issue, but in another it is a little bit concerning, concerning that I’m able to mangle the /etc/passwd file and misrepresent it to an administrator perhaps. This made me think of red teaming and how we put together pretexts in our phishing tests to convince victims to click on links or do things for us. If I can’t use this to add a new user, can I use it to fake adding a new user? Stay with me on this.
The majority of work I’ve done over the last two decades has been application security testing, the kind of testing where we need to encode all the things, double encode, find alternative representations of characters, etc. in order to circumvent input validation rules, evade WAFs and ultimately get that alert box with the number 1 popping up. This got me thinking. I can’t put in an actual colon due to that blacklist in chfn, but we got a free pass with non-printables. We can use the CARRIAGE RETURN control character “\r” to start the line again and then be a bit creative and use the Unicode character “\ua789” to get as close a match to a colon as possible, all while injecting into the roomno parameter.
And that’s exactly what I did next.
Spot the difference?
I wanted to support such a pretext where we’ve managed to compromise a local account on the machine, but that we want to pretend to the administrator that we actually have a root privileged user account, so we tell them to check the /etc/passwd file and see for themselves. Maybe this being believed would then invoke another action like them having to log into another box/invoke an IR process which we have other tools waiting for this – Responder, Mimikatz, etc. to grab hashes, tokens, etc. something like that.
Attacker making the injection:
Administrator does a ‘cat /etc/passwd’:
Here are some screenshots also so you can get a sense of believability of the colon replacement on the screen.
The /etc/passwd before:
Figure 3: /etc/passwd before injection
The injection with the subsequent “hacked” account at the bottom of /etc/passwd, with ‘root’ privileges:
Figure 4: /etc/passwd after injection of the ‘hacked’ root user
That brings us to the end of our story, well nearly.
Remember at the start where I said chfn can be found in util-linux and shadow packages? Well strangely enough, the util-linux version blocks control characters via “iscntrl” in ch-common.c. In addition, it adds the “ character into the blacklist, so perhaps there is a story and some research behind that one too?
From util-linux-2.37.2/login-utils/ch-common.c (used by chfn.c), lines 17 to 34:
Prior to this blog post being published, I got in touch with the shadow package maintainers and showed them the proof of concept. I then followed up with a pull request on GitHub with the following commit which they have now merged into the main shadow repository.
Now, if control characters are detected then -1 is returned to err (quitting out of chfn), treating them the same as the illegal characters. This little bug has been assigned CVE-2023-29383.
Thanks for reading!
Reference
TWSL2023-004: Improper input validation in shadow-utils package utility chfn