Hacker News new | past | comments | ask | show | jobs | submit login
Unix shells are generally not viable access control mechanisms any more (utcc.utoronto.ca)
174 points by ingve on Sept 18, 2023 | hide | past | favorite | 141 comments



This article could have been published 30 years ago. In professional unix admin circles this was already well known back them. Although I could be misreading it as the article is not very clear. I think this are the points it is trying to make:

1. Once upon a time you could rely on the passwd file and shell behavior as an effective means of authentication and access control.

2. It has been a very long time since that was an effective approach, for a variety of reasons, and you should not do this on modern production systems.


Even 30 years ago, the core argument would have been nonsensical.

1. They introduce their argument as if it is solely about shell access (the conclusion also only mentions "login access control"), but then the first example/statement they make is about non-shell access (Samba, IMAP, Apache).

2. The second argument conflates authentication and authorization, and concludes that to implement shell authorization properly, your only choice is to provide multiple authentication systems.

Zero effort is spent on explaining why existing/historic shell authorization systems (such as simple DAC groups or rbash) are inadequate, and it's not clear to me what threat model they are using to arrive at their conclusion.

edit: rethinking this, I think TFA is just lacking a clear problem statement. They seem to be talking specifically about non-shell services that (ab)use the user's shell field in /etc/passwd as authorization information, and then complaining that many services did not follow suit.


Few contractions foment confusion as much as “auth”. Don’t do it.


authn vs authz: Authentication vs Authorization

authn/authentication: user proves who they are, with username/password or otherwise

authz/authorization: based on who the user is, system determines what they are allowed to do, via group membership or otherwise


authz may be confusing to non USA English speakers. I wouldn't make the connection without it spelled out to me. Unfortunately I don't have a better suggestion because auths as short for authorisation is probably worse.


If you work with computers (rather than using them) and don't default to USA English when discussing and using them you are likely in for a bad time.


I think it is less confusion than just calling it auth. I have read many articles about basic auth vs oauth. But the auth here isn't the same.


You can't pronounce authn and authz very well, but to be perfectly honest I'm not sure if that falls under the 'pro' or 'con' column.


I think it's a pro. in saying auth-enn and auth-zee (zed), it's clear which of the two you're talking about.


To me they look like the kind of abbreviations I'd only do when writing. I just say authentication or authorisation when reading them (out loud or in my own mind)


TBH, we'd be better if without any of the contracted forms.


the only exception is if you mean both, but even that's confusing if the context isn't clear.

spell them out or use authn/authz.


You're not thinking like a thought leader.


This blog of generally gibberish hits the HN front page with an astounding frequency. IMHO, there are many interesting blogs on "system administration" topics that are submitted to HN every week that never reach the front page while there are a handful of familiar, low-quality ones that routinely appear on page one.


Much of tech is a theatre, a jobs program that keeps people employed in a middle class salary so long as they diligently pretend to be engineers. This theatre serves as a prop for a higher level theatre in our virtual economy for investors and their game of financialization.

Its expected that as tech grows in number of workers clutching to that middle-class life-raft that the baseline of knowledge discussed in tech spheres (like this site) will sink lower.


Is it September already?


it has been for 30 years


I think the point being made is that the fact that a user has rksh as their shell means nothing to samba, ftp, some features of ssh, httpd, cron, and etc. Fundamentally unix has pretty simple permissions, you're either root or you're not. The existence of a user account on a system is often enough to enable SMB and SSH access even if the only purpose of the account is to own files and an application process and is never intended to have interactive logins or to transfer data to and from the server.


> I think the point being made is that the fact that a user has rksh as their shell means nothing to samba, ftp, some features of ssh, httpd, cron, and etc

Which has been true for...... 30 years? If not longer?


Would be possible to share those reasons?


Here are some:

* Doesn't scale. Having passwords in a plain text file is not a scalable solution for users directory. Can probably go up to a hundred users, but not much more.

* In computer clusters you want user identity to "stick" to the user when they use multiple machines, containers etc. That's why you have LDAP... but it doesn't help all that much because user id is encoded into the file system (huge mistake...) which makes it very difficult to contain users to things they should control. If your only mechanism was the /etc/passwd, it would mean you'd have to constantly synchronize this file across all those machines and containers you have.


It may be old and not particularly appealing, but LDAP has been serving that role effectively at many companies.[0]

Using a terminal remains standard practice for sysadmins and devops.[1]

I believe there's some confusion in both the article and the comment between authentication and authorization. LDAP is fully equipped to handle both tasks.

[0]https://access.redhat.com/documentation/en-us/red_hat_enterp...

[1]https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-co...


To anyone reading this and thinking "yeah dummy, of course it doesn't scale because you're not supposed to store passwords in plain text in the first place" I'll direct you to Chapter 7ish of The Linux Programming Interface.

If you look in your /etc/passwd right now, you'll almost certainly see a single "x" where the (EDIT: no, it was still encrypted!) password originally was - nowadays that single "x" is an instruction to go look in /etc/shadow instead, for the salted hash of the password you're trying to check.

I think this minimizes the number of users who need read permissions to /etc/shadow, and the amount of time they need it for.

This has been your seemingly useless bit of Linux trivia for today. :)


/etc/shadow was born not because /etc/passwd had a plain text password but because the hashes became crackable and /etc/passwd is a public read file. Linux has never had them. Here's the man page indicating encrypted passwords for Unix v7 /etc/passwd release in 1979: https://man.cat-v.org/unix_7th/5/passwd


Whoops! My bad, this is an even better bit of trivia.

My mistaken memory really sells the underlying point that everything old is new again.


I have a vague recollection of my 20 floppy of Slackware already having /etc/shadow. That would have been fall of 92 or winter 93, based on where I was living at the time.


