OpenSSL issues a bugfix for the previous bugfix

If you’re an OpenSSL user, you’re probably aware of the most recent high-profile bugfix release, which came out back in March 2022.

That fix brought us OpenSSS 3.0.2 and 1.1.1n, updates for the two current fully-supported flavours of the product.

(There’s a legacy version, 1.0.2, but updates to that version are only available to customers paying for premium support, and given the changes and improvements in the product since the days of 1.0.2, we urge you to jump ahead to a mainstream version even – perhaps especially – if you plan to continue paying for support.)

The March 2022 update was a vital reminder that deeply-buried code with unusual bugs may end up getting overlooked for years, especially if that code is part of a complex, specialised, low-level function.

The bug fixed back then related to a special-purpose algorithm for computing what are known as modular square roots, which are more complicated to calculate than regular square roots.

Unfortunately, the code to perform this calculation, using an algorithm first discovered in the 1890s, was clumsily coded, tortuously written, poorly commented, and hard to follow.

However, given that it wasn’t in an obvious “externally-facing” part of OpenSSL, and given that rewriting it would have been a daunting task, we’re assuming that it was tested carefully for the correctness of its answers when presented with well-formed numbers, but not probed for its robustness when faced with unlikely input.

Because, when faced with digital certificates that had been booby-trapped to produce ill-formed numbers, OpenSSL’s `BN_mod_sqrt()` function could be tricked into looping forever, trying to close in on an answer that didn’t exist.

When you work only with integers, and disallow fractions of any sort, you find that many numbers don’t have modular square roots, just as you find that many integers don’t have regular square roots. Thus 7×7 = 49, so 49 has a square root that is a whole number, namely 7. But there’s no integer that can be multiplied by itself to give 50, or 51, because the next “perfect square” is 8×8 = 64. You can try for as long as you like, but you will never find a whole-number answer for √51.

Never actually incorrect, just incomplete

In other words, although OpenSSL’s BigNumber code (many encryption algorithms rely on working with numbers that are hundreds or even thousands of digits long) never gave a wrong answer, it sometimes didn’t realise that there wasn’t an answer to find, and would get stuck in an infinite loop.

This infinite loop, which could be abused to provoke what’s known as a Denial-of-Service attack (DoS), could be triggered if a malevolent website sent across a booby-trapped digital certificate.

This meant, ironically, that software that was scrupulous about validating digital certificates could be brought to its knees via this bug, dubbed CVE-2022-0778, while programs that didn’t bother with certificate validation at all weren’t affected by it.

Given the important “teachable moments” revealed by this bug, we covered it in detail not only on Naked Security, where we explained how to write a better style of code, but also on Sophos News, where SophosLabs showed the gory details of how a booby-trapped certificate could trigger the flaw, and how to debug the code to understand the bug.

Two more security holes in the meantime

The next OpenSSL update was 3.0.3, or 1.1.1o for users of the previous release, which patched a bug that wasn’t considered a major flaw (at least, we didn’t cover it on Naked Security), mainly because the bug wasn’t in the OpenSSL encryption library code itself.

Instead of affecting all software that relied on OpenSSL as its crytographic provider, CVE-2022-1292 just affected a utility script, written in Perl, that came along with the OpenSSL toolkit.

This script, known as `c_rehash` (short for certificate directory rehash) is a little-known tool that takes a directory of cryptographic certificate files, such as the ones maintained as trusted certificate authorities (CAs) by Mozilla, and creates a list of file hashes that can help software find specific certificates more quickly than searching an alphabetical list of names.

For example, Mozilla’s CA certificate directory looks like this…

```\$ ls -l /usr/share/ca-certificates/mozilla
-rw-r--r-- 1 duck duck 2772 2022-06-23 05:32 ACCVRAIZ1.crt
-rw-r--r-- 1 duck duck 1972 2022-06-23 05:32 AC_RAIZ_FNMT-RCM.crt
-rw-r--r-- 1 duck duck  904 2022-06-23 05:32 AC_RAIZ_FNMT-RCM_SERVIDORES_SEGUROS.crt
[. . .]
-rw-r--r-- 1 duck duck 1302 2022-06-23 05:32 emSign_Root_CA_-_G1.crt
-rw-r--r-- 1 duck duck  774 2022-06-23 05:32 vTrus_ECC_Root_CA.crt
-rw-r--r-- 1 duck duck 1911 2022-06-23 05:32 vTrus_Root_CA.crt
```

…while OpenSSL’s `c_rehash` script generates a list of symbolic links that allow individual certificates to be located via hashes based on the issuer’s name in the certificate itself, rather than via its filename:

