sshhole
120 points by serce
120 points by serce
Barely coherent animations every three sentences make this a really unfun read. And, is it just me, or do none of the very overly verbose explanations actually ever explain how those behaviors come together to produce a vulnerability? I had to ignore the prose and put the code snippets together to get what actually happened.
This is a bad writeup IMO. It never bothers to explain how the different changes combined to produce the vulnerability, nor does it explain the changes with enough fidelity for a reader to piece it together themselves:
* shadow entries on musl non-PAM distros would have been vulnerable as soon as the second 2012 change landed. So why does the intro claim 2016? The answer is in one of the screenshots, but the page doesn't explain it: crypt() takes the hashed password (or, at least, the algorithm and salt at the beginning of the hashed password) as its second argument, and the first 2012 change made sure not to return a hash of * when the hashed password starts with *, presumably to avoid this exact bug.crypt()'s second argument is what broke musl's safety check from 2012.)! broke key login, which seems relevant in reconstructing how the vulnerability came to be.The animations are also pretty useless. By showing the arguments and return value of crypt() in different scenarios, they could have illustrated the bug even without clear text. Alas, the 2012 one omits the arguments and the 2016 one omits the return value.
I agree its not explained well, but I disagree that a reader can't piece it together themselves. A coworker and I read and (we think) understood it.
I'm confused by the 2022 change. As described, it seems like Alpine's root user wasn't actually vulnerable until 2022, so why does the writeup say 2016?
The root account was locked with ! between 2016 and 2022. OpenSSH checks for locked accounts and doesn't allow you to login even with a key. However other user accounts configured with * as a placeholder in /etc/shadow were vulnerable in this time.
The 2022 change made it so that the root account is no longer locked, but merely has * as the password hash placeholder, which should match no password ever but did in the musl crypt implementation.
It really reads a lot like someone had Claude code up fancy graphics and never considered whether those graphics have any explanatory value or not. I agree they're distracting and, well, sloppy.
It's an interesting vuln, but this glossy website presentation style that buries the lede (am i affected, what to do), is really annoying to read through.
I know that named-vuln-site itself is a cliche that some people roll their eyes on, but in comparison:
feel much more grounded and immediately useful than this, but I guess the LLM graphics stuff gets more clicks or something :/
That checklist at the end is also like...... Maybe I'm old-fashioned but I thought it's not a good UI/UX thing to have things randomly pop-in or blink.
Also the most generic slop design ever, really. You'd think they'd at least prompt "don't make it look like every single other website ever generated by an LLM"
There are all pre-AI-slop sites, so a human brain was involved in creating them. sshhole seems to have had an AI brain involved, which explains the contortions.
I guess responsible disclosure is out the window.
Here is alpine's security advisory, which has the pertinent information rather than a publicity stunt.
Am I right in understanding that this is fully mitigated by disabling password login and using ssh keys instead? Because that should be the norm nowadays anyway for other reasons as well, right?
(Not trying to say this shouldn't be fixed and isn't a good find though.)
Yes, disabling password logins mitigates it fully, but AFAICT ssh ships with password logins enabled by default.
Password logins are enabled by default, yes, but not for root. Having only an asterisk in the password-field in /etc/shadow is hopefully rare for other users.
I don't know how Alpine does it, but my Debian machine's /etc/shadow is full of "system" users with an asterisk in the password field...
On Debian, those users are assigned the login shell /usr/sbin/nologin.
Ah, good point!
Also, the bug is specific to musl-based linuxes. Debian uses glibc, and is not affected :)
It seems ! is default for users (other than root) without a password on Alpine. Hopefully the other musl-distros doesn't follow Debian in using *.
Why did musl even decide to "fix" an API that always returns NULL on failure elsewhere? The correct solution would be to fix the program that uses the API, IMO.
I suspect because this is a fairly rare thing to happen that most people don't think about it when testing their programs, and so don't handle that case correctly. And if you have a large number of real-world programs running that already do the wrong thing and crash, then it's probably easier to fix it at the API level rather than fix all those programs (even if technically those programs are wrong).
Ideally, they could have used ADTs to force users to handle the error cases or crash explicitly (Ok(String) | Err(UnknownAlgorithm)) but that's not really viable for a C API unfortunately.
Yeah but those programs are more likely used on glibc in the vast majority of systems, which will also return NULL. So musl fixing it unilaterally isn't going to make as much of an impact as trying to fix the programs.
"Fixing" it in musl doesn't cause incompatibility with programs that would correctly check for null though. So in theory it can be "fixed" from both sides of the API. Musl fixing it in their version is just a step in the right direction. Sadly it caused a bug that now also needs to be fixed.
Blog post appears to be LLM written, has awkward layout and is full of annoying animations. The actual important information (requirements for the exploit) is very low contrast text.
The Alpine security advisory is a much better source, and includes detailed mitigation instructions and indicators of compromise:
https://www.alpinelinux.org/posts/2026-11-05-openssh-password-authentication-bypass/
Imagine C has proper sum types that force you to unwrap an Ok(value) or Error. It's easy if you try.
I don't think sum types solve the fundamental issue here, i.e. I don't think
Maybe<Hash> crypt(Password, Hash);
would be a good interface. Leaving the actual hash comparision to the user is error-prone. It'd be better to have two different functions - one for creating new hashes, and another for verifying them, which just returns a boolean. I think I first saw this in PHP, but OpenBSD does also happens to have these:
int crypt_checkpass(const char *password, const char *hash);
int crypt_newhash(const char *password, const char *pref, char *hash, size_t hashsize);
(the int is basically a boolean, I assume it's this way to follow convention. I looked around tech@ a bit and it turns out tedu wrote a short article about this in 2014 too)
I'm also inclined to argue that, as specified, crypt could be treated as returning a sum type? It either returns nonnull char* or NULL, and static analyzers can tell if you're treating a nullable value as non-null (Maybe<T> as T).
Now, the entire reason musl made this change is that ~nobody actually does this, and I do also think that C programs could benefit a lot from sum types (untagged unions suck) - but I also feel like defending C a tiny bit here.
Yes the design is a little slop-y, but interesting vulnerability… does this have a CVE then? Not sure how that works. Does this effect anyone running default alpine docker up on the internet then? If you run a docker container with alpine that has ssh installed, are we saying it’s possible to log into the ‘alpine’ user that way by default?
That was a fun read!
Luckily PermitRootLogin hasn't allowed logins with passwords for root since 2015. I'm guessing that's at least part of why this went undiscovered for so long.
For this to hit root I believe you'd have to actively allow password-logins for a root-account that doesn't have a password, which seems like a weird thing to do. But people are forgetful, so it's entirely possible to end up in this situation. I'm glad OpenSSH decided to patch even though they "consider the root cause of this bug to be outside sshd".
First thing I do on any server is turn off password authentication, for years, just because of all the automated brute force attempts. I fail2ban them, of course, but there are always more. It can be a pretty big inconvenience when I have a new device, but I'm just living with it.