I have a vague recollection of being given the choice on a 90s vintage distribution with some warning about security and password length if I did not use shadow passwords. At some point in the early 2000s we started authenticating regular users against AD but the shadow file was still there for root.


We had a couple of labs of sparcstations that just went away a couple of times a year because something bad would happen with all of the NFS mounted partitions and they'd have to turn the cluster on one box at a time to prevent thundering herd issues with NFS.

I think they may have been mounting parts of /etc as well. People get the idea that managing accounts for a cluster of boxes should be centralized. It's all fun and games until the network mount disappears.


Shadow file was definitely from quite a while ago.

Can't remember exact date, but might have been around time of SVR4 intro.

I know because I remember going "ugh", but without investigating the reason why it (shadow) was introduced :) - which was of course wrong on my part.


That was not a "plaintext password," it was a DES hash (from 7th edition onwards).

This is the same format used by the classic htpasswd utility.

https://en.wikipedia.org/wiki/Crypt_(C)#Traditional_DES-base...


plaintext vs plain text

unencrypted vs unstructured

Of course, unstructured is also incorrect; the passwd and shadow files have structured records, one per line.


…and being structured, the passwd file content should be accessed with the getpwent family of functions.


Which unfortunately are not thread safe.


"unencrypted" is normally written as "cleartext". "plaintext" means "(readable / intended to be read) without a special viewer". Your.ssh /id_rsa is plaintext but not cleartext.


My school had 30k students, including grad and doctoral students. When they gave students shell accounts, they tried to put them all onto a single Sequent box. They got my incoming class, the following, and anyone previous who asked for one onto that box. I’m pretty sure it had an /etc/password file, and would have had about 8-10k people on it.

After that they gave up. Even with aggressive ulimits it was too hard, and each new class was apportioned to a separate Sparc. Which was a shame because we learned an awful lot about Unix administration from users pranking each other, figuring out what they did and how they did it, protecting yourself and retaliating (some people would prank others without first protecting themselves).

Made it a lot harder chatting with people from other classes as well. For electives and humanities you weren’t always in classes with people your exact age. They could be ahead of you or behind.


> Doesn't scale. Having passwords in a plain text file is not a scalable solution for users directory. Can probably go up to a hundred users, but not much more.

This simply cannot be true. A users directory that is frequently read will be in cache. The file is also fairly simple to parse. Even on old hardware you should be able to parse thousands of users.


It's more of a problem of the number of nodes your users will be accessing than the number of users. It's a PITA to make mechanisms for password changes, for adding and removing users, for unlocking accounts, etc. when you have a distinct file on each server that needs to be changed. As well as adding new nodes to the environment and bringing them up to date on the list of users and etc.


Fair point, networking always makes things more complicated. I'm not sure it's really that much more complicated though. Like any concurrency problem with shared state, a single-writer and multiple readers keeps things simple, eg. a single host is authoritative and any changes are made there, and any file changes trigger automated scripts that distribute the changed file over NFS, FTP, etc.

As long as new nodes start with the same base configuration/image this seems manageable. Simple to do with inotify, but even prior to that some simple C programs to broadcast that a change is available and listen for a change broadcast and pull the changed file is totally within the realm of a classic sysadmin's skillset.


You would have to lock the file, or guarantee consistency in some other way. Right now, I don't believe Linux does anything about consistency of reads / writes to that file... which is bad, but we pretend not to notice.

So... the system is kind of broken to begin with, and it's kind of pointless to try to assess its performance.

Also, it would obviously make a lot of difference if you had a hundred of users with only a handful being active users, or if you had a hundred of active users. I meant active users. Running programs all the time.

NB. You might have heard about this language called Python. Upon starting the interpreter it reads /etc/passwd (because it needs to populate some "static" data in os module). Bet a bunch of similar tools do the same thing. If you have a bunch of users all running Python scripts while there are some changes to the user directory... things are going to get interesting.


> You would have to lock the file, or guarantee consistency in some other way.

I think the standard approach to atomicity is to copy, change the copy, then move that copy overwriting the original (edit: file moves are sorta atomic). Not perfect but generally works.

I agree that this approach is not good for a users directory, I'm just disagreeing that the reason it's not good is performance-related.


Moves are atomic. During the move, at no time is it possible to get the contents of file 1 and file 2 confused when reading from the file descriptors. (Confusion by the human operating things is eminently possible.)


Most systems come with "vipw" which does the atomic-rename dance to avoid problems with /etc/password. In practice this works fine. Things get more complicated when you have alternate PAM arrangements.

A whole bunch of standard functions like getpwents() are defined to read /etc/password, so that can't be changed.