```lrwxrwxrwx 1 duck duck   23 2022-06-24 13:41 002c0b4f.0 -> GlobalSign_Root_R46.crt
lrwxrwxrwx 1 duck duck   45 2022-06-24 13:41 02265526.0 -> Entrust_Root_Certification_Authority_-_G2.crt
lrwxrwxrwx 1 duck duck   36 2022-06-24 13:41 03179a64.0 -> Staat_der_Nederlanden_EV_Root_CA.crt
[. . .]
lrwxrwxrwx 1 duck duck   19 2022-06-24 13:41 fe8a2cd8.0 -> SZAFIR_ROOT_CA2.crt
lrwxrwxrwx 1 duck duck   23 2022-06-24 13:41 feffd413.0 -> GlobalSign_Root_E46.crt
lrwxrwxrwx 1 duck duck   49 2022-06-24 13:41 ff34af3f.0 -> TUBITAK_Kamu_SM_SSL_Kok_Sertifikasi_-_Surum_1.crt
```

Some software relies on these “hash links” to act as a kind of basic database system for indexing and finding specific certificates.

Furthermore, some operating system distros automatically invoke the `c_rehash` script in the background to keep these special-purpose links up to date.

Shell metacharacters considered harmful

Unfortunately, the script relied on the Perl `system()` function (or an equivalent command) to calculate the file hashes, and the `system()` system automatically launches a command shell, such as Bash, to launch any needed sub-programs.

And, as you probably know, command shells don’t always treat their command-line arguments literally, so that if you put special characters in those arguments, the shell handles them in potentially dangerous ways.

For example, the command `echo runthis` literally prints the text `runthis`, but the command `echo \$(runthis)` doesn’t directly print out the characters `\$(runthis)`.

Instead, the so-called metacommand `\$(runthis)` means command substitution, so it says, “Run the command `runthis` and replace the `\$(...)` part with the output of that command when it’s finished”:

```   # argument treated literally, no metacharacters found
\$ echo runthis
runthis

# tries to execute 'runthis', but no such command exists
\$ echo \$(runthis)

# runs two commands, collects output of both
\$ echo \$(whoami; uname -s -r)
duck Linux 5.18.6
```

If the risk posed by `\$(...)` sounds familiar, that’s because it was the metacommand vulnerability that was exploited in the recent “Follina” bug on Windows. To learn more, and see that bug live in action, you can watch our recorded webinar. Just click on the image below. [Registration required, access is immedidate thereafter.]

What got fixed?

Scripts that accepts untrusted input from someone else – whether that’s a string typed into a web form or a made-up filename supplied from outside – need to be very careful not to allow these special metacommands to leak out as shell arguments when relying on the command shell for running external utilities.

Below, you can see the code that was changed from 1.1.1n to 1.1.1o:

A Perl command of the form ``...`` (a command between backticks, such as ``runthis``, is simply an old-fashioned way of writing the `\$(runthis)` command substitution) was replaced with a dedicated internal function called `compute_hash` that takes greater care with weird metacharacters in the constructed command string.

Well, it turns out that the maintainers didn’t quite catch all the places in this utility script where an external commands was run without due care and attention.

This week therefore saw the release of OpenSSL 3.0.4 and 1.1.1p, to fix another risky system command in the `c_rehash` utility:

This time, it was a call-out to the `cp` (copy file) command via the shell-based `system()` function that was replaced with a safer, dedicated internal function called `copy_file`.

This bugfix has the official identifier CVE-2022-2068.

As the OpenSSL changelog warns:

[The `c_rehash`] script is distributed by some operating systems in a manner where it is automatically executed. On such operating systems, an attacker could execute arbitrary commands with the privileges of the script.

What to do?

• Update OpenSSL as soon as you can. If you are relying on your Linux distro to manage a centrally-installed copy, check with your distro maker for details. If you’re relying on your own build of OpenSSL instead of (or as well as) a system-wide one, don’t forget to update that copy, too. You’re looking for 3.0.4 or 1.1.1p. Run `openssl version` to see what version you’ve got.
• Consider retiring the `c_rehash` utility if you are using it. The all-in-one utility `openssl`, which is commonly used for generating and signing certificates in the first place, now includes a built-in sub-command called `rehash` to do the same job. Try `openssl rehash -help` for further information.
• Sanitise your inputs and outputs. Never assume that input you receive is safe to use as-is, and be cautious with the data you pass on as output to other parts of your code.
• Be vigilant for multiple errors when reviewing code for specific types of bug. A programmer who was careless with a `system()` command at one place in the code may have made similar mistakes elsewhere.

Programmers often produce (or reproduce) the same sort of bug many times, usually for perfectly innocent and understandable reasons.

Either they weren’t aware of that class of bug at the time they worked on the code, or they took a “temporary shortcut” to speed up prototype work but never went back and tidied up later, or they copied-and-pasted someone else’s flawed code and made it their own…