`getpwents()` is not defined to only read `/etc/passwd`. There is only a requirement that there is some abstract "user database" or "password database" (depending on if you're reading the linux man pages or the Single Unix Specification man pages).

In practice, `getpwent` on linux uses the nsswitch mechanism to provide return values from `getpwent`. One can probably disable using `/etc/passwd` entirely when using glibc if: all users do use `getpwent`, and you remove `files` from the `passwd` entry in `/etc/nsswitch.conf`.


I had >30k users on a bunch of systems in 2001 (I inherited that approach, mind, I'm not -recommending- it).

We moved to LDAP a couple years later because it was a much nicer architecture to deal with overall, but performance and consistency weren't problems in practice.


Exactly, even large textfile based DNS servers have capability to "compile" the textfile to a db file for faster access.


So what if they do?

This file is a public interface exposed by Linux to other programs. So what if Linux caches its contents when eg. Python interpreter on launch will read this file. And it's not coming from some "fake" filesystem like procfs or sysfs. It's an actual physical file most of the time.


the python interpreter reads /etc/passwd on launch? i guess it's looking for home directory


> user id is encoded into the file system

This is kind of unavoidable, but you do have 32 bits to play with. Windows did it slightly better with the SID: https://learn.microsoft.com/en-us/windows-server/identity/ad...

> which makes it very difficult to contain users to things they should control

It's not the file system that's the problem here, it's that "everything is a file" is not true for a whole bunch of important stuff that you might want to apply access control to on a UNIX system. Such as the right to initiate TCP connections. This sort of thing is why containers are so popular.

NIS and LDAP do let you have a large number of users. Heck, we managed a few thousand users in /etc/password back when I was running https://www.srcf.net/ .. in 2000.


> it's not the file system that's the problem here, it's that "everything is a file" is not true for a whole bunch of important stuff that you might want to apply access control to on a UNIX system

I wonder if there has ever been an attempt to really lean into, and push the limits of sticking with the "everything is a file" philosophy in this realm.

I.e. how far could you get with having special files for fine grained permissions like "right to initiate a TCP connection", and making access control management be, essentially, managing which groups a user belonged to?


Plan 9 probably took this the furthest. Sad it didn't take off. https://en.m.wikipedia.org/wiki/Plan_9_from_Bell_Labs


I think that was Plan 9.


I think Hurd and Plan 9 take the EIAF further.


Plan9 tried to "remedy this".

But in reality a file is not a good abstraction for an internet socket. The ACLs would in essence spell out firewall rules. Because the bigger question is where can it connect to than "user" that is connecting.

That's why this is done on the level of kernel networking, where kernel knows what process is trying to open a socket and can firewall it.


This sounds like a completely unrelated thing and you are not constrained by the plain text password/shadow file for scale. NIS existed for many decades. You can even use Active Directory (or samba) for authentication and user management.

But the article is not about this at all.


This shows you either didn't read what you replied to or don't understand the subject.

This file is the public interface of the Linux system to everyone who wants to get information about users on the system. It doesn't matter that alternative tools exist: they were already mentioned in the post you replied to. It's not the point...


If I didn't understand I'd be grateful if you could explain it.

As far as I know that file does not get referenced for the users in an external directory server. That's how the systems scale without needing to put the users in the file. Aren't we talking about a high number of users (and their authorization levels) when talking about scalibility in this case?


> This file is the public interface of the Linux system to everyone who wants to get information about users on the system

No it isn't. PAM is. The password file is only one of the places where users might be defined.


I've wondered why we don't have a passwd.d folder, the way we do with other things in the UNIX filesystem, with individual user accounts represented by individual files. Could even retain the same line-oriented format, just stored separately.


I think a directory of 10000 files is worse than a file of 10000 lines.


Maybe, but account synchronization across machines might be a lot simpler and easier.


> * Doesn't scale. Having passwords in a plain text file is not a scalable solution for users directory. Can probably go up to a hundred users, but not much more.

Why not? A file 100,000 line file will only take a moment to scan.


That's the first understandable explanation I've ever heard of what exactly LDAP is and what it's for!


If you have services with even a few users you should set up LDAP (openldap is fine) and then experiment with it.

LDAP is pretty old and very well supported overall. At one point I configured ldap for postfix virtual hosting. I chose LDAP rather than a database backed solution because of widespread first class support everywhere. The same directory later enabled me to use a ton of other things including SSO. You're always finding new ways to use it once you have it.

It's a great skill to have and is nowhere near as complicated as people make it out to be, including live replication.


Several are outlined in TFA


Tangent, I can't find what "TFA" stands for. My gut tells me it's "The Fucking Article". Am I correct?


Yes.

TFA = The Fucking Article.

Much like:

RTFM = Read The Fucking Manual.


As I said to my mother-in-law, the 'F' is silent.


When Gwen Shotwell was asked about the acronym BFR on international television, she replied "Big Falcon Rocket". Lovely response, quite dependent on context!


This is like how I use 'ofc' to abbreviate 'of course'. Once upon a time the 'f' may have stood for something, but I never use it that way. For me, 'the F is silent'.


I pretend its The Forementioned Article


I like to read it as The Featured Article


Yes, but in polite company you can pretend it means "The Freaking Article"


In polite company you say "The article" or "read the manual" and patiently await your reward, which is their asking "what does the 'F' stand for?", to which you reply only with a condescending look and raised eyebrow(s).

That look of realisation is precious.

I've manufactured this experience once or twice, and it's wonderful.


Fine instead of Freaking is much nicer.


I think "Friendly" was/is popular explanation where devs tried to translate their subculture into something generally digestible.


Digestible maybe, but swapping "friendly" in for "fucking" changes the tone and intent of someone's statement. At least "freaking" expresses similar, if muted, exclamation.

A recipient who is being not-so-subtly reproached with an F-bomb acronym might misunderstand what is being implied.


But when the person asking was management, and asking for the tenth time, there was a decided advantage to changing the tone.

All of that is now wrapped up in the initialism.


Or the fine article.


Yes


I really hate this acronym and this comment is no different from saying “read the article” which is against the guidelines here


> saying “read the article” which is against the guidelines here

The guidelines say that you should not accuse someone of not having read the article. However, as the guidelines say it is fine to point out that something is mentioned in the article.

There is a subtle difference.

From the guidelines:

> Please don't comment on whether someone read an article. "Did you even read the article? It mentions that" can be shortened to "The article mentions that".

Parent comment was in line with the guidelines IMO.

And as for “TFA” as an acronym I like to read it as meaning “The Featured Article”. Then it seems nice and friendly.


I'd assume any code in a non-memorysafe language that parses any freeform data entered by the user is a potentially exploitable security vulnerability, so an interactive shell is a huge surface area for attacks?


Yes, but irrelevant here. Basically any shell access means you've changed from preventing remote code execution to preventing privilege escalation, which is much harder.


Which is why sudo has its own editor.

If you fuck up the sudo file while saving it, you might no longer be able to log in to fix it. Before I knew about sudoedit I would open two shells as root, edit the file, then use a third window to make sure I could still sudo.

With two windows I could accidentally close the subshell in one without locking myself out. Think if it like linemen, who use two tethers for climbing structures. They are never detached from the safety lines.


  sudo visudo


Ask the chatbot:

1. system: in the context of setting up secure remote access to a Unix-like system, discuss whether relying on the passwd file and shell behavior as an effective means of authentication and access control is a good approach. What are some reasons this is not (or is) an effective approach, which should not (or should) be used on modern production systems. user: system administrator on a Unix-based network. assistant: technically, there are several reasons...

2. If you have a collection of Unix systems, can you reasonably do a certain amount of access control to your overall environment by forcing different logins to have specific administrative shells?


But when was it ever effective if 30 years ago it was already well known?


Last time it might've been effective was probably in old-school Unix time sharing with users connected via tty's rather than TCP/IP. Already early SQL databases, with the possible exception of Informix SE, had a client/server process model where the server process had full access to all data files and would at best authenticate sessions but not individual accesses against /etc/passwd such as via Oracle's pipe/bequeather connector but more commonly would assume fixed global roles and handle auth on the app side. As soon as IP and "services" were introduced, /etc/passwd stopped being effective, as pointed out by bluetomcat [1]. Actually, even gaining shell access is considered game over from a security PoV, due to multiple privilege escalations.

[1]: https://news.ycombinator.com/item?id=37462806


It was effective for a ftp server accessing public directories in the home of users. I can't remember the details but you would use the username and password of the user to exchange files with and get into that directory. All transmitted as cleartext, of course.

30+ years ago we already had services (daemons!) with their own user id, to keep them isolated from root and the human users. This post is as news as the invention of hot water.


> It was effective for a ftp server accessing public directories in the home of users. I can't remember the details ...

Most ftpd need a shell whitelisted in /etc/shells .

In macOS, /etc/shells begin with this comment:

  # List of acceptable shells for chpass(1).
  # Ftpd will not allow users to connect who are not using
  # one of these shells.


When you SSH into GitLab, you get a "shell," which at one point was a locked-down Unix shell where you could only run Git commands. That's a workable solution, but they (rightly) migrated off it [0], so now it's just a Go program [1] that runs an SSH server and interprets a small list of valid commands. This basically inverts the security model. The Unix shell approach is leaky, because you never know if you've plugged all the holes while only allowing the commands you want. Whereas the Go program is only capable of executing a narrow range of commands, which it interprets itself, in its own fake shell, while exposing an SSH server.

Fun fact: one of the commands [2] the shell implements is `personal_access_token`, so you can programmatically mint a new PAT, since you've already authenticated via your SSH key:

    ssh git@gitlab.com personal_access_token someTokenName api,read_repository,read_api,read_user,read_registry 90

[0] https://about.gitlab.com/blog/2022/08/17/why-we-have-impleme...

[1] https://gitlab.com/gitlab-org/gitlab-shell

[2] https://gitlab.com/gitlab-org/gitlab-shell/-/blob/main/inter...


How is that different from writing your own shell that implements a tiny subset of the functionality with support for a small number of commands? You can even write it in Go if that's your preferred language.


That's exactly what it is, but the point is that it's not a Unix shell, and it's not running as a subcommand of SSH (which you could also do with a custom shell). There's no tty involved, and so the attack surface is reduced - if you exploit a logic error in the Go code (e.g. parameter pollution), you won't be able to do more than run one of the commands that it already runs. But if you exploit a logic error in a Unix shell with parameter pollution, then the attack surface is much greater. That said, of course if an attacker finds a bug in the Go code that enables arbitrary code execution, then the attack surface is just as wide as any other binary on the system, including a Unix shell, so you still need to lock down the privileges of the process itself.


I think the point is: how is bundling a custom ssh server into the same executable that interprets the commands, a reduced surface as opposed to using standard openssh's sshd, and defining a simple shell like so:

  #!/bin/bash

  cmd_foo() {
    printf "called foo with %s\n" "$*"
  }

  cmd_bar() {
    printf "called bar with %s\n" "$*"
  }

  run_cmd_call() {
    local cmd="$1"; shift
    local cmd_args=("$@")

    case "$cmd" in
      foo) cmd_foo "${cmd_args[@]}" ;;
      bar) cmd_bar "${cmd_args[@]}" ;;
      *) printf "%s: unknown command\n" "$cmd" >&2
    esac
  }

  while (( $# )); do
    case "$1" in
      -c)
        read -ra cmd_call <<< "$2"
        shift
      ;;

      *)
        printf "%s: unknown argument\n" "$1" >&2
        exit 1
      ;;
    esac
    shift
  done

  if [[ "$cmd_call" ]]; then
    run_cmd_call "${cmd_call[@]}"
  else
    while
      printf "> "
      read -ra cmd_call
    do
      run_cmd_call "${cmd_call[@]}"
    done
  fi
(then, of course,)

  sudo useradd -ms /path/to/custom-shell.sh userfoo
  sudo -u userfoo bash -c 'mkdir ~/.ssh; printf "%s\n" "$your_pubkey" >> ~/.ssh/authorized_keys'
How would you "do more than run one of the commands that it already runs" with this from a `ssh userfoo@localhost` call?


Don't forget to disable sftp in your sshd_config. and ssh port forwarding. and hopefully there's no sneaky way that an attacker can push an environment variable to break something like with LD_PRELOAD, and are you sure there are no directly/indirectly exploitable bugs/omissions in your script logic for invoking complex commands like git, and that your distributed filesystem works quickly across a few million home folders for your authorized_keys files, ...


EDIT: Crap. You're right. Port forwarding worked... So there's that. `ssh -J userfoo@localhost other-place` also worked... I would imagine that's all able to be disabled, but I see that the user is not fundamentally limited to what the shell allows. That's disappointing. I thought it would wrap everything in `custom-shell.sh -c` calls like it does with the sftp and scp servers. I imagine/hope that under a default configuration one is still not able to touch the contents of the server if the shell doesn't permit it, but being able to use it as a proxy or listen on its ports is not what I expected.

ORIG_REPLY:

> Don't forget to disable sftp in your sshd_config

Doing `strace -fp $(pgrep sshd)` then `sftp userfoo@localhost` in another terminal, I see:

  $ sftp userfoo@localhost
  Connection closed.  
  Connection closed
  $
then in the `strace` output:

  [pid 408173] execve("/tmp/custom-shell.sh", ["custom-shell.sh", "-c", "/usr/lib/ssh/sftp-server"], 0x55c5f3bae0d0 /* 14 vars */) = 0
  [pid 408173] write(1, "/usr/lib/ssh/sftp-server: unknown command\n", 42) = 42
As far as I understand, SSH's security of what a user can do is completely based on what the shell permits the user to do.

> are you sure there are no directly/indirectly exploitable bugs/omissions in your script logic for invoking complex commands like git

Including a ssh server in the same executable doesn't save you from such bugs.

> and that your distributed filesystem works quickly across a few million home folders for your authorized_keys files

OpenSSH's sshd seems quite flexible in ways to authenticate users. I don't think having a home folder per user is necessary if you don't want it.

> and hopefully there's no sneaky way that an attacker can push an environment variable to break something like with LD_PRELOAD

SSH's default is to not accept the client's environment, and this shell itself provides no way to manipulate them. They can be nonexistent in the shell's interface if you don't want them.

I put the script so someone can offer some concrete way (as in show me some code) to bypass it in a way that having a custom integrated SSH server would prevent it. I'd love to see a `ssh/sftp userfoo@localhost` call that does something other than call cmd_foo, cmd_bar, or erring out.


There was another hole if the target command was executed using /bin/bash in a certain way: https://en.wikipedia.org/wiki/Shellshock_(software_bug) It's probably not the case when the command is the user's shell, but it was exploitable when it was the command= in authorized_keys.

Additionally, maybe they are using a custom server because they don't want to fork a new instance of the handler on every login.

Here is a stackoverflow topic on limiting the SSH user: https://serverfault.com/questions/152726/how-can-ssh-allowed.... They suggest a few more options, probably not relevant for a gitlab-like use-case.

> SSH's default is to not accept the client's environment

sshd accepts environment variables specified by AcceptEnv in /etc/ssh/sshd_config. By default, they are mostly related to locales.

  $ LC_ALL=en_US.UTF8 ssh x date
  Tue Sep 19 01:14:41 AM UTC 2023
  $ LC_ALL=cs_CZ.UTF8 ssh x date
  Út 19. září 2023, 01:14:46 UTC


> There was another hole if the target command was executed using /bin/bash in a certain way: https://en.wikipedia.org/wiki/Shellshock_(software_bug)

I agree bash is not the best choice security-wise, but language choice is a bit beside the point with regards to whether or not to bundle a server.

> Additionally, maybe they are using a custom server because they don't want to fork a new instance of the handler on every login.

That's very valid, though the discussion was about having a reduced attack surface from adding a custom server.

> Here is a stackoverflow topic on limiting the SSH user: https://serverfault.com/questions/152726/how-can-ssh-allowed.... They suggest a few more options, probably not relevant for a gitlab-like use-case.

Thanks for that.

> sshd accepts environment variables specified by AcceptEnv in /etc/ssh/sshd_config. By default, they are mostly related to locales.

That seems to be a Debian-specific patch. Upstream doesn't include AcceptEnv in the default config file and there seems to be no default value. Your example commands fail to set LC_ALL on my system. `ssh -o SetEnv=LC_ALL=...` also fails.


Yeah it was a useful approach, "disprove me with code"

>> Don't forget to disable sftp in your sshd_config >Doing `strace -fp $(pgrep sshd)` then `sftp userfoo@localhost` in another terminal, I see: [...] >execve("/tmp/custom-shell.sh", ["custom-shell.sh", "-c", "/usr/lib/ssh/sftp-server"]

If your sshd_config had "Subsystem sftp internal-sftp" as a shipped default, then I don't think it would have gone through the user's shell. Not a major issue, but there are just so many little points to be certain are locked down

>I'd love to see a `ssh/sftp userfoo@localhost` call that does something other than call cmd_foo, cmd_bar, or erring out.

The reference to LD_PRELOAD was to hint at that (since if the attacker can get a binary on the system -- e.g. in a git repo or config file) they could change what functions bash is invoking... I'm not a wiley enough attacker, but I bet if you put something valuable behind it and put out a public challenge you'd be impressed with the opensshd vulnerabilities and other interesting features of ssh/tty/environment variables.

As you probably guessed, my comment was just listing a few of the things (today) that you're playing whack-a-mole with when it comes to making sure everything is secure. I think your "disprove me with code" approach is a great basis for a discussion.

Writing their own 'ssh server and shell' program is a different set of tradeoffs vs trying to fully lock down programs that are intended to be general-purpose... to be perfectly honest I don't think I'd want to be involved in either route, the stakes are stressfully high... but there sure is a lot less code to audit and test in writing your own ssh+shell than there is trying to secure and keep up-to-date sshd, pam, bash, etc. (especially since they'll be adding features that you explicitly do not want for your purposes)


Yeah, regarding whack-a-mole, I thought that wouldn't be necessary. I mistakenly believed:

>> As far as I understand, SSH's security of what a user can do is completely based on what the shell permits the user to do.

I was mistaken in believing that sshd was designed with the user being limited to their specific shell as their sole system interface (as their only way to get the system to do things) as a core design principle. With that sort of funnel, I thought a custom shell would suffice.

> If your sshd_config had "Subsystem sftp internal-sftp" as a shipped default, then I don't think it would have gone through the user's shell.

It's curious how much you can do on a secure "shell" server without involving shells at all.

You know, what's particularly interesting is that strace shows sshd does `custom-shell.sh -c /usr/lib/ssh/sftp-server` instead of `/usr/lib/ssh/sftp-server` directly. That feels like it has no other purpose than to adhere to the principle I believed it was following, but it doesn't apply it for everything. I think it should be possible wrap stuff like `-L`, `-R`, `-D`, `-J` et al. in their own shell calls to another executable, but they don't... Wonder what's the story there.

> The reference to LD_PRELOAD was to hint at that (since if the attacker can get a binary on the system -- e.g. in a git repo or config file) they could change what functions bash is invoking

I had mentioned that the client's environment is not accepted by sshd by default, so I did address this point in my previous comment. Maybe what you're getting at here is that the shell itself may allow modifying the environment through its own logic? But you're still going to have that issue regardless of whether you use sshd or a custom integrated ssh server.

> Writing their own 'ssh server and shell' program is a different set of tradeoffs vs trying to fully lock down programs that are intended to be general-purpose

Indeed. If the hassle to lock-down is large enough, it can be better to opt for simpler, more easily verifiable software. Tradeoffs aplenty, and one such is that you may end up adopting less mature software, by way of the chosen dependencies and your own code, however that server is implemented. That can be worth it.


Bash actually has this feature built in. Restricted shell they call it. Start it as rbash or bash -r.

It will lock down the shell to not allow cd, setting envvars, launching commands outside working directory and a lot more.

If you look at the full list of things they disable you realize how many obscure holes are available you never would have thought about.

Not that I would ever trust it enough to expose on a shared box. Likewise with a tailor made shellscript. I’d take a bespoke server in go any day.


It's not the same thing. You're comparing a white-listing approach with a black-listing approach. The example shell doesn't have any concept of directories or environment variables or anything other than cmd_foo and cmd_bar. The only things that exist are the things written to exist.


What difference does it being a subcommand make? Just use a seccomp filter. It'll apply to all descendent processes.


Attackers cannot get access to features which don't exist even if you forget to configure them off.


I don't get it. When was shell involved with using Apache basic passwords or LDAP authentication? It requires extensive extra steps (pam modules etc) to plug these things into a unix system for authentication purposes. Also that would be authentication but access control implies authorization.


Yeah my reading comprehension process maxed out a core on this one.

> First, you almost certainly operate a variety of services that normally only use Unix logins as a source of (password) authentication and perhaps a UID to operate as, and ignore the login's shell. This is the common pattern of Samba, IMAP servers, Apache HTTP Basic Authentication, and so on.

So you have a user on your server nginx:x:100:101:nginx:/var/lib/nginx:/sbin/nologin

And your also running samba network shares, you point your samba client at your server and use user nginx and inexplicably the password you also set for that user to login? This a service is using etc/shadow basses authentication but not sending the message in /etc/nologin .. presumably samba won’t work really in this case..

>In some cases you may be able to teach these services to look at the login's shell and do special things, but some of them are sealed black boxes and even the ones that can be changed require you to go out of your way. If you forget one, it fails open (allowing access to people with an administrative shell that should lock them out

Is this talking about setting up applications..like a web server, that would give a http access to a uses home for, And having these services authenticate with the servers etc/shadow or configured Pam providers, and also then check out the shell in /etc/password to gracefully handle access management and error messages?


Mh. I think the problem is: There are (at least) 2 fundamentally different use cases for linux/unix systems.

One is what I'd call a shell-server. On a shell-server, I have a bunch of accounts for users, and there are services supplied for these users. There will be /home/tetha, and /home/tetha/share and /home/tetha/http. And then you have some SMB sharing /home/*/share and you login there with your password, Apache serves /home/*/http for the intranet and so on. This is a very common setup at universities, for example.

The other thing is what I'd call an application server or a service server (but that name sucks). Here, you have a system and the main purpose of the system is to serve a web page via nginx, or be a postgres node and such.

These service-systems tend to be both more controlled, but also simpler. You need to grant a rather small, very known set of users access to these - 10 - 30 usually. And, honestly, these service-level systems tend to have very streamlined permissions, because realistically, shell-access is enough attack profile to be considered root access unless you are very diligent.

Shell-servers however are very, very complex to handle. One big shell-server can be overall more complex than many infrastructures around in total.


Ok. I read it twice trying to catch something but had the same thoughts as you.


Judging by the thread here, the very premise seems difficult to imagine today. But there was a time, indeed, when end users regularly logged in to a Unix system as themselves, and their shell specified the program they could run, like a line of business app or something, and short of exploiting a vulnerability in that app, that was all they could access, sitting there at a physical terminal typing on a keyboard. There just weren’t that many other ways to spawn a process.

Meanwhile, these days, it seems like the only user ever running anything is docker, and “access control” has an entirely different set of meanings.


And the author is a sysadmin at a university, and universities are pretty much the last vestige of that traditional Unix login model of accessing shared services. From some other articles he's written, it sounds like his environment has been very conservatively updated over the years.


It’s always interesting to find people who don’t know about `-N` („Do not execute a remote command. This is useful for just forwarding ports.“[0]), which was quite neat to access the MySQL on the same host (with no root database Passwort, duh) at one of my previous employers.

[0]: https://man7.org/linux/man-pages/man1/ssh.1.html


Side note - is this author noteworthy for something in particular? Their somewhat average posts consistently make it to the front page and tend to get a more mixed/negative reaction than normal https://news.ycombinator.com/from?site=utoronto.ca


The post does not back up its headline? How can a SHELL=/bin/false be bypassed via SSH?

For the various kind of forwarding SSH supports, they all can be disabled via sshd_config.


I think it's comparing to the "old days" when there was a more 1:1 relationship of service-on-a-port plus a specific userid. Because the article specifically calls out:

"One of these services is SSH itself, since you can generally initiate SSH sessions and ask for port forwarding or other features that don't cause SSH to run the login shell"

But, even that sort of falls apart because we also had inetd in the "old days", which spawned lots of different things as different users without invoking a login shell.

Generally, the article seems to be lamenting that not everything is gated by userids and groups anymore. That's true, but it doesn't seem recent to me. Aside from inetd, I could (and did) massage kermit (or uucp, etc) into being a multi-service gateway in the 1980's.

I suppose it's somewhat correct in the idea that figuring how any particular service limits rights is now very complicated. You have the old familiar stuff (users, groups). But now you also have virtual machines, containers, namespaces, capabilities. And things like seccomp and apparmor. Then, various sandboxing schemes within utilities, languages and frameworks or OS facilities like ebpf.


It seems to me (it's kinda unclear) that the article is talking mostly about various other things pulling users from /etc/passwd and the ssh thing is an aside. For anon ssh you can have e.g.

  Match User anonymous
          PasswordAuthentication yes
          PermitEmptyPasswords yes
          DisableForwarding yes
          PermitTTY no
and possibly ForceCommand and ChrootDirectory depending on how you're sandboxing anonymous and to what; plus a restricted login shell (the above anonymous user of mine has gotsh, got's version of git-shell).


Ive used something like ''' ssh -t user@host /bin/sh '''

To bypass shell restrictions in the past, but I'm not sure if that will work with your example.


This won't work because the command send to the server is not an argument vector, but a command string interpreted by the users login shell.

`ssh -t user@remote /bin/sh asdf` would execute `/bin/false -c "/bin/sh asdf"` on the remote.


Makes sense, thanks for the clarification.


Feel free to try it out

    sftp share@ohblog.net   (no pw)

    # grep share /etc/passwd /etc/shadow
    /etc/passwd:share:x:5002:5000::/data/sftp/share:/bin/false
    /etc/shadow:share::19614::::::
The partially redacted /etc/ssh/sshd_config is copied to /pub/ in the SFTP account.

If you can bypass the restriction please do share how it was done. I don't offer bug bounties but I think people would find it interesting. OS is Alpine Linux. All CPU mitigations are disabled in the VM. No MAC As in no SElinux or AppArmor. I won't complain, just pretty please don't DDoS the server or anything that would make that VPS do work.

Feel free to also tinker with the web and voice chat server on that node.


For a port-forward with `-N` on client side.

Besides that, I don’t know.


You can pass various env vars with ssh, typically restricted to LANG and LC_*.

But I vaguely remember that in the past it may have been anything, including SHELL.


I have had the pleasure(?) of running a large linux network, I don't think it was ever really the case that shells were a viable access control mechanisms on their own.

Firstly it only really worked if you were all sharing the same machine with remote terminals, which most people don't do anymore. Second NFS happened when if you configured it badly you could just pretend you were any user you liked.

I'm assuming this is part of the reason why kerberos was invented. Basically the only practical way to tie down a network of machines was to make sure that authentication was done with LDAP and Kerberos. LDAP did the name, UID/GID and user metadata and kerberos the authentication. You could then use that ticket to gain access to other things (yes, even over HTTP, NFS or SSH)

Nowadays you'd use active directory, which is LDAP+Kerberos, but with a nice expensive gui.

/etc/passwd(or shadow) died _years_ ago, It was dodgy even in the 90s, let alone now. Its fine for single user machines, but not networked.


In case anyone is tempted to read the article: it's totally incoherent, even to someone knowledgeable in the field.


are they speaking about changing the "shell" field in `/etc/password` as a means of broader access control?

That went away a long, long time ago; the question of "which system am i authenticating myself to" was often already too complicated for that way back when when "shadow passwords" were a new idea.


I guess I'm just lucky that I have never ever in my life even once seen something almost similar to using a Unix shell as an access control mechanism.

Maybe next we will hear that Hammers are not viable screwdrivers any more because, you know, that is also probably something everybody already knows.


>In the realm of unconventional tools, the potential application of biological structures has always been a topic of intrigue. This study delves into the material science behind the potential of hamster claws to function as micro-screwdrivers, given their inherent sharpness and structure.

Using scanning electron microscopy (SEM), we characterized the microstructure of hamster claws, revealing a keratinous composition with intricate surface patterns. Nano-indentation techniques were employed to determine the hardness and resilience of the claws. Their sharpness was quantitatively assessed using a sharpness index, which considered both geometric and material properties.

To test the potential application as micro-screwdrivers, we designed a set of experiments where hamster claws were used to engage with micro-screws commonly found in electronics and precision instruments. The results indicated that while hamster claws can fit into the grooves of certain micro-screws, the torque they can generate is limited due to their flexibility and the lack of a proper grip mechanism.


Yeah, this post is giving https://xkcd.com/2071/


I pretty much assume now that if somebody has ability to run an arbitrary process on the machine (ie shell), they have root access to that machine regardless of their starting access level.


At best, this is an exposition of someone's conflation of their perception of a controller problem as a view problem, with the implied model problem ignored.


I think this is being approached backwards - both by the article and by commenters here.

A Unix account is a viable access control mechanism. If you create one, you give its human user access to do everything that the Unix user can do. This can be very useful.

But others want to give humans users more restricted access than their corresponding Unix account. This is futile, since the Unix account is the basic unit of access control on a Unix or Unix-like system. The author is correct that if you want to do this then you must create a sandbox. General tools won't do, because they're not designed for it, and/or are generally swimming against the tide.


One of the learnings lately has been that when you think about access control, it is combination of the downstream access control mechanism + a network layer grant to access the resource. For eg. If you want to access a server or host, you have some authorization to SSH into it but also by you have network access only to that server or host from your source and then authorization is revoked also the network access to the server or host is revoked too.


I'd better re-read this article because I don't think shells were ever (in my 20something-mumble years)a terribly strong access control mechanism(). Without putting in the effort. Meaning an appropriate (poss non-default) security model, chroot/jails, possibly pledge etc.


Basically they want a somewhat poorly designed configuration map (environment variables) to be attached to these operations, but instead everything comes from adhoc/bespoke configuration sources on a per app basis (like ~/.ssh/config)


Using custom shells for IMAP or HTTP auth must have been the parallel reality where checkpassword(1) never existed. I certainly never ran into this behavior in the wild and would have strenuously objected had I done so. Setting custom shells is fine for providing tunneled services over ssh tunnels or the like (many git hosts use this functionality) but using them for access control just meant your only safety net was whether the program's author was really good at preventing string overflows. This was never a good idea.


I skimmed this article using a different POV than many of you, and it made sense. From the other POV it is clear why many of you don’t like or in some cases understand it.

If your idea of “Unix” is Linux kernel with Busybox and dropbear, it makes sense. I think.


This is a response to the headline and not yet to the story: were UNIX shells ever an access control mechanism? I never viewed them that way.


Uhh... wat?


Can somebody translate this to english? A shell was never an access control mechanism. It was a prompt at which you could type commands to run. As a hack they added authentication to it some time in the 60's at MIT.

> Today, the only two measures of login access control that really work in a general environment are either scrambling the login's password (and disable any SSH authorized keys) or excluding the login entirely from your various authentication data sources (your LDAP servers, your Apache htpasswd files, and so on). It's a pity that changing people's shells is no longer enough (it was both easy and convenient), but that's how the environment has evolved.

This literally makes no sense. Scrambling a login password is not an access control mechanism. Excluding "a login" (a what?) from an authentication data source... the hell does this mean? You mean disabling a user account?

The last part, "changing a user's shell", makes a little sense, as you used to be able to prevent a user from logging in by changing their shell to "/bin/false" or something, but that isn't disabling authentication, that's just breaking the shell. Authentication still works if the shell has changed, it just can't execute the shell after you authenticate.

The proper way of disabling the account is to set the "disabled account" bit in the shadow or password file. But that's just one authentication mechanism (NSS). There is no universal authentication mechanism, so for any other given authentication mechanism, you need to disable it however that given method allows you.


Traditional Unix conflates authentication and authorization - if you can authenticate as a user you are authorized to use the computer, full stop. If you want to later revoke authorization, or only authorize certain services but not others, Unix provides no general means to do so.

Scrambling the password works as a deauthorization mechanism solely because you strip the user of the ability to authenticate. This wouldn't clear authorized_keys, though; services that needed to implement new authentication mechanisms generally did not bother extending /etc/shadow or NSS in an obvious way everyone agreed upon.

Changing the login shell used to work because Unix used to be used solely through the medium of /bin/sh. This, however, conflates two different signals:

- Who should be allowed to start an interactive session on this machine?

- What kind of shell does this user prefer to use?

We pretend that disabled users just really love using this one particular shell that immediately kicks them out of their login session.

The only bulletproof way to deauthorize a user is to unperson them - delete the account. Except if you do this then their old files still sit there on the disk with their user ID. If you reuse the user ID then the new user gains authorization to the old user's files; so you have to keep track of unpersons to avoid reusing their IDs or meticulously reassign their files to a dedicated cleanup user.


> Traditional Unix conflates authentication and authorization - if you can authenticate as a user you are authorized to use the computer, full stop.

Incorrect. Nearly every service in a Unix computer has its own authorization mechanisms, there is no blanket authorization over an entire host.

The login service has a "nologin" and "securetty" files. Sudo has its sudoers file. RSH has the .rhosts file. FTP daemons have their own authorization logic. NFS authorization options could override local filesystem permissions. Filesystem permissions defined the authorization for device files, but network services (such as CUPS) would add their own authorization mechanisms. Etc, etc, etc.

The difference between authentication and authorization has been apparent since time-sharing systems were invented. The operators were authorized to perform different operations than the bog-standard users. There hasn't been a time since the 60's that everyone was authorized the same just because they were authenticated.

> Scrambling the password works as a deauthorization mechanism solely because you strip the user of the ability to authenticate.

Doesn't work. If you're using SSH key authentication you can skip password auth. With RSH, password auth isn't required if you're coming from a trusted host defined in a .rhosts file.

> The only bulletproof way to deauthorize a user is to unperson them - delete the account.

That doesn't always work either. There may be more than one authentication system, and they may be configured to simply skip to the next authentication method if a user account is not found. So you would have to delete the account from every authentication system connected.

You have to use the prescribed deactivation method for every authentication system connected, otherwise the first auth method may skip the deactivated user but the second system would pick up its own user with that name/ID.


I think you've got the wrong end of the stick - it's about forcing the login shell to something (like sftp or rbash or or /bin/nologin whatever) which was a historically somewhat popular thing to do




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: