The case for keyed password hashing

An idea whose time has come?

Inefficient by design

Image for: Inefficient by design

Computer science is all about efficiency: doing more with less. Getting to the same answer faster in fewer CPU cycles, using less memory or economizing on some other scarce resource such as power consumption. That philosophy makes certain designs stand out for doing exactly the opposite: protocols crafted to deliberately waste CPU cycles, take up vast amounts of memory or delay availability of some answer. Bitcoin mining gets criticized— often unfairly— as the poster-child for an arms race designed to throw more and more computing power at a “useless” problem. Yet there is a much older, more mundane and rarely questioned waste of CPU cycles: password hashing.

In the beginning: /etc/passwd

Password hashing has ancient origins when judged by the compressed timeline of computing history. Original UNIX systems stored all user passwords in a single world-readable file under /etc/passwd. That was in the day of time-sharing systems, when computers were bulky and expensive. Individuals did not have the luxury of keeping one under their desk, much less in their pocket. Instead they were given accounts on a shared machine owned and managed by the university/corporation/branch of government. When Alice and Bob are on the same machine, the operating system is responsible for making sure they stay in their lane: Alice can’t access Bob’s files or vice verse. Among other things, that means making sure Alice can not find out Bob’s password.

Putting everyone’s password in a single world-readable file obviously fall short of that goal. Instead UNIX took a different approach: place a one-way cryptographic hash of the password. Alice and Bob could still observe each other’s password hashes but the hashes alone were not useful. Logging into the system required the original, cleartext version of the password. With a cryptographic hash the only way to “invert” the function is by guessing: start with a list of likely passwords, hash each one and compare against the one stored in the passed file. The hash function was also made deliberately slow. In most cryptographic applications, one prefers their hash function to execute quickly, inefficiency is a virtue here. It slows down attackers more than it slows down the defenders. The hash function is executed only a handful of times for “honest” paths when a legitimate user is trying to login. By contrast an attacker trying to guess passwords must repeat the same operation billions of times.

Later UNIX versions changed this game by separating password hashes— sensitive stuff— from other account metadata which must remain world-readable. Hashes were moved to a different place appropriately called the “shadow” file commonly placed at /etc/shadow. With this improvement attackers must first exploit a local privilege-escalation vulnerability to read the shadow file before they can unleash their password-cracking rig on target hashes.

Atavistic designs for the 21st century

While the 1970s are a distant memory, vestiges of this design persist in the way modern websites manage customer passwords. There may not be a world-readable password file or even shadow file at risk of being exposed to users but in one crucial way the best-practice has not budged: use one-way hash function to process passwords for storage. There have been significant advances in the construction of the hash functions are designed. For example the 1990s era  PBKDF2 includes an adjustable difficulty— read: inefficiency— level, allowing the hash function to waste more time as CPUs become faster. Its modern successor bcrypt follows the same approach. Scrypt ups the along a new dimension: in addition to being inefficient in time by consuming gratuitous amount of CPU cycles, it also seeks to be inefficient in space by deliberately gobbling up memory to prevent attackers from leveraging fast but memory-constrained systems such as GPUs and ASICs. (Interestingly these same constraints also come up in the design of proof-of-work functions for blockchains where burning up some resource such as CPU cycles is the entire point.)

Aside from committing the cardinal sin of computer science— inefficiency— there is a more troubling issue with this approach: it places the burden of security on users. The security of the password depends not only on the choice of hash function and its parameters, but also on the quality of the password selected. Quality is elusive to define formally, despite no shortage of “password strength meters” on websites egging users on to max out their scale. Informally it is a measure of how unlikely that password is to be included among guesses tried by a hypothetical attacker. That of course depends on exactly the order an attacker goes about guessing. Given that the number of all possible passwords is astronomically large, no classical computer has a chance of going through each guess even when a simple hash function is used. (Quantum-computers provide a speed-up in principle with Grover’s algorithm.) Practical attacks instead leverage the weak point: human factors. Users have difficulty remembering long, random strings. Absent some additional help such as password managers to generate such random choices, they are far more likely to pick simple ones with patterns: dictionary words, keyboard sequences (“asdfgh”),  names of relatives, calendar dates. Attacks exploit this fact by ordering the sequence of guesses from simple to increasing complexity. The password “sunshine123” will be tried relatively early. Meanwhile “x3saUU5g0Y0t” may be out of reach: there is not enough compute power available to the attacker to get that far down the list of candidates in the allotted time. Upshot of this constraint: users with weak passwords are at higher risk if an attacker gets hold of password hashes.

Not surprisingly websites are busy making up arbitrary password complexity rules, badgering user with random restrictions: include mixed case, sprinkle in numerical digits, finish off with a touch of special characters but not that one. This may look like an attempt to improve security for customers. Viewed from another perspective, it is an abdication of responsibility: instead of providing the same level of security for everyone, the burden of protecting password hashes from brute-force attack is shifted to customers.

To be clear, there is a minimal level of password quality helpful to resist online attacks. This is when the attacker tries to login through standard avenues, typically by entering the password into a form. Online attacks are very easy to detect and protect against: most services will lock out accounts after a handful of incorrect guesses or require CAPTCHA to throttle attempts. Because attackers can only get in a handful of tries this way, only the most predictable choices are at risk. (By contrast an attacker armed with stolen database of password hashes is only limited by the processing power available. Not to mention this attack is silent: because there is no interaction with the website— unlike the online guessing approach— the defenders have no idea that an attack is underway or which accounts need to be locked for protection.) Yet the typical password complexity rules adopted by websites go far beyond what is required to prevent online guessing. Instead they are aimed at the worst-case scenario of an offline attack against leaked password hashes.

Side-stepping the arms race: keyed hashing

Image for: Side-stepping the arms race: keyed hashing

One way to relieve end-users from the responsibility of securing their own password storage— as long as passwords remain in use, considering FIDO2 is going nowhere fast— is to mix in a secret into the hashing process. That breaks an implicit but fundamental assumption in the threat model: we have been assuming attackers have access to the same hash function as the defenders use so they can run it on their own hardware millions of times. That was certainly the case for the original UNIX password hashing function “crypt.” (Side-note: ironically crypt was based on the block cipher DES which uses a secret-key to encrypt data. “Crypt” itself does not incorporate a key.) But if the hash function requires access to some secret only known  to the defender, then the attacker is out of luck:

For this approach to be effective, the “unknown” must involve a cryptographic secret and not the construction of the function. For example making some tweaks to bcrypt and hoping the attacker remains unaware of that change provides no meaningful benefit. That would be a case of the thoroughly discredited security-through-obscurity approach. As soon as the attacker gets wind of the changes— perhaps by getting access to source code in the same breach that resulted in the leak of password hashes— it would be game over.

Instead the unknown must be a proper cryptographic key that is used by the hash function itself. Luckily this is exactly what a keyed hash does. In the same way that a plain hash function transform input of arbitrary size into a fixed size output in an irreversible manner, a keyed hash does the same while incorporating a key.

KH(message, key) → short hash

HMAC is one of the better options but there are others including the older generation CMAC and newer alternatives such as Poly1305 and KMAC. All of these functions share the same underlying guarantee: without knowing the key, it is computationally difficult to produce the correct result for a new message. That holds true even when the attacker can “sample” the function on other messages. For example an attacker may have multiple accounts on the same website and observe the keyed hashes for his/her own accounts for which he/she has selected the password. Assuming our choice of keyed hash lives up to its advertised properties, that would still provide no advantage in being able to compute keyed-hashes on other candidate passwords.

Modern options for key management

The main challenge for deploying keyed hashing at scale comes down to managing the lifecycle of the key. There are three areas to address:

  1. Confidentiality is paramount. This key must be guarded well. If attackers get hold of it, it stops being a keyed hash and turns into a plain hash. (One way to mitigate that is to apply the keyed hash in series after an old-school, slow unkeyed hash. More on this in a follow up post.) That means the key can not be stored in say the same database where the password hashes are kept: otherwise any attack that could get at hashes would also divulge the key, losing out on any benefits.
  2. Availability is equally critical. Just as important as preventing attackers from getting their hands on the key is making sure defenders do not lose their copy. Otherwise users will not be able to login: there is no way to validate whether they supplied the right password without using this key to recompute the hash. That means it can not be some ephemeral secret generated on one server and never backed-up elsewhere. Failure at that single point would result in loss of the only existing copy and render all stored hashes useless for future validation.
  3. Key rotation is tricky. It is not possible to simply “rehash�� all passwords with a new key whenever the service decides it is time for change. There is no way to recover the original password out of the hash, even with possession of the current key. Instead there will be an incremental/rolling migration: as each customer logs in and submits their cleartext password, it will be validated against the current version of the key and then rehashed with the next version to replace the database entry.

Of these #3 is something an inevitable implementation twist, not that different in spirit from trying to migrate from one unkeyed hash to another. But there is plenty of help available in modern cloud environments to solve the first two problems.

Until about ~10 years ago using dedicated cryptographic hardware— specifically HSM or hardware security module— was the standard answer to any key management problem with this stringent combination of confidentiality and availability. HSMs allow “grounding” secrets to physical objects in such a way that the secret can be used but not disclosed.  This creates a convenient oracle abstraction: a blackbox that can be asked to perform the keyed-hash computations on any input message of our choosing, but can not be coerced to divulge the secret involved in those computations. Unfortunately those security guarantees come with high operational overhead: purchasing hardware, setting them up  in one or more physical data centers and working with the arcane, vendor-specific procedures to synchronize key material across across multiple units while retaining some backups in case the datacenter itself goes up in flames. While that choice is still available today and arguably provides the highest level of security/control, there are more flexible options with different tradeoffs along the curve:

  • CloudHSM offerings from AWS and Google have made it easier to lease HSMs without dealing with physical hardware. These are close to the bare-metal interface of the underlying hardware but often throw-in some useful functionality such as replication and backup automatically.
  • One level of abstraction up, major cloud platforms have all introduced “Key Management Service” or KMS offerings. These are often backed by an HSM but hide the operational complexity and offer simpler APIs for cryptographic operations compared to the native PKCS#11 interface exposed by the hardware. They also take care of backup and availability, often providing extra guard-rails that can not be removed. For example AWS imposes a mandatory delay for deleting keys. Even an attacker that temporarily gains AWS root permissions can not inflict permanent damage by destroying critical keys.
  • Finally TPMs and virtualized TPMs have become common enough that they can be used often at a fraction of the cost of HMSs. TPM can provide the same blackbox interface for computing HMACs with a secret key held in hardware. TPMs do not have the same level of tamper resistance against attackers with physical access but that is often not the primary threat one is concerned with. A more serious limitation is that TPMs lack replication or backup capabilities. That means keys must be generated outside the production environment and imported into each TPM, creating weak points where keys are handled in cleartext. (Although this only has to be done once for the lifecycle of the hardware. TPM state is decoupled form the server. For example all disks can be wiped and OS reinstalled with no effect on imported keys.)

Threat-model and improving chances of detection

In all cases the threat model shifts. Pure “offline” attacks are no longer feasible even if an attacker can get hold of password hashes. (Short of a physical attack where the adversary walks into the datacenter and rips an HSM out of the rack or yanks the TPM out of the server board— hardly stealthy.) However that is not the only scenario where an attacker can access the same hash function used by the defenders to create those hashes. After getting sufficient privileges in the production environment, a threat actor can also make calls to the same HSM, KMS or TPM used by the legitimate system. That means they can still mount a guessing attack by running millions of hashes and comparing against known targets. So what did the defenders gain?

First note this attack is very much online. The attacker must interact with the gadget or remote service performing the keyed hash for each guess. It can not be run in a sealed environment controlled by the attacker. This has two useful consequences:

  • Guessing speed is capped. It does not matter how many GPUs the attacker has at their disposal. The bottleneck is the number of HMAC computations the selected blackbox can perform. (Seen in this light, TPMs have an additional virtue: they are much less powerful than both HSMs and typical cloud KMSes. But this is a consequence of their limited hardware rather than any deliberate attempt to waste cycles.)
  • Attackers risk detection. In order to invoke the keyed hash they must either establish persistence in the production environment where cryptographic hardware resides or invoke the same remote APIs on a cloud KMS service. In the first case this continued presence increases the signals available to defenders for detecting an intrusion. In some cases the cryptographic hardware itself could provide clues. For example many HSMs have an audit trail of operations. Sudden spike in number of requests could be a signal of unauthorized access. Similarly in the second case usage metrics from the cloud provider provide a robust signal of unexpected use. In fact since KMS offerings typically charge per operation, the monthly bill alone becomes evidence of an ongoing brute-force attack.

CP

AWS CloudHSM key attestations: trust but verify

(Or, scraping attestations from half-baked AWS utilities)

Verifying key provenance

Image for: Verifying key provenance

Special-purpose cryptographic hardware such as HSMs are one of the best options for managing high value cryptographic secrets, such as private keys controlling blockchain assets. When significant time is spent implementing such a heavyweight solution, it is often useful to be able to demonstrate this to third-parties. For example the company may want to convince customers, auditors or even regulators that critical value key material exists only on fancy cryptographic hardware, and not on a USB drive in the CEO’s pocket or some engineer’s commodity laptop running Windows XP. This is where key attestations come in handy. An attestation is effectively a signed assertion from the hardware that a specific cryptographic key exists on that device.

At first that may not sound particularly reassuring. While that copy of the key is protected by expensive, fancy hardware, what about other copies and backups lying around on that poorly guarded USB drive? These concerns are commonly addressed by design constraints in HSMs which guarantee keys are generated on-board the hardware and can never be extracted out in the clear. This first part guarantees no copies of the key existed outside the trusted hardware boundary before it was generated, while the second part guarantees no other copies can exist after generation. This notion of being “non-extractable” means it is not possible to observe raw bits of the key, save them to a file, write them on a Post-It note, turn it into a QR code, upload it to Pastebin or any of the dozens of other creative ways ops personnel have compromised key security over the years. (To the extent backups are possible, it involves cloning the key to another unit from the same manufacturer with the same guarantees. Conveniently that creates lock-in to one particular model in the name of security— or what vendors prefer to call “customer loyalty.” 🤷🏽)

CloudHSM, take #2

Image for: CloudHSM, take #2

Different platforms handle attestation in different ways. For example in the context of Trusted Platform Modules, the operations are standardized by the TPM2 specification. This blog post looks at AWS CloudHSM, which is based on the Marvell Nitrox HSMs, previously named Cavium. Specifically, this is the second version of Amazon hosted HSM offering. The first iteration (now deprecated) was built on Thales née Gemalto née Safenet hardware. (While the technology inside an HSM advances slowly due to FIPS certification requirements, the nameplate on the outside can change frequently with mergers & acquisitions of manufacturers.)

Attestations only make sense for asymmetric keys, since it is difficult to convey useful information about a symmetric key without actually leaking the key itself. For asymmetric cryptography, there is a natural way to uniquely identify private keys: the corresponding public-key. It is sufficient for the hardware then to output a signed statement to the effect “the private key corresponding to public key K is resident on this device with serial number #123.” When the authenticity of that statement can be verified, the purpose of attestation is served. Ideally that verification involves a chain of trust going all the way back to the hardware manufacturer who is always part of the TCB. Attestations are signed with a key unique to each particular unit. But how can one be confident that unit is supposed to come with that key? Only the manufacturer can vouch for that, typically by signing another statement to the effect “device with serial #123 has attestation-signing key A.” Accordingly every attestation can be verified given a root key associated with the hardware manufacturer.

If this sounds a lot like the hierarchical X509 certificate model, that is no coincidence. The manufacturer vouches for a specific unit of hardware it built, and that unit in turn vouches for the pedigree of a specific user-owned key. X509 certificates seem like a natural fit. But not not all attestation models historically follow the standard. For example TPM2 specification defines its own (non-ASN1) binary format for attestations. It also diverges from the X509 format, relying on a complex interactive protocol to improve privacy, by having a separate, static endorsement key (itself validated by a manufacturer issued X509 certificate, confusingly enough) and any number of attestation keys that sign the actual attestations. Luckily Marvell has hewed closely to the X509 model, with the exception of the attestations themselves where another home-brew (again, non-ASN1) binary format is introduced.

Trust & don’t bother to verify?

There is scarcely any public documentation from AWS on this proprietary format. In fact given the vast quantity of guidance on CloudHSM usage, there is surprisingly no mention of proving key provenance. There is one section on verifying the HSM itself— neither necessary nor sufficient for our objective. That step only covers verifying the X509 certificate associated with the HSM, proving at best that there is some Marvell unit lurking somewhere in the AWS cloud. But that is a long ways from proving that the particular blackbox we are interacting with, identified by a private IP address within the VPC, is one of those devices. (An obvious question is whether TLS could have solved that problem. In fact the transport protocol does use certificates to authenticate both sides of the connection but in an unexpected twist, CloudHSM requires the customer to issue that certificate to the HSM. If there was a preexisting certificate already provisioned in the HSM that chains up to a Marvell CA, it would indeed have proven the device at the other end of the connection is a real HSM.)

Neither CloudHSM documentation or the latest version of CloudHSM client SDK (V5) have much to say on obtaining attestations for a specific key generated on the HSM. There are references to attestations in certain subcommands of key_mgmt_util, specifically for key generation. For example the documentation for genRSAKeyPair states:


-attest

Runs an integrity check that verifies that the firmware on which the cluster runs has not been tampered with.

This is at best an unorthodox definition of key attestation. While missing from the V5 SDK documentation, there are also references in the “previous” V3 SDK (makes you wonder what happened to V4?) to the same optional flag being available when querying key attributes with “getAttribute” subcommand. That code path will prove useful for understanding attestations: each key is only generated once, but one can query attributes any number of times to retrieve attestations.

Focusing on the V3 SDK which is no longer available for download, one immediately run into problems with ancient dependencies and incompatibility with modern Linux distributions It is linked against OpenSSL 1.x which will prevent installation out-of-the-box on modern distributions.

But even after jumping through the necessary hoops to make it work, the result is underwhelming: while the utility claims to retrieve and verify Marvell attestations, it does not expose the attestation to the user. In effect these utilities are asserting: “Take our word for it, this key lives on the HSM.” That defeats the whole point of generating attestations, namely being able to convince third-parties that keys are being managed according to certain standards. (It also raises the question of whether Amazon itself understands the threat model of a service for which it is charging customers a pretty penny.)

Step #1: Recovering the attestation

Image for: Step #1: Recovering the attestation

When existing AWS utilities will not do the job, the logical next step is writing code from scratch to replicate their functionality while saving the attestation, instead of throwing it away after verification. But that requires knowledge of the undocumented APIs offered by Marvell. While CloudHSM is compliant with the standard PKCS#11 API for accessing cryptographic hardware, PKCS#11 itself does not have a concept of attestations. Whatever this Amazon utility is doing to retrieve attestations involves proprietary APIs or at least proprietary extensions to APIs such as a new object attribute which neither Marvell nor Amazon have documented publicly. (Marvell has a support portal behind authentication, which may have an SDK or header files accessible to registered customers.) 

Luckily recovering the raw attestation from the AWS utility is straightforward. An unexpected assist comes from the presence of debugging symbols, making it much easier to reverse engineer this otherwise blackbox binary. Looking at function names with the word “attest”, one stands out prominently:

[ec2-user@ip-10-9-1-139 1]$ objdump -t /opt/cloudhsm/bin/key_mgmt_util  | grep -i attest
000000000042e98b l     F .text 000000000000023b              appendAttestation
000000000040516d g     F .text 0000000000000196              verifyAttestation

We can set a break point on verifyAttestation with GDB:

(gdb) info functions verifyAttestation
All functions matching regular expression "verifyAttestation":
File Cfm3Util.c:
Uint8 verifyAttestation(Uint32, Uint8 *, Uint32);
(gdb) break verifyAttestation
Breakpoint 1 at 0x40518b: file Cfm3Util.c, line 351.
(gdb) cont
Continuing.

Next generate an RSA key pair and request an attestation with key_mgmt_util:

Command:  genRSAKeyPair -sess -m 2048 -e 65537 -l verifiable -attest
Cfm3GenerateKeyPair returned: 0x00 : HSM Return: SUCCESS
Cfm3GenerateKeyPair:    public key handle: 1835018    private key handle: 1835019

The breakpoint is hit at this point, after key generation has already completed and key handles for public/private halves returned. (This makes sense; an attestation is only available after key generation has completed successfully.)

Breakpoint 1, verifyAttestation (session_handle=16809986, response=0x1da86e0 "", response_len=952) at Cfm3Util.c:351
351 Cfm3Util.c: No such file or directory.
(gdb) bt
#0  verifyAttestation (session_handle=16809986, response=0x1da86e0 "", response_len=952) at Cfm3Util.c:351
#1  0x0000000000410604 in genRSAKeyPair (argc=10, argv=0x697a80 <vector>) at Cfm3Util.c:4555
#2  0x00000000004218f5 in CfmUtil_main (argc=10, argv=0x697a80 <vector>) at Cfm3Util.c:11360
#3  0x0000000000406c86 in main (argc=1, argv=0x7ffdc2bb67f8) at Cfm3Util.c:1039

Owing to the presence of debugging symbols, we also know which function argument contains the pointer to the attestation in memory (“response”) and its size (“response_len”.) GDB can save that memory region to file for future review:

(gdb) dump memory /tmp/sample_attestation response response+response_len

Side note before moving on to the second problem, namely making sense of the attestation: While this example showed interactive use of GDB, in practice the whole setup would be automated. GDB allows defining automatic commands to execute after a breakpoint, and also allows launching a binary with a debugging “script.” Combining these capabilities:

  • Create a debugger script to set a breakpoint on verifyAttestation. The breakpoint will have an associated command to write the memory region to file and continue execution. In that sense the breakpoint is not quite “breaking” program flow but taking a slight detour to capture memory along the way.
  • Invoke GDB to load this script before executing the AWS utility.

Step #2: Verifying the signature

Image for: Step #2: Verifying the signature

Given attestations in raw binary format, next step is parsing and verify the contents, mirroring what the AWS utility is doing in the “verifyAttestation” function. Here we specifically focus on attestations returned when querying key attributes because that presents a more general scenario: key generation takes place only once, while attributes of an existing key can be queried anytime. 

By “attributes” we are referring to PKCS#11 attributes associated with a cryptographic object present on the HSM. Some examples:

  • CKA_CLASS: Type of object (symmetric key, asymmetric key…)
  • CKA_KEYTYPE: Algorithm associated with a key (eg AES, RSA, EC…)
  • CKA_PRIVATE: Does using the key require authentication?
  • CKA_EXTRACTABLE: Can the raw key material be exported out of the HSM? (PKCS#11 has an interesting rule that this attribute can only be changed from true→false, it can not go in the other direction.)
  • CKA_NEVEREXTRACTABLE: Was the CKA_EXTRACTABLE attribute ever set to true? (This is important when establishing whether an object is truly HSM-bound. Otherwise one can generate an initially extractable key, make a copy out of the HSM and later flip the attribute.)

Experiments show the exact same breakpoint for verifying attestations is triggered through this alternative code path when “-attest” flag is present:

Command:  getAttribute -o 524304 -a 512 -out /tmp/attributes -attest
Attestation Check : [PASS]
Verifying attestation for value
Attestation Check : [PASS]
Attribute size: 941, count: 27
Written to: /tmp/attributes file
Cfm3GetAttribute returned: 0x00 : HSM Return: SUCCESS

Here is an example of the text file written with all attributes for an RSA key is. Once again the attestation itself is verified and promptly discarded by the utility under normal execution. But debugger tricks described earlier help capture a copy of the original binary block returned. There is no public documentation from AWS or Marvel on the internal structure of these attestations. Until recently there was a public article on the Marvell website (no longer resolves) which linked to two python scripts that are still accessible as of this writing:

These scripts are unable to parse attestations from step #1, possibly because they are associated with a different product line or perhaps different version of the HSM firmware. But they offer important clues about the format, including the signature format: it turns out to be the last 256 bytes of the attestation, carrying a 2048-bit RSA signature. In fact one of the scripts can successfully verify the signature on a CloudHSM attestation, when given the partition certificate from the HSM:

[ec2-user@ip-10-9-1-139 clownhsm]$ python3 verify_attest.py partition_cert.pem sample_attestation.bin 
*************************************************************************
Usage: ./verify_attest.py <partition.cert> <attestation.dat>
*************************************************************************
verify_attest.py:29: DeprecationWarning: verify() is deprecated. Use the equivalent APIs in cryptography.
  crypto.verify(cert_obj, signature, blob, 'sha256')
Verify3 failed, trying with V2
RSA signature with raw padding verified
Signature verification passed!

Step #3: Parsing fields in the attestation

Image for: Step #3: Parsing fields in the attestation

Looking at the remaining two scripts we can gleam how PKCS#11 attributes are encoded in general. Marvel has adopted the familiar tag-length-value model from ASN1 and yet is inexplicably not ASN1. Instead each attribute is represented as concatenation of:

  • Tag containing the PKCS#11 attribute identifier, as 32-bit integer in big-endian format 
  • Length of the attributes in bytes, also 32-bit integer in same format
  • Variable length byte array containing the value of the attribute 

One exception to this pattern are the first 32-bytes of an attestation. That appears to be a fixed-size header containing metadata, which does not conform to this TLV pattern. Disregarding that section, here is a sample Python script for parsing attributes and outputting them using friendly PKCS11 names and appropriate formatting where possible. (For example CKA_LABEL as string, CKA_SENSITIVE as boolean and CKA_MODULUS_BITS as plain integer.)

CP

Spot the Fed: CAC/PIV card edition

Privacy leaks in TLS client authentication

Image for: Privacy leaks in TLS client authentication

Spot the Fed is a long-running tradition at DefCon. Attendees try to identify suspected members of law enforcement or intelligence agencies blending in with the crowd. In the unofficial breaks between scheduled content, suspects “denounced” by fellow attendees as potential Feds are invited on stage— assuming they are good sports about it, which a surprising number prove to be— and interrogated by conference staff to determine if the accusations have merit. Spot-the-Fed is entertaining precisely because it is based on crude stereotypes, on a shaky theory that the responsible adults holding button-down government jobs will stand out in a sea of young, irreverent hacking enthusiasts. While DefCon badges feature a cutting-edge mix of engineering and art, one thing they never have is identifying information about the attendee. No names, affiliation or location. (That is in stark contract to DefCon’s more button-down, corporate cousin, Blackhat Briefings which take place shortly before DefCon. BlackHat introductions often start with attendees staring at each others’ badge.)

One can imagine playing a version of Spot the Fed online: determine which visitors to a website are Feds. While there are no visuals to work with, there are plenty of other signals ranging from the visitor IP address to the specific configuration of their browser and OS environment. (EFF has a sobering demonstration on just how uniquely identifying some of these characteristics can be.) This blog post looks at a different type of signal that can be gleamed from a subset of US government employees: their possession of a PIV card.

Origin stories: HSPD 12

Image for: Origin stories: HSPD 12

The story begins in 2004 during the Bush era with an obscure government edict called HSPD12: Homeland Security Presidential Directive #12:

There are wide variations in the quality and security of identification used to gain access to secure facilities where there is potential for terrorist attacks. In order to eliminate these variations, U.S. policy is to enhance security, increase Government efficiency, reduce identity fraud, and protect personal privacy by establishing a mandatory, Government-wide standard for secure and reliable forms of identification issued by the Federal Government to its employees and contractors […]

That “secure and reliable form of identification” became the basis for one of the largest PKI and smart-card deployments. Initially called CAC for “Common Access Card” and later superseded by PIV or “Personal Identity Verification,” these two programs combined for the issuance of millions of smart-cards bearing X509 digital certificates issued by a complex hierarchy of certificate authorities operated by the US government.

PIV cards were envisioned to function as “converged” credentials, combining physical access and logical access. They can be swiped or inserted into a badge-reader to open doors and  gain access to restricted facilities. (In more low-tech scenarios reminiscent of how Hollywood depicts access checks, the automated badge reader is replaced by an armed sentry who casually inspects the card before deciding to let the intruders in.) But they can also open doors in a more virtual sense online: PIV cards can be inserted into a smart-card reader or tapped against a mobile device over NFC to leverage the credential online. Examples of supported scenarios:

  • Login to a PC, typically using Active Directory and the public-key authentication extension to Kerberos
  • Sign/encrypt email messages via S/MIME
  • Access restricted websites in a web browser, using TLS client authentication.

This last capability creates an opening for remotely detecting whether someone has a PIV card— and by extension, affiliated with the US government or one of its contractors.

Background on TLS client authentication

Image for: Background on TLS client authentication

Most websites in 2024 use TLS to protect the traffic from their customers against eavesdropping or tampering. This involves the site obtaining a digital certificate from a trusted certificate authority and presenting that credential to bootstrap every connection. Notably the customers visiting that website do not need any certificates of their own. Of course they must be able validate the certificate presented by that website, but that validation step does not require any private, unique credential accessibly only to that customer. As far as the TLS layer is concerned, the customer or “client” in TLS terminology, is not authenticated. There may be additional authentication steps at a higher layer in the protocol stack, such as a web page where the customer inputs their email address and password. But those actions take place outside the TLS protocol.

While the majority of TLS interactions today are one-sided for authentication, the protocol also makes provisions for a mode where both sides authenticate each other, commonly called “mutual authentication.” This is typically done with the client also presenting an X509 certificate. (Being a complex protocol TLS has other options including a “preshared key” model but those are rarely deployed.) At a high level, client authentication adds a few more steps to the TLS handshake:

  • Server signals to the client that certificate authentication is required
  • Server sends a list of CAs that are trusted for issuing client certificates. Interestingly this list can be empty, which is interpreted as anything-goes
  • Client sends a certificate issued by one of the trusted anchors in that list, along with a signature on a challenge to prove that it is control of the associated private key

Handling privacy risks

Since client certificates typically contain uniquely identifying information about a person, there is an obvious privacy risk from authenticating with one willy-nilly to every website that demands a certificate. These risks have been long recognized and largely addressed by the design of modern web browsers.

A core privacy principle is that TLS client authentication can only take place with user consent. That comes down to addressing three different cases when a server requests a certificate from the browser:

  1. User has no certificate issued by any of the trust anchors listed by the server. In this case there is no reason to interrupt the user with UI; there is nothing actionable. Handshake continues without any client authentication. Server can reject such connections by terminating the TLS handshake or proceed in unauthenticated state. (The latter is referred to as optional mode, supported by popular web servers including nginx.)
  2. There is exactly one certificate meeting the criteria. Early web browsers would automatically use that certificate, thinking they were doing the user a favor by optimizing away an unnecessary prompt. Instead they were introducing a privacy risk: websites could silently collect personally identifiable information by triggering TLS client authentication and signaling that they will accept any certificate. (Realistically this “vulnerability” only affected a small percent of users because client-side PKI deployments were largely confined to enterprises and government/defense sectors. That said, those also happen to be among the most stringent scenarios where the customer cares a lot about operational security and privacy.)
    Browser designers have since seen the error of their ways. Contemporary implementation are consistent in presenting some UI before using the certificate. This is an important privacy control for users who may not want to send identifying information:
  • There is still one common UX optimization to streamline this: users can indicate that they trust a website and are always willing to authenticate with a specific certificate there. Here is Firefox presenting a checkbox for making that decision stick:
  1. Multiple matching certificates can be used. This is treated identically as case #2, with the dialog showing all available certificate for the user to choose from, or decline authentication altogether.

Detecting the existence of a certificate

Image for: Detecting the existence of a certificate

Interposing a pop-up dialog appears to address privacy risks from websites attempting to profile users through client certificates. While any website visited can request a certificate, users remain in control of deciding whether their browser will go along with. (And if the person complies and sends along their certificate to a website that had no right to ask? Historically browser vendors react to such cases with a time-honored strategy: blame the user— “PEBCAC! It’s their fault for clicking OK.”)

But even with the modal dialog, there is an information leak sufficient to enable shenanigans in the spirit of spot-the-Fed. There is a difference between case #1—no matching certificates— and remaining cases where there is at least one matching certificate. In the latter cases some UI is displayed, disrupting the TLS handshake until the user interacts with that UI to express their decision either way. In the former case, TLS connection proceeds without interruption. That difference can be detected: embed a resource that requires TLS client authentication and measure its load time.

While the browser is waiting for the user to make a decision, the network connection for retrieving the resource is stalled. Even if the user correctly decides to reject the authentication request, page load time has been altered. (If they agree, timing differences are redundant: the website gets far more information than it bargained for with the full certificate.) The resulting delay is on the order of human reaction times—the time taken to process the dialog and click “cancel”— well within the resolution limits of web Performance API.

Proof of concept: Spot-the-Fed

Image for: Proof of concept: Spot-the-Fed

This timing check suffices to determine whether a visitor has a certificate from any one of a group of CAs chosen by the server. While the server will not find out the exact identity of the visitor— we assume he/she will cancel authentication when presented with the certificate selection dialog— the existence of a certificate alone is enough to establish affiliation. In the case of the US government PKI program, the presence of a certificate signals that the visitor has a PIV card.

Putting together a proof-of-concept:

  1. Collect issuer certificates for the US federal PKI. There are at least two ways to source this.
  1. Host a page on Github for the top-level document. It will include basic javascript to measure time taken for loading an embedded image that requires client authentication.
  2. Because Github Pages do not support TLS client authentication, that image must be hosted somewhere else. For example one can use nginx running on an EC2 instance to serve  a one-pixel PNG image.
  3. Configure nginx for optional TLS client authentication, with trust anchors set to the list of CAs retrieved in step #1

There is one subtlety with step #4: nginx expects the full issuer certificates in PEM format. But if using option 1A above, only the issuer names are available. This turns out not to be a problem: since TLS handshake only deals in issuer names, one can simply create a dummy self-signed CA certificate with the same issuer but brand new RSA key. For example, from login.gov we learn there is a trusted CA with the distinguished name “C=US, O=U.S. Government, OU=DoD, OU=PKI, CN=DOD EMAIL CA-72.”  It is not necessary to have the actual certificate for this CA (although it is present in the publicly availably bundles linked above); we can create a new self-signed certificate with the same DN to appease nginx. That dummy certificate will not work for successful TLS client authentication against a valid PIV card— the server can not validate a real PIV certificate without the real issuer public key of the issuing CA. But that is moot; we expect users will refuse to go through with TLS client authentication. We are only interested in measuring the delay caused by asking them to entertain the possibility.

Limitations and variants

Stepping back to survey what this PoC accomplishes: we can remotely determine if a visitor to the website has a certificate issued by one of the known US government certificate authorities. This check does not require any user interaction, but it also comes with some limitations:

  • Successful detection requires that the visitor has a smart-card reader connected to their machine, their PIV card is present in that reader and all necessary middleware required to use that card is present. In practice, no middleware is required for the common case of Windows: PIV support has been built into the OS cryptography stack since Vista. Browsers including Chrome & Edge can automatically pick up any cards without requiring additional configuration. On other platforms such as MacOS and Linux additional configuration may be required. (That said: if the user already has scenarios requiring use of their PIV card on that machine, chances are it is already configured to also allow the card to work in the browser without futzing with any settings.)
  • It is not stealthy in the case of successful identification. Visitors will have seen a certificate selection dialog come up. (Those without a client certificate however will not observe anything unusual.) That is not a common occurrence when surfing random websites. There are however a few websites (mis?)configured to demand client certificates from all visitors, such as this IP6 detection page.
    • It may be possible to close the dialog without user interaction. One could start loading a resource that requires client authentication and later use javascript timers to cancel that navigation. In theory this will dismiss the pending UI. (In practice it does not appear to work in Chrome or Firefox for embedded resources, but works for top-level navigation.) To be clear, this does not prevent the dialog from appearing in the first place. It only reduces the time the dialog remains visible, at the expense of increased false-positives because detection threshold must be correspondingly lower.
    • A less reliable but more stealthy approach can be built if there is some website the target audience frequently logs into using their PIV card. In that case the attacker can attempt to source embedded content from that site— such as an image— and check if that content loaded successfully. This has the advantage that it will completely avoid UI in some scenarios. If the user has already authenticated to the well-known site within the same browser session, there will be no additional certificate selection dialogs. That signals the user has a PIV card because they are able to load resources from a site ostensibly requiring a certificate from one of the trusted federal PKI issuer. In some cases UI will be skipped  even if the user has not authenticated in the current session, but has previously configured their web browser to automatically use the certificate at that site, as is possible with Firefox. (Note there will also be a PIN prompt for the smart-card— unless it has been recently used in the same browser session.)
  • While the PoC checks whether the user has a certificate from any one of a sizable collection of CAs, it can be modified to pinpoint the CA. Instead of loading a single image, one can load dozens of images in series from different servers each configured to accept only one CA among the collection. This can be used to better profile the visitor, for example to distinguish between contractors at Northrop Grumman (“CN=Northrop Grumman Corporate Root CA-384”) versus employees from the Department of Transportation (“CN=U.S. Department of Transportation Agency CA G4.”)
  • There are some tricky edge-cases involving TLS session resumption. This is a core performance improvement built into TLS to avoid time-consuming handshakes for every connection. Once a TLS session is negotiated with a particular server—with or without client authentication— that session will be reused for multiple requests going forward. Here that means loading the embedded image a second time will always take the “quick” route by using the existing session. Certificate selection UI will never be shown even if there is a PIV card present. Without compensating logic, that would result in false-negatives whenever the page is refreshed or revisited within the same session. This demonstration attempts to counteract that by setting a session cookie when PIV cards are detected and checking for that cookie on subsequent runs. In case the PoC is misbehaving, try using a new incognito/private window.

Work-arounds

Image for: Work-arounds

The root cause of this information disclosure lies with the lack of adequate controls around TLS client authentication in modern browsers. While certificates will not be used without affirmative consent from the consumer, nothing stops random websites from initiating an authentication attempt.

Separate browser profiles are not necessarily effective as a work-around. At first it may seem promising to create two different Chrome or Edge profiles, with only one profile used for “trusted” sites setup for authenticating with the PIV card. But unlike cookie jars, digital certificates are typically shared across all profiles. Chrome is not managing smart-cards; Windows cryptography API is responsible for that. That system has no concept of “profiles” or other boundaries invented by the browser. If there is a smart-card reader with a PIV card present attached, the magic of OS middleware will make it available to every application, including all browser profiles.

Interestingly using Firefox can be a somewhat clunky work-around because Firefox uses NSS library instead of the native OS API for managing certificates. While this is more a “bug” than feature in most cases due to the additional complexity of configuring NSS with the right PKCS#11 provider to use PIV cards, in this case it has a happy side-effect: it becomes possible to decouple availability of smart-cards on Firefox from Chrome/Edge. By leaving NSS unconfigured and only visiting “untrusted” sites with Firefox, one can avoid these detection tricks. (This applies specifically to Windows/MacOS where Chrome follows the platform API. It does not apply to Linux where Chrome also relies on NSS. Since there is a single NSS configuration in a shared location, both browsers remain in lock-step.) But it is questionable whether users can maintain such strict discipline to use the correct browser in every case. It would also cause problems for other applications using NSS, including Thunderbird for email encryption/signing.

Until there are better controls in popular browsers for certificate authentication, the only reliable work-around is relatively low-tech: avoid leaving a smart-card connected when the card is not being actively used. However this is impossible in some scenarios, notably when the system is configured to require smart-card logon and automatically lock the screen on card removal.

CP

Password management quirks at Fidelity

IVR Login

Image for: IVR Login

“Please enter your password.”

While this prompt commonly appears on webpages, it is not often heard during a phone call. This was not a social-engineering scam either: it was part of the automated response from the Fidelity customer support line. The message politely invites callers to enter their Fidelity password using the dial-pad, in order to authenticate the customer before connecting them to a live agent. There is one complication: dial-pads only have digits and two symbols, asterisk/plus and pound key. To handle the full range of symbols present in passwords, the instructions call for using the standard convention for translating letters into digits helpfully displayed on most dial-pads:

Standard dial-pad, with mapping of letters to numbers
(Image from Wikipedia)

The existence of this process indicates a quirk in the way Fidelity stores passwords for their online brokerage service.

Recap: storing passwords without storing them

Image for: Recap: storing passwords without storing them

Any website providing personalized services is likely to have an authentication option that involves entering username and password. That website then has to verify whether the correct password is entered, by comparing the submitted version against the one recorded when the customer last set/changed their password.

Surprisingly there are ways to do that without storing the original password itself. In fact storing passwords is an anti-pattern. Even storing them encrypted is wrong: “encryption” by definition is reversible. Given encrypted data, there is a way to recover the original, namely by using the secret key in a decryption operation. Instead passwords are stored using a cryptographic hash-function. Hash functions are strictly one-way: there is no going back from the output to the original input. Unlike encryption, there is no magic secret key known to anyone— not even the person doing the hashing— that would permit reversing the process.

For example, using the popular hash function bcrypt, the infamous password “hunter2” becomes:

$2b$12$K1.sb/KyOqj6BdrAmiXuGezSRO11U.jYaRd5GhQW/ruceA9Yt4rx6

This cryptographic technique is a good fit for password storage because it allows the service to check passwords during login without storing the original. When someone claiming to be that customer shows up and attempts to login with what is allegedly their password, the exact same hash function can be applied to that submission. The output can be compared against the stored hash and if they are identical, one can conclude with very high probability that the submitted password was identical to the original. (Strictly speaking, there is a small probability of false positives: since hash functions are not one-to-one, in theory there is an an infinite number of “valid” submissions that yield the same hash output. Finding one of those false-positives is just as difficult as finding the correct password, owing to the design properties of hash functions.)

From a threat model perspective, one benefit of storing one-way hashes is that even if they are disclosed, an attacker does not learn the user credential and can not use it to impersonate the user. Because hashes are irreversible, the only option available to an attacker is to mount a brute-force attack: try billions of password guesses to see if any of them produce the same hash. The design of good hash-functions for password storage is therefore an arms-race between defenders and attackers. It is a careful trade-off between making hashing efficient enough for the legitimate service to process a handful of passwords quickly during a normal login process, while making it expensive enough to raise costs for an attacker trying to crack a hash by trying billions of guesses.

A miss is as good as a mile

Much ink has been spilled over the subtleties of designing good functions for password hashing. An open competition organized in 2013 sought to create a standardized, loyalty-free solution for the community. Without going too much into details of these constructions, the important property for our purposes is that good hash functions are highly sensitive to their input: change even one bit of the input password and the output changes drastically. For example, the passwords “hunter2” and “hunter3” literally differ in just one bit—the very last bit distinguishes the digit three from the digit two in ASCII code. Yet their bcrypt hashes look nothing alike:

Hash of hunter2 ➞ zSRO11U.jYaRd5GhQW/ruceA9Yt4rx6

Hash of hunter3 ➞ epdCpQLaQbcGTREZLZAFDKp5i/zQmp2

(One subtlety here: password hash functions including bcrypt incorporate a random “salt” value to make each hash output unique, even when they are invoked on the same password. To highlight changes caused by the password difference, we deliberately used the same salt when calculating these hashes above and omitted the salt from the displayed value. Normally it would appear as a prefix.)

Bottom line: there is no way to check if two passwords are “close enough” by comparing hash outputs. For example, there is no way to check if the customer got only a single letter of the password wrong or if they got all the letters correct except for lower/upper-case distinction. One can only check for exact quality.

The mystery at Fidelity

Image for: The mystery at Fidelity

Having covered the basics of password hashing we can now turn to the unusual behavior of the Fidelity IVR system. Fidelity is authenticating customers by comparing against a variant of their original password. The password entered on the phone is derived from, but not identical to the one used on the web. It is not possible to perform that check using only a strong cryptographic hash. A proper hash function suitable for password storage would erase any semblance of similarity between these two versions.

There are two possibilities:

  1. Fidelity is storing passwords in a reversible manner, either directly as clear-text or in an encrypted form where they can still be recovered for comparison.
  2. Fidelity is canonicalizing passwords before hashing, converting them to the equivalent “IVR format” consisting of digits before applying the hash function.

It is not possible to distinguish between these, short of having visibility into the internal design of the system.

But there is another quirk that points towards the second possibility: the website and mobile apps also appear to validate passwords against the canonicalized version. A customer with the password “hunter2” can also login using the IVR-equivalent variant “HuNt3R2

Implications

Image for: Implications

IVR passwords are much weaker than expected against offline cracking attacks. While seemingly strong and difficult to guess, the password iI.I0esl>E`+:P9A is in reality equivalent to the less impressive 4404037503000792.

The notion of entropy can help quantify the problem. A sixteen character string composed of randomly selected printable ASCII characters has an entropy around 105 bits. That is a safety margin far outside the reach of any commercially motivated attacker. Now replace all of those 16 characters by digits and the entropy drops to 53 bits— or about nine quadrillion possibilities, which is surprisingly within range of hobbyists with home-brew password cracking systems.

In fact entropy reduction could be worse depending on exactly how users are generating passwords. The mapping from available password symbols to IVR digits is not uniform:

  • Only the character 1 maps to the digit 1 on the dial-pad.
  • Seven different characters (A, B, C, a, b, c, 2) are mapped to the digit 2
  • By contrast there are nine different symbols mapping to the digit 7, due to the presence of an extra letter for that key on the dial-pad
  • Zero is even more unusual in that all punctuation marks and special characters get mapped to it, for more than 30 different symbols in total

In other words, a customer who believes they are following best-practices, using a password manager application and having their app generate “random” 16 character password will not end up with a uniformly distributed IVR password. Zeroes will be significantly over-represented due to the bias in mapping while the digit one will be under-represented.

The second design flaw here involves carrying over the weakness of IVR passwords to the web interface and mobile applications. While the design constraints of phone-based customer support requires accepting simplified passwords, it does not follow that the web interface would also accept these. (This assumes the web interface grants higher privileges, in the sense that certain actions can only be carried out by logging into the website— such as adding bank accounts and initiating funds transfers— and not possible through IVR.)

Consider an alternative design where Fidelity asks customers to pick a secondary PIN for use on the phone. In this model there are two independent credentials. There is the original password used when logging in through the website or mobile apps. It is case-sensitive and permits all symbols. An independent PIN consisting only of digits is selected by customers for use during phone authentication. Independent being the operative keyword here: it is crucial that the IVR PIN is not derived from the original password via some transformation. Otherwise an attacker who can recover the weak IVR PIN can use that to get a head-start on recovering the full password.

Responsible disclosure

Image for: Responsible disclosure

This issue was reported to the Fidelity security team in 2021. This is their response:

“Thank you for reaching out about your concerns, the security of your personal information is a primary concern at Fidelity. As such, we make use of industry standard encryption techniques, authentication procedures, and other proven protection measures to secure your information. We regularly adapt these controls to respond to changing requirements and advances in technology. We recommend that you follow current best practices when it comes to the overall security of your accounts and authentication credentials. For further information, you can visit the Security Center on your Fidelity.com account profile to manage your settings and account credentials.”

Postscript: why storing two hashes does not help

Image for: Postscript: why storing two hashes does not help

To emphasize the independence requirement, we consider another design and explain why it does not achieve the intended security effect. Suppose Fidelity kept the original design for a single password shared between web and IVR, but hashed it two ways:

  • First is a hash of the password verbatim, used for web logins.
  • Second is a hash of the canonicalized password, after being converted to IVR form by mapping all symbols to digits 0 through 9. This hash is only checked when the customer is trying to login through the phone interface.

This greatly weakens the full password against offline cracking because the IVR hash provides an intermediate stepping stone to drive guessing attacks against the full credential. We have already pointed out that the entropy in all numeric PINs is very low for any length customers could be expected to key into a dial-pad. The additional problem is that after recovering the IVR version, the attacker now has a much easier time cracking the full password. Here is a concrete example: suppose an attacker mounts a brute-force attack against the numeric PIN and determines that a stolen hash corresponds to “3695707711036959.” That data point is very useful when attempting to recovery the full password, since many guesses are not compatible with that IVR version. For example, the attacker need not need waste any CPU cycles on hashing the candidate password “uqyEzqGCyNnUCWaU.” That candidate starts with “u” which would map to the digit “8” on a dial-pad. It would be inconsistent with something the attacker already knows: the first character of the real password maps to 3, based on the IVR version. Working backwards from the same observation, the attacker knows that they only need to consider guesses where the first symbol is one of {8, t, u, v, T, U, V}.

This makes life much easier for an attacker trying to crack hashes. The effort to recover a full password is not even twice the amount required to go after the IVR version. Revisiting the example of 16 character random passwords: we noted that unrestricted entries have about 105 bits of entropy while the corresponding IVR version is capped to around 53 bits. That means once the simplified IVR PIN is recovered by trying 2**53 guesses at most, the attacker only needs another 2**(105-53) == 2**52 guesses to hit on the full password. This is only about 50% more incremental work— and on average the correct password will be discovered halfway through the search. In other words, having a weak “intermediate” target to attack has turned an intractable problem (2**105 guesses) into two eminently solvable sub-problems.

CP

Saved by third-party cookies: when phishing campaigns make mistakes

(Or, that rare instance when third-party cookies actually helped improve security.)

Third-party cookies have earned a bad reputation for enabling widespread tracking and advertising driven surveillance online— one that is entirely justified. After multiple half-hearted attempts to deprecate them by leveraging its browser monopoly with Chrome, even Google eventually threw in the towel. Not to challenge that narrative but this blog provides an example of an unusual incident where third-party cookies actually helped protect consumers, protecting them from an ongoing phishing attack. While this phishing campaign occurred in real life, we will refer to the site in question as Acme, after the hypothetical company in Wile E Coyote.

Recap on phishing

Image for: Recap on phishing

Phishing involves creating a look-alike, replica of a legitimate website in order to trick customers into disclosing sensitive information. Common targets are passwords which can be used to login to the real website by impersonating the user. But phishing can also go after personally identifiable information such as a credit-cards or social-security numbers directly, since those are often monetizable on their own. The open nature of the web makes it trivial to clone the visual appearance of websites for this purpose. One can simply download every image, stylesheet, video from that site, stash the same content on a different server controlled by the attacker and point users into visiting this latter copy. (While this sounds sinister, there are even legitimate use cases for it such as mirroring websites to reduce load or protect them from denial-of-service-attacks.)

Important point to remember is that visuals can be deceptive: the bogus site may be rendered pixel-for-pixel identical inside the browser view. The address bar is one of the only reliable clues to the provenance of the content; that is because it is part of the user-interface controlled 100% by the browser. It can not be manipulated by the attacker. But good luck spotting the difference between login.acme.com, loginacme.com, acnne.com or any of the dozen other surprising ways to confuse the unwary. Names that look “close” at the outset can be controlled by completely unrelated entities, thanks to the way DNS works.

What makes phishing so challenging to combat is that the legitimate website is completely out of the picture at the crucial moment the attack is going on. The customer is unwittingly interacting with a website 100% controlled by an adversary out to get them, while under the false impression that they are dealing with a trusted service they have been using for years. Much as the real site may want to jump in with a scary warning dialog to stop their customer from making a crucial judgment error, there is no opportunity for such interventions. Recall that the customer is only interacting with the replica. All content the user sees is sourced from the malicious site, even if it happens to be a copy of content originally copied from the legitimate one.

Rookie mistake

Image for: Rookie mistake

Unless that is, the attacker makes a mistake. That is what happened with the crooks targeting Acme: they failed to clone all of the content and create a self-contained replica. One of the Acme security team members noticed that the malicious site continued to reference content hosted from the real one. Every time a user visited the phishing site, their web browser was also fetching content from the authentic Acme website. That astute observation paved the way for an effective intervention.

In this particular phishing campaign, the errant reference back to the original site was for a single image used to display a logo. That is not much to work with. On the one hand, Acme could detect when the image was being embedded from a different website, thanks to the HTTP Referer [sic] header. (Incidentally referrer-policies today would interfere with that capability.) But this particular image consumed precious little screen real estate, only measuring a few pixels across. One could return an altered image— skull and crossbones, mushroom cloud or something garish— but it is very unlikely users would notice. Even loyal customers who have the Acme logo committed to memory might not think twice if a different image appears. Instead of questioning the authenticity of the site, they would attribute that quirk to a routine bug or misguided UI experiment.

It is not possible to influence the rest of the page by returning a corrupt image or some other type of content. For example it is not possible to prevent the page from loading or redirect it back to the legitimate login page. Similarly one can not return some other type of content such as javascript to interrupt the phishing attempt or warn users. (Aside: if the crooks had made the same mistake by sourcing a javascript file from Acme, such radical interventions would have been possible.)

Remember me: cookies & authentication

Image for: Remember me: cookies & authentication

To recap, this is the situation Acme faces:

  1. There is an active phishing campaign in the wild
  2. Game of whack-a-mole ensues: Acme security team continues to report each site to browser vendors and hosting companies and Cloudflare (Crooks are fond of using Cloudflare, because it acts as a proxy sitting in front of the malicious site, disguising its true origin and making it difficult for defenders to block it reliably.) Attacker responds by changing domain names and resurfacing the exact same phishing page under a different domain name.
  3. Every time a customer visits a phishing page, Acme can observe the attack happening in real time because its servers receive a request
  4. Despite being in the loop, Acme can not meaningfully disrupt the phishing page. It has limited influence over the content displayed to the customer

Here is the saving grace: when customers reach out to the legitimate Acme website in step #3 as part of the phishing page, their web browser sends along all cookies. Some of those cookies contain information that uniquely identifies the specific customer. That means Acme finds out not only that some customer visited a phishing site, but it can find out exactly which customer did. In fact there were at least two such cookies:

  • “Remember me” cookie used to store email address and expedite future logins by filling in the username field in login forms.
  • Authentication cookies set after login. Interestingly, even expired cookies are useful for this purpose. Suppose Acme only allowed login sessions to last for 24 hours. After the clock runs out, customer must reauthenticate by providing their password or MFA again. Authentication cookies would have embedded timestamps reflecting those restriction. In keeping with the policy of requiring “fresh” credentials, after 24 hours that cookie would no longer be sufficient for authenticating the user. But for the purpose of identifying which user is being phished, it works just fine. (Technical note: “expired” here is referring to the application logic; the HTTP standard itself defines an expiration time for cookies after which point the browser deletes that cookie. If a cookie expires in that sense, it would not be of much use— it becomes invisible to the server.)

This gives Acme a lucky break to protect customers from the ongoing phishing attack. Recall that Acme can detect when incoming request for the image is associated with the phishing page. Whenever that happens, Acme can use the accompanying cookies to look up exactly which customer has stumbled onto the malicious site. To be clear, Acme can not determine conclusively whether the customer actually fell for phishing and disclosed their credentials. (There is a fighting chance the customer notices something off about the page after visiting it, and stops short of giving away their password. Unfortunately that can not be inferred remotely.) As such Acme must operate on the worst-case assumption that phishing will succeed and place preemptive restrictions on the account, such as temporarily suspending logins or restricting dangerous actions. That way, even if the customer does disclose their credentials and the crooks turn around to “cash in” those credentials by logging into the genuine Acme website, they will be thwarted from achieving their objective.

Third-party stigma for cookies

Image for: Third-party stigma for cookies

There is one caveat to the availability of cookies required to identify the affected customer: those cookies are now being replayed in a third-party context. Recall that the first vs third-party distinction is all about context: whether a resource (such as image) being fetched for inclusion on a webpage is coming from the same site as the overall owner of the page, called “top level document.” When an image is fetched as part of a routine visit to Acme website, it is a first-party request because the image is hosted at the same origin as the top-level document. But when it is being retrieved by following a reference from the malicious replica, it becomes a third-party request.

Would existing Acme cookies get replayed in that situation and reveal the identity of the potential phishing victim? That answer depends on multiple factors:

  1. Choice of browser. At the time of this incident, most popular web browsers freely replayed cookies in third-party contexts, with two notable exceptions:
    • Safari suppressed cookies when making these requests.
    • Internet Explorer: As the lone browser implementing P3P, IE will automatically “leash” cookies if they are set without an associated privacy policy: cookies will be accepted, but only replayed in first-party contexts.
  2. User overrides to browser settings. While the preceding paragraph section describes the default behavior of each browser, users can modify these to make them more or less stringent.
  3. Use of “samesite” attribute. About 15 years after IE6 inflicted P3P and cookie management on websites, a proposed update to the HTTP cookie specification finally emerged to standardize and generalize its leashing concept. But the script was flipped: instead of browsers making unilateral decisions to protect user privacy, website owners would declare whether their cookies should be made available in third-party contexts. (One can imagine which way advertising networks— crucially dependent on third-party cookie usage for their ubiquitous surveillance model— leaned on that decision.)

Luckily for Acme, the relevant cookies here were not restricted by the samesite attribute. As for browser distribution, IE was irrelevant with market share in the single digits, primarily restricted to enterprise users in managed IT environments, a far cry from the target audience for Acme. Safari on the other hand did have a non-negligible share, especially among mobile clients since Apple did not allow independent browser implementations such as Chrome at the time. (That restriction would only be lifted in 2024 when the EU reset Apple’s expectations around monopolistic behavior.)

In the end, it was possible to identify and take evasive actions for the vast majority of customers known to have visited the malicious website. More importantly, intelligence gathered from one interaction is frequently useful in protecting other customers, even when the latter are not directly identified as being targeted by an attack. For example, an adversary often has access to a handful of IP4 addresses when they are attempting to cash-in stolen credential. When an IP address is observed attempting to impersonate a known phishing victim, every other login from that IP can be treated with higher suspicion. Detection mechanisms can be invaluable even with less than 100% coverage.

Verdict on third-party cookies 

Image for: Verdict on third-party cookies

Does this prove third-party cookies have some redeeming virtue and the web will be less safe when— or at this rate, if— they are fully deprecated? No. This anecdote is more the exception proving the rule. Advertising industry has been rallying in defense of third-party cookies for over a decade, spinning increasingly desperate and far-fetched scenarios. From the alleged death of “free” content (more accurately, ad-supported content where the real product being peddled are the audience eyeballs for ad networks) to allegedly reduced capability for detecting fraud and malicious activity online, predictions of doom have been a constant part of the narrative.

To be clear: this incident does not in any way provide more ammunition for such thinly-veiled attempts at defending a fundamentally broken business model. For starters, there is nothing intrinsic to phishing attacks that requires help from third-party cookies for detection. The crooks behind this particular campaign made an elementary mistake: they left a reference to the original site when cloning the content for their malicious replica. There is no rule that says other crooks are required to follow suit. While this is an optimistic assumption built into defense strategies such as canary tokens, new web standards have made it increasingly easier to avoid such mistakes. For example content security policy allows a website to precisely delineate which other websites can be contacted for fetching embedded resources. It would have been a trivial step for crooks to add CSP headers and prevent any accidental references back to the original domain, neutralizing any javascript logic lurking in there to alert defenders.

Ultimately the only robust solution for phishing is using authentication schemes that are not vulnerable to phishing. Defense and enterprise sectors have always had the option of deploying PKI with smart-cards for their employees. More recently consumer-oriented services have convenient access to a (greatly watered-down) version of that capability with passkeys. Jury is out on whether it will gain any traction or remain consigned to niche audience owing to the morass of confusing, incompatible implementations.

CP

QCC: Quining C Compiler

Automating quine construction for C

Image for: Automating quine construction for C

A quine is a program that can output its own source code. Named after the 20th century philosopher Willard Van Orman Quine and popularized in Godel, Escher, Bach, writing quines has quickly become a quintessential bit of recreational coding. Countless examples can be found among the International Obfuscated C Code Contest (IOCCC) entries. While quines are possible in any Turing complete programming language— more on that theoretical result below— writing them can be tricky due to the structural requirements placed on the code for a valid quine. Success criteria is very strict: the output must be exactly identical to the original source, down to spacing and newlines. A miss is as good as a mile. The starting point for this blog post is a search for simplifying the creation of arbitrary quines. For concreteness, this proof of concept will focus on C. But the underlying ideas extend to other programming languages.

Since most interesting quines have some additional functionality besides being able to output their own code, it would be logical to divide development into two steps:

  1. Write the application as one normally would
  2. Convert it into a quine by feeding it through a compiler. (More accurately, a “transpiler” or “transcompiler” since the output itself is another valid program in the same language.)

Motivating example

Consider the canonical example of a quine. It takes no inputs and simply prints out its own source code to standard out. Here is a logical, if somewhat naive, starting point in C:

Minor problem: this is not a valid C program. Doing all the heavy lifting is a mystery function “get_self” that is not declared— much less defined— anywhere in the source file. (Historical aside: C used to permit calling such undeclared functions, with an implicit assumption of integer return type. Those liberties were revoked with the C99 standard. In any case, adding a declaration will merely postpone the inevitable: the compile-time error about undefined symbols becomes a link-time error about unresolved symbols.)

What if we could feed this snippet into another program that automatically transform it into a valid C program working as intended? Before going down that path, it is important to clarify the success criteria for a “working” quine. Informally we are looking for a transformation with these properties:

  • Accepts as input an “almost valid” C program with one undefined function get_self(). This function takes no arguments and returns a nil-terminated C string.
  • Outputs a valid C program with identical functionality
  • That new program includes a valid C implementation of  get_self() which returns the entire source code of the modified program, including the newly added function.

There are two subtleties here: First the implementation must return the source code of the modified program, incorporating all additions and changes made by the transformation. Printing the original, pre-transformed source would not qualify as a true quine. Second, the implementation provided for the mystery function get_self() must operate at source level. There are trivial but OS dependent ways to convert any program into a quine by mucking with the binary executable, after the compilation is done. For example, the ELF format for Linux executables allows adding new data sections to an existing file. One could take the source code and drop that into the compiled binary as a symbol with a specific name such as “my_source_code.” This allows for a simple  get_self() implementation that parses the binary for the current executable, searching for that symbol in data sections and converting the result into a C string. Expedient as that sounds, such approaches are considered cheating and do not qualify as true quines according to generally accepted definition of a self-replicating program. Informally, the  “quineness” property must be intrinsic to the source. In the hypothetical example where one is playing games with ELF sections, the capability originated with an after-the-fact modification of the binary and is not present anywhere in the source. Another example of cheating would be to commit the source code to an external location such as Github and download it at runtime. These are disqualified under our definition.

QCC: Quining C Compiler

Image for: QCC: Quining C Compiler

qcc is a proof-of-concept that transforms arbitrary C programs to create valid quines according to the rules sketched above. For example, running the pre-quine sample from the last section through qcc yields a valid quine:

Looking at the transformed output, we observe the original program verbatim in the listing (lines 8-15) sandwiched between prepended and appended sections:

Changes preceding the original source are minimal:

  • Courtesy warning against manual edits, as any change is likely to break the quine property. This is optional and can be omitted.
  • Include directives for standard C libraries required by the get_self() implementation. This is also optional if the original source already includes them. (This PoC does not bother to check. Standard library headers commonly have include guards; second and subsequent inclusions become a noop.) Incidentally, includes can also be deferred until the section of source referencing them. There is no rule dictating that all headers must appear at the start; it is merely a style convention most C/C++ developers abide by.
  • Declaration of get_self(). Also can be omitted if the original program has one.

More substantial additions appear after the original code:

  • Definition of a helper function for hex-decoding.
  • Two string constants carrying hex-encoded payloads. These could have been merged into a single string constant, but keeping them separate helps clarify the quine mechanism.
  • Definition of the get_self() function. This is the core of the implementation.

Hex-decoding the first string shows that it is just the the original program, with minor changes for headers/declaration prepended and the helper function appended. There is nothing self-referential or unusual going on. But the second hex payload when decoded is identical to the function get_self(). That is the tell-tale of sign quines: inert “data” strings mirroring code that gets compiled into executable instructions. Note that while the first string constant is a function of the original input program, the second one is identical for all inputs. (This is why separating them helps clarify the mechanism.)

Making a quine out of the quining compiler

Image for: Making a quine out of the quining compiler

Careful readers will note that qcc itself is a C program, but the initial version referenced above is not itself a quine. That will not stand. One natural option is to add a new command line flag that will induce qcc to output its own source code instead of quining another program.

Boot-strapping that has one tricky aspect: one needs a functioning qcc executable before it can quine its own source code. While this can be achieved by maintaining two different versions of the compiler, it is possible to get by with a single version with some judicious use of preprocessor tricks and conditional compilation. The trick is defining a preprocessor macro and surrounding any code paths that reference quine-like behavior with #ifdef guards referencing that macro:

  1. Add an option to qcc to define this macro explicitly in the transformation when outputting a quine. That capability will come in handy for any application that needs to be a valid C program in its pre-quine state.
  2. Compile with default settings, resulting in the preprocessor macro being undefined and leaving those sections out of the build. (That is a good thing: otherwise compiler errors will result from the nonexistent self-referential function referenced in those lines.)
  3. Run this original, pre-quine qcc binary on its own source code, and specify that the preprocessor macro is to be defined in the output.
  4. Save and recompile the transformed output as the new qcc. No need to explicitly define the preprocessor macro via compiler flags; it is already hardwired into the transformed source.

This final version can now output its own source, in addition to quining other C programs:

Caveats and design options

Image for: Caveats and design options

As this is a proof of concept, there are caveats:

  • It only operates on single files. As such it can not be used to create multiquines, where a collection of more than one program has access to the source code of every other program in the collection. That case can be handled by first concatenating them into a single program and some preprocessor tricks. Straight concatenation alone would result in an invalid C program, due to multiple definitions for main, not to mention any other conflicting symbol used. But conditional compilation with #ifdef allows activating only a subset of code during compilation.
  • Choice of hex encoding is arbitrary. It is certainly not space-efficient, doubling the size of the original input. On the other hand, using hex avoids the visual clutter/confusion caused by having strings that look like valid C code. Raw C strings also have the drawback that special characters such as double quotes and backslashes must be escaped.  While decoding is free in the sense that the C compiler takes care of it, helper functions are still needed for outputting escaped strings. Hex is simple enough to encode/decode in a handful of lines compared to more elaborate options such as base64 or compression, both of which would require more code inlined into every quine or pull in new library dependencies.
  • Implementation of get_self() is not thread safe, due to use of static variables. If there is a race condition where multiple threads race to execute the first-time initialization code, the string holding the source code representation will get computed multiple times. All threads will still receive the correct value, but extra memory will be wasted for unused versions.
  • String constants are currently emitted as a single line, which may overflow the maximum allowed depending on compiler. (C standard requires support for at least 4K characters and most compilers in practice far exceed that limit.)
  • Finally: QCC transformations are easily recognizable. There is no attempt to hide the fact that a quine is being created. Functions and variable identifiers have meaningful, intuitive names. This is problematic in contexts such as IOCC where obfuscation and brevity are virtues.

Post-script: excursion on Kleene’s recursion theorem

Image for: Post-script: excursion on Kleene’s recursion theorem

Stepping back, qcc is an implementation of a transform that is guaranteed to exist by virtue of a result first proven in 1938, namely Kleene’s second-recursion theorem. While that theorem was originally stated in the context of recursive functions, here we state it in an equivalent setting with Turing machines.

Consider a Turing machine T that computes a function of two inputs x and y:

T(x, y) 

Kleene’s result states that there is a Turing machine S which computes a function of just one input, such that the behavior of S on all inputs is closely related to the behavior of T:

x S(x) = T(x, #S)

where #S is the Turing number for the machine S— in other words, a reference to S itself. This statement says that for all inputs, S behaves just like the original machine T acting on the first input, with the second input hard-wired to its own (that is, of S) Turing number.

Since programming languages are effectively more convenient ways to construct Turing machines, we can translate this into even more familiar territory. Suppose we have a program P written in a high-level language which takes two inputs:

P(x, y) output

Kleene’s theorem guarantees the existence of a program Q which takes a single input x and computes:

Q(x) = P(x, source(Q))

Here source(Q) is the analog of Turing numbers for high-level programming languages, namely a textual representation of the program Q in the appropriate alphabet, such as ASCII or Unicode.

For a concrete example, consider replicating a recent IOCCC entry: write a program that outputs its own SHA512 hash. At first this seems impossible, barring a catastrophic break in SHA512. As a cryptographic hash function, SHA512 is designed to be one-way: given a target hash, it is computationally infeasible to find a preimage input that would produce that target when fed into SHA512. Given the difficulty of that basic problem, finding a “self-consistent” program such that its own SHA512 hash is somehow contained in the source code seems daunting. But Kleene’s result makes it tractable. As a starting point, it is easy enough to write a program that receives some input (for example, from console) and outputs the SHA512 hash of that input. Squinting a little, we can view this as a degenerate function on two inputs. The first input is always ignored while the second input is the one processed through the hash function:

P(x, y) SHA512(y)

Kleene’s recursion theorem then guarantees the existence of a corresponding program Q:

Q(x) = P(x, source(Q)) = SHA512(source(Q))

In other words, Q prints a cryptographic hash of its own source code.

Given this background, we have a theoretical view on QCC: For C programs that can be expressed in a single compilation unit, QCC constructs another C program that is the “fixed-point” guaranteed to exist by Kleene’s second recursion theorem, with the second input hard-wired to the source code of the new program.

CP

From TPM quotes to QR codes: surfacing boot measurements

The Trusted Platform Module (TPM) plays a critical role in measured boot: verifying that the state of a machine after booting into the operating system. This is accomplished by a chain of trust rooted in the firmware, with each link in the chain recording measurements of the next link in the TPM before passing control to its successor. For example, the firmware measures option ROMs before executing each one, then the master-boot record (for legacy BIOS) or GPT configuration (for EFI) before handing control over to the initial loader such as the shim for Linux, which in turn measures the OS boot-loader grub. These measurements are recorded in a series of platform-configuration registers or PCRs of the TPM. These registers have the unusual property that consecutive measurements can only be accumulated; they can not be overwritten or cleared until a reboot. It is not possible for a malicious component coming later in the boot chain to “undo” a previous measurement or replace it by a bogus value. In TPM terminology one speaks of extending a PCR instead of writing to the PCR, since the result is a function of both existing value present in the PCR— the history of all previous measurements accumulated so far— and current value being recorded.

At the end of the boot process, we are left with a series of measurements across different PCRs. The question remains: what good are these measurements?

TPM specifications suggest a few ideas:

  • Remote attestation. Prove to a third-party that we booted our system in this state. TPMs have a notion of “quoting”— signing a statement about the current state of the PCRs, using an attestation key that is bound to that TPM. The statement also incorporates a challenge chosen by our remote peer, to prove freshness of the quote. (Otherwise we could have obtained the quote last week and then booted a different image today.) As side-note: remote attestation was one of the most controversial features of the TPM specification, because it allows remote peers to discriminate based on software users are running. 
  • Local binding of keys. TPM specification has an extensive policy language around controlling when keys generated on a TPM can be used. In the basic case, it is possible to generate an RSA key that can only be used when a password is supplied. But more interestingly, key usage can be made conditional on the value of a persistent counter, the password associated with a different TPM object (this indirection allows changing password all at once on multiple keys) or specific values of PCRs. Policies can also be combined using logical conjunctions and disjunctions.

PCR policies are the most promising feature for our purposes. hey allow binding a cryptographic key to a specific state of the system. Unless the system is booted into this exact state— including the entire chain from firmware to kernel, depending on what is measured— that key is not usable. This is how disk-encryption schemes such as Bitlocker and equivalent DIY-implementations built on LUKS work: the TPM key encrypting the disk is bound to some set of PCRs. More precisely, the master key that is used to encrypt the disk is itself “wrapped” using a TPM key that is only accessible when PCRs are correct. Upshot of this design is that unless the boot process results in the same exact PCR measurements, disk contents can not be decrypted. (Strictly speaking, Bitlocker uses another way to achieve that binding. TPM also allows defining persistent storage areas called “NVRAM indices.” In the same way usage policies can be set on PCR, NVRAM indices can be associated with an access policy such that their contents are only readable if PCRs are in a given state.)

To see what threats are mitigated by this approach, imagine a hypothetical Bitlocker-like scheme where PCR bindings are not used and a TPM key exists that can decrypt the boot volume on a laptop without any policy restrictions. If that laptop is stolen and an adversary now has physical access to the machine, she can simply swap out the boot volume with a completely different physical disk that contains a Linux image. Since that image is fully controlled by the attacker, she can login and run arbitrary code after boot. Of course that random image does not contain any data from the original victim disk, so there is nothing of value to be found immediately. But since the TPM is accessible from this second OS, attacker can execute a series of commands to ask the TPM to decrypt the wrapped-key from the original volume. Absent PCR bindings, the TPM has no way to distinguish between the random Linux image that booted and the “correct” Windows image associated with that key.

The problem with PCR bindings

Image for: The problem with PCR bindings

This security against unauthorized changes to the system comes at the cost of fragility: any change to PCR values will render TPM objects unusable, including those that are “honest.” Firmware itself is typically measured into PCR0 and if a TPM key is bound to that PCR, it will stop working after the upgrade. In TPM2 parlance, we would say that it is no longer possible to satisfy the authorization policy associated with the object. (In fact, since firmware upgrades are often irreversible to avoid downgrading to vulnerable versions, that policy is likely never satisfiable again.) The extent of fragility depends on selected PCRs and frequency of expected changes. Firmware upgrades are infrequent, they are increasingly integrated with OS software update mechanism such as fwupd on Linux. On the other hand, Linux Integrity Measurement Architecture or “IMA” feature measures key operating system binaries into PCR10. That measurement can change frequently with kernel and boot-loader upgrades. In fact since IMA is configurable in what gets measured, it is possible to configure it to measure more components and register even minor OS configuration tweaks. There is intrinsic tension between security and flexibility here: the more OS components are measured, fewer opportunities left for an attacker to backdoor the system unnoticed. But it also means fewer opportunities to modify that system since any change to a measured component will brick keys bound to those measurement.

There are some work-arounds for dealing with this fragility. In some scenarios, one can deal with an unusable TPM key by using an out-of-band backup. For example, LUKS disk encryption supports multiple keys. In case the TPM key is unavailable, the user can still decrypt using a recovery key. Bitlocker also supports multiple keys but MSFT takes a more cautious approach, recommending that full-disk encryption is suspended prior to firmware updates. That strategy does not work when the TPM key is the only valid credential enabling a scenario. For example when an SSH or VPN key is bound to PCRs and the PCRs change, those credentials need to be reissued.

Another work-around is using wildcard policies. TPM2 policy authorizations can express very complex statements. For example wildcard policies allow an object to be used as long as an external private-key signs a challenge from the TPM. Similarly policies can be combined using logical AND and OR operators, such that a key is usable either based on correct PCR values or a wildcard policy as fallback. In this model, decryption would normally use PCR bindings but in case the PCRs have changed, some other entity would inspect the new state and authorize use of the key if those PCRs look healthy.

Surfacing PCR measurements

Image for: Surfacing PCR measurements

In this proof-of-concept, we look at solving a slightly orthogonal problem: surfacing PCR measurements to the owner for that person make a trust decision. That decision may involve providing the wildcard authorization or something more mundane, such as entering the backup passphrase to unlock their disk. More generally, PCR measurements on a system can act as a health check, concisely capturing critical state such as firmware version, secure-boot mode and boot-loader used. Users can then make a decision about whether they want to interact with this system based on these data points.

Of course simply displaying PCRs on screen will not work. A malicious system can simply  report the expected healthy measurements while following a different boot sequence. Luckily TPMs already have a solution for this, called quoting. Closely related to remote attestation, a quote is a signed statement from the TPM that includes a selection of PCRs along with a challenge selected by the user. This data structure is signed using an attestation key, which in turns is related to the endorsement key that is provisioned on the TPM by its manufacturer. (The endorsement key comes with an endorsement certificate baked into the TPM to prove its provenance, but it can not be used to sign quotes directly. In an attempt to improve privacy, TCG specifications complicated life by requiring one level of indirection with attestation keys, along with an interactive challenge/response protocol to prove relationship between EK and AK.) These signed quotes can be provided to users after boot or even during early boot stages as datapoint for making trust decisions.

Proof-of-concept with QR codes

Image for: Proof-of-concept with QR codes

There are many ways to communicate TPM quotes to the owner of a machine: for example the system could display it as text on screen, write out the quote as regular file on a removable volume such as USB drive or leverage any network interface such as Ethernet, Bluetooth or NFC to communicate them. QR codes have the advantage of simplicity in only requiring a display on the quoting side and a QR scanning app on the verification side. This makes it particularly suited to interactive scenarios where the machine owner is physically present to inspect its state. (For non-interactive scenarios such as servers sitting in a datacenter, transmitting quotes over the network would be a better option.)

As a first attempt, we can draw the QR code on the login background screen. This allows the device owner to check its state after it has booted but before the owner proceeds to enter their credentials. The same flow would apply for unlocking the screen after sleep/hibernation state. First step is to point the default login image. Several tutorials describe customizing the login screen for Ubuntu by tweaking a specific stylesheet file. Alternatively there is the gdm-settings utility for a more point-and-click approach. The more elaborate part is configuring a task to redraw that image periodically. Specifically, we schedule a task to run on boot and every time the device comes out of sleep. This task will:

  1. Choose a suitable challenge to prove freshness. In this example, it retrieves the last block-hash from the Bitcoin blockchain. That value is updated every 10 minutes on average, can be independently confirmed by any verifier consulting the same blockchain and can not be predicted/controlled by the attacker. (For technical reasons, the block hash must be truncated down to 16 bytes, the maximum challenge size accepted by TPM2 interface.)
  2. Generate a TPM quote using the previously generated attestation key. For simplicity, the PoC assumes that AK has been made into a persistent TPM object to avoid having to load it into the TPM repeatedly.
  3. Combine the quote with additional metadata, most important one being actual PCR measurements. The quote structure includes a hash of the included PCRs but not the raw measurements themselves. Recall that our objective is to surface measurements so the owner can make an informed decision, not proving that they equal to a previously known reference value. If expected value was cast in stone and known ahead of time, one could instead use PCR policy to permanently bind some TPM key to those measurements instead, at the cost of fragility discussed earlier. (This PoC also includes the list of PCR indices involved in the measurement for easy parsing, but that is redundant as the signed quote structure already includes that.)
  4. Encode everything using base64 or another alphabet that most QR code applications can handle. In principle QR codes can encode binary data but not every scannerhandles this case gracefully.
  5. Convert that text into a PNG file containing its QR representation and write this image out on the filesystem where we previously configured Ubuntu to locate its background image.
TPM quote rendered as QR code on Ubuntu login screen

This QR code now contains sufficient information for the device owner to make a trust decision regarding the state of the system as observed by the TPM.

Corresponding verification steps would be:

  1. Decode QR image
  2. Parse the different fields and base64 decode to binary values
  3. Verify the quote structure using the public half of the TPM attestation key
  4. Concatenate actual PCR measurements including in the QR code, hash the resulting sequence of bytes and verify that this hash is equal to the hash appearing inside the quote structure. This step is necessary to establish that the “alleged” raw PCR measurements attached to the quote are, in fact, the values going into that quote.
  5. Confirm that the validated PCR measurements represent a trustworthy state of the system.

Step #5 is easier said than done, since it is uncommon to find public sources of “correct” measurements published by OEMs. Most likely one would take a differential approach, comparing against previous measurements from the same system or measurements taken from other identical machines in the fleet. For example, if applying a certain OS upgrade to a laptop in known healthy state in the lab produces a set of measurements, one can conclude that observing the same PCRs on a different unit from the same manufacturer is not unexpected occurrence. On the other hand, if a device mysteriously starts reporting a new PCR2 value—associated with option ROMs from peripheral devices that are loaded by firmware during boot stage— it may warrant further investigation by the owner.

Moving earlier in the boot chain

Image for: Moving earlier in the boot chain

One problem with using the lock-screen to render TPM quotes is that it is already too late for certain scenario. Encrypted LUKS partitions will already have been unlocked at that point, possibly by the user entering a passphrase or their smart-card PIN during the boot sequence. That means a compromised operating system already has full access to decrypted data by the time any QR code appears. At that point the QR code still has some value as a detection mechanism, as the TPM will not sign a bogus quote. An attacker can prevent the quote from being displayed, feign a bug or attempt to replay a previous quote containing stale challenges but these all produce detectable signals. More subtle attempts may wait until disk-encryption credentials have been collected from the owner, exfiltrate those credentials to attacker-controlled endpoint, fake a kernel panic to induce reboot back into a healthy state where the next TPM quote will be correct.

With a little more effort, the quote rendering can be moved to earlier in the boot sequence and give the device owner an opportunity to inspect system state before any disks are unlocked. The idea is to move the above logic into the initrd image which is a customizable virtual disk image that contains instructions for early boot stages after EFI firmware has already passed control to the kernel. Initrd images are customized using scripts that execute at various stages. By moving the TPM quote generation to occur around the same time as LUKS decryption, we can guarantee that information about PCR measurements are available before any trust decisions are made about the system. While the logic is similar to rendering QR quotes on the login screen, there are several implementation complexities to work around. Starting with the most obvious problem:

  • Displaying images without the benefit of a full GUI framework. Frame-buffer to the rescue. It turns out that this is already a solved problem for Linux: fbi and related utilities can render JPEG or PNGs even while operating in console mode by writing directly to the frame-buffer device. (Incidentally it is also possible to take screenshots by reading from the same device; this is how the screenshot attached below was captured.)
  • Sequencing, or making sure that the quote is available before the disk is unlocked.  One way to guarantee this is to force quote generation to occur as integral part of LUKS unlock operation. Systemd has removed support for LUKS unlock scripts, although crypttab remains an indirect way to execute them. In principle we could write a LUKS script that invokes the quote-rendering logic first, as a blocking element. But this would create a dependency between existing unlock logic and TPM measurement verification. (Case in point: unlocking with TPM-bound secrets used to require custom logic but is now supported out of the box with systemd-cryptenroll.)
  • Stepping back, we only need to make sure that quotes are available for the user to check, before they supply any secrets such as LUKS passphrase or smart-card PIN to the system. There is no point in forcing any additional user interaction, since a user may always elect to ignore the quote and proceed with boot. To that end this PoC handles quote generation asynchronously. It opens a new virtual terminal (VT) and brings it to the foreground. All necessary work for generating the QR code— including prompting the user for optional challenge— will take place in that virtual terminal. Once the user exits the image viewer by pressing escape, they are switched back to the original VT where the LUKS prompt awaits. Note there is no forcing function in this sequence: nothing stops the user from ignoring the quote generation logic and invoking the standard Ctrl+Alt+Function key combination to switch back to the original VT immediately if they choose to.
  • Choosing challenges. Automatically retrieving fresh quotes from an external, verifiable source of randomness such as the bitcoin blockchain assumes network connectivity. While basic networking can be available inside initrd images and even earlier during the execution of EFI boot-loaders, it is not going to be the same stack running as the operating system itself. For example, if the device normally connects to the internet using a wifi network with the passphrase stored by the operating system, that connection will not be available until after the OS has fully booted. Even for wired connections, there can be edge-cases such as proxy configuration or 802.1X authentication that would be difficult to fully replicate inside the initrd image.
    This PoC takes a different tack to work around networking requirement, by using a combination of EFI variables and prompting the user. For the sunny-day path, the EFI variable is updated using an OS task scheduled to execute at shutdown, writing the same challenge (eg most recent block-hash from Bitcoin) into firmware flash. On boot this value will be available for the initrd scripts to retrieve for quote generation. If the challenge did not update correctly eg during a kernel-panic induced reboot, the owner can opt for manually entering a random value from the keyboard.
TPM quote rendered as QR code during boot, before LUKS unlock

Another minor implementation difference is getting by without a TPM resource manager. When executing in multi-user mode, TPM access is mediated by the tabrmd service, which stands for “TPM Access Broker and Resource Manager Daemon.” That service has exclusive access to the raw TPM device typically at /dev/tpm0 and all other processes seeking to interact with the TPM communicate with the resource manager over dbus. While it is possible to carry over the same model to initrd scripts, it is more efficient to simply have our TPM commands directly access the device node since they are executing as root and there is no risk of contention from other processes vying for TPM access.

CP

Evading Safe Links with S/MIME: when security features collide

From an attacker perspective email remains one of the most reliable channels for reaching their targets. Many security breaches start out with an employee making a poor judgment call to open a dangerous attachment or click on a link from their inbox. Not surprisingly cloud providers of email service invest significant time in building security features to protect against such risks. Safe Links is part of the defenses built into MSFT Defender for Office 365. As the second word implies, it is concerned with links: specifically protecting users from following harmful links embedded in email messages.

Working from first principles, there are two ways one could go about designing that functionality:

  1. Validate the link when the email is first received by the cloud service
  2. Validate it when the message is read by the recipient.

In both cases the verb “validate” assumes there is some blackbox that can look at a website and pass judgment on whether it is malicious, perhaps with a confidence score attached. In practice that would be a combination of crowd-sourced blacklists— for example, URLs previously reported as phishing by other users— and machine learning models trained to recognize specific signals of malicious activity..

There are trade-offs to either approach. Scanning as soon as email is received (but before it is delivered to the user inbox) allows for early detection of attacks. By not allowing users to ever see that email, we can short circuit human factors and avoid the risk that someone may be tempted to click on the link. On the other hand, it runs into a common design flaw known as TOCTOU or time-of-check-time-of-use. Here of “time of check” is when the webpage is scanned. “Time of use” is when the user clicks on the link. In between the content that the link points at can change; what started out as a benign page can morph into phishing or serve up  browser exploits.

Cloaking malicious content this way would be trivial for attackers, since they have full control over the content returned at all times. At the time they send their phishing email, the  server could be configured to serve anodyne, harmless pages. After waiting a few hours— or perhaps waiting until the initial scan, which is quite predictable in the case of MSFT Defender— they can flip a switch to start the attack. (Bonus points for sending email outside business hours, improving the odds that the victim will not accidentally stumble on the link until after the real payload is activated.) There is also a more mundane possibility that the page never changes but the classifiers get it wrong, mistakenly labeling it as harmless until other users manually report the page as malicious. Validating links on every click avoids such hijinks, leveraging most up-to-date information about the destination.

Wrapping links

While validating links every time is the more sound design, it poses a problem for email service providers. They do not have visibility into every possible situation where users are following links from email. In the fantasy universe MSFT sales teams inhibit, all customers read their email on MSFT Outlook on PCs running Windows with a copy of Defender for Endpoint installed for extra safety. In the real world, enterprises have heterogenous environments where employees could be reading email on iPhones, Android, Macs or even on Linux machines without  a trace of MSFT software in the picture.

Safe Links solves that problem by rewriting links in the original email before delivering it to the customer inbox. Instead of pointing to the original URL, the links now point to a MSFT website that can perform checks every time it is accessed and only redirect the user to the original site if considered safe. Once the original copy of the message has been altered, it no longer matters which email client or device the user prefers. They will all render messages with modified links pointing back to MSFT servers. (There is a certain irony to MSFT actively modifying customer communications in the name of security, after running a FUD campaign accusing Google of merely reading their customers’ messages. But this is an opt-in enterprise feature that customers actually pay extra for. As with most enterprise IT decisions, it is inflicted on a user population that has little say on the policy decisions affecting their computing environment.) 

Safe Links at work

Image for: Safe Links at work

To take an example, consider the fate of a direct link to Google when it appears in a message addressed to an Office 365 user with Defender policies enabled. There are different ways the link can appear, such as plaintext, as hyperlink from text section or image. and image with hyperlink. Here is the message according to the sender:


Here is the same message from the vantage point of the Office 365 recipient:

Visually these look identical. But on closer inspection the links have been altered. This is easiest to observe from the MSFT webmail client. True URLs are displayed in the browser status bar at the bottom of the window when hovering over links:


The alterations are more blatant when links are sent as plaintext email:


In this case Safe Links alters the visual appearance of the message, because the URL appears as plain- text instead of being encoded in the HTML mark-up which is not directly rendered.

Structure of altered links

Modifications follow the same pattern:

  • Hostname points to “outlook.com” a domain controlled by MSFT.
  • Original URL is included verbatim as one of the query-string parameters
  • Email address of the recipient also makes an appearance, in the next parameter
  • What is not obvious from the syntactic structure of the link but can be verified experimentally: this link does not require authentication. It is not personalized. Anyone— not just the original recipient— can request that URL from MSFT and will be served a redirect to Google. In other words these links function as semi-open redirectors.
  • There is an integrity check in the link. Tampering with any of the query-string parameters or removing them results in an error from the server. (“sdata” field could indicate a Signature over the Data field. It is exactly 32 bytes of base64-encoded content, consistent with an HMAC-SHA256 or similar MAC intended for verification only by MSFT.) This is what happens if the target URL is modified from Google to Bing:
Safe Link integrity checks: even MSFT’s own redirector refuses to send customers to Bing 🤷‍♂️

Bad interactions: end-to-end encryption

Image for: Bad interactions: end-to-end encryption

Given this background, now we can discuss a trivial bypass. S/MIME is a standard for end-to-end encryption and authentication of email traffic. It is ancient in Internet years, dating back to the 1990s. Emerging around the same time as PGP, it is arguably the “enterprisey” buttoned-down response to PGP, compliant with other fashionably enterprise-centric standards of its era. While PGP defined its own format for everything from public-keys to message structure, S/MIME built on X509 for digital certificates and CMS/PKCS7 for ciphertext/signature formatting. (Incidentally both are based on the binary encoding format ASN1.) As with PGP, it has not taken off broadly except in the context of certain high-security enterprise and government/defense settings.

At the nuts and bolts level, S/MIME uses public-key cryptography. Each participant has their own key-pair. Their public-key is embedded in a digital certificate issued by a trusted CA that can vouch for the owner of that public key. If Alice and Bob have each others’ certificates, she can encrypt emails that only Bob can read. She can also digitally sign those messages such that Bob can be confident they could only have originated with Alice.

How does all this interact with Safe Links? There is an obvious case involving encrypted messages: if an incoming message is encrypted such that Bob can only read it after decrypting with his private key— which no one else possesses— then the email service provider can not do any inspection, let alone rewriting, of hyperlinks present. That applies broadly to any link scanning implemented by a middle-man, not just MSFT Safe Links.  (Tangent: that restriction only holds for true end-to-end encryption. Cloud providers such as Google have muddied the waters with lobotomized/watered-down variants where private-keys are escrowed to the cloud provider in order to sign/decrypt on behalf of the end user. That is S/MIME in name only and more accurately “encraption.”)

In practice, this bypass is not very useful for a typical adversary running a garden-variety phishing campaign:

  • Most targets do not use S/MIME
  • For those who do— while few in number, these will be high-value targets with national security implications— the attacker likely does not have access to the victim certificate to properly encrypt the message. (Granted, this is security through obscurity. It will not deter a resourceful attacker.)
  • Finally even if they could compose encrypted emails, such messages are likely to raise suspicion. The presence of encryption can be used as a signal in machine learning models as contributing sign of malicious behavior, as in the case of encrypted zip files sent as attachments. Even the recipient may have heightened awareness of unusual behavior if opening the email requires taking unusual steps, such as entering the PIN for their smart-card to perform decryption.

Trivial bypass with signatures

But there is a more subtle interaction between Safe Links and S/MIME. Recall that digital signatures are extremely sensitive to any alteration of the message. Anything that modifies message content would invalidate signatures. It appears that the Safe Links design accounted for this and shows particular restraint: clear-text messages bearing an S/MIME signature are exempt from link-wrapping.

Interestingly, the exemption from Safe Links works regardless of whether the S/MIME certificate used for signing is trusted. In the above above screenshot from Outlook on MacOS, there is an informational message about the presence of a digital signature, accompanied by the reassuring visual indication of security, the ubiquitous lock icon. But taking a closer look via “Details” shows the certificate was explicitly marked as untrusted in MacOS keychain:

Similarly the web UI merely contains a disclaimer about signature status being unverifiable due to a missing S/MIME control. (Notwithstanding the legacy IE/ActiveX terminology of “control” that appears to be a reference to a Chrome extension for using S/MIME with webmail.) This limitation is natural: signature validation is done locally by the email client running on a user device. Safe Links operates in the cloud and must make a decision about rewriting links at the time the email is received, without knowing how the recipient will view it. Without full visibility into the trusted CAs associated with every possible user device, a cloud service can not make an accurate prediction about whether the signing certificate is valid for this purpose. MSFT makes a conservative assumption, namely that the signature may be valid for some device somewhere. It follows that signed messages must be exempt from tampering by Safe Links.

Exploiting cleartext signed messages to evade Safe Links is straightforward. Anyone can roll out their own CA and issue themselves certificates suitable for S/MIME. The main requirement is the presence of a particular OID in the extended key-usage (EKU) attribute indicating that the key is meant for email protection. While such certificates will not be trusted by anyone, the mere existence of a signature is enough to exempt messages from Safe Links and allow links to reach their target without tampering.

Crafting such messages does not require any special capability on the part of the target. Recall that they are signed by the sender— in other words, the attacker— but they are not encrypted. There is no need for the recipient to have S/MIME setup or even know what S/MIME is. Depending on the email client, there may be visual indications in the UI about the presence of a digital signature, as well as its trust status. (Worst case scenario, if there are obtrusive warnings concerning an untrusted signature, attackers can also get free S/MIME certificates from a publicly trusted CA such as Actalis. This is unlikely to be necessary. Given the lessons from Why Johnny Can’t Encrypt, subtle warnings are unlikely to influence trust decisions made by end users.)

Options for mitigation

Image for: Options for mitigation

While this post focused on cloud-hosted Exchange, the same dilemma applies to any so-called “email security” solution predicated on rewriting email contents: it can either break S/MIME signatures or fail-open by allowing all links in signed messages through unchanged. Even the weak, key-escrow model espoused by companies such as Google is of little help. GMail can decrypt incoming messages on behalf of a customer of Google Apps, if the customer willingly relinquishes their end-to-end confidentiality and hands over their private key to Google. But GMail still can not re-sign an altered message that was originally signed by unaffiliated party.

Given the rarity of S/MIME, a pragmatic approach is to allow enterprises to opt into the first option. If their employees are not setup for S/MIME and have no expectation of end-to-end authentication in the first place, this functionality is introducing pure risk with no benefit. In that scenario it makes sense for Exchange to not only rewrite the link, but remove signatures altogether to avoid confusion.

That will not fly in high-security environments where S/MIME is actually deployed and end-to-end encryption is important. In that case, more fine grained controls can be applied to cleartext signed messages. For example, the administrator could require that cleartext signed messages are only allowed if the sender certificate was issued by a handful of trusted CAs.

CP

Understanding Tornado Cash: code as speech vs code in action

Tornado Cash is a mixer on the Ethereum network. By design mixers obfuscate the movement of funds. They make it more difficult to trace how money is flowing among different blockchain addresses. In one view, mixers improve privacy for law-abiding ordinary citizens whose transactions are otherwise visible for everyone else in the world to track. A less charitable view contends that mixers help criminals launder the proceeds of criminal activity. Not surprisingly Tornado found a very happy customer in North Korea, a rogue nation-state with a penchant for stealing digital assets in order to evade sanctions. Regulators were not amused. Tornado Cash achieved the dubious distinction of becoming the first autonomous smart-contract to be sanctioned by the US Treasury. Its developers were arrested and put on trial for their role in operating the mixer. (One has already been convicted of money laundering by a Dutch court.)

Regardless of how Tornado ends up being used in the real world, lobbying groups have been quick to come to the defense of its developers. Some have gone so far as to cast the prosecution as an infringement on constitutionally protected speech, pointing to US precedents where source code was deemed in scope of first amendment rights. Underlying such hand-wringing is a slippery slope argument. If these handful of developers are held liable for North Koreans using their software to commit criminal activity, then what about the thousands of volunteers who are publicly releasing code under open-source licenses? It is almost certain that somebody somewhere in a sanctioned regime is using Linux to further the national interests of those rogue states. Does that mean every volunteer who contributed to Linux is at risk of getting rounded up next? 

This is a specious argument. It brushes aside decades of lessons learned from previous controversies around DRM and vulnerability disclosure in information security. To better understand where Tornado crosses the line, we need to look at the distinction between code as speech and code in action.

“It’s alright Ma— I’m only coding”

Image for: “It’s alright Ma— I’m only coding”

There is a crucial difference between Tornado Cash and the Linux operating system, or for that matter open-source applications such as the Firefox web browser. Tornado Cash is a hosted service. To better illustrate why that makes a difference, let’s move away from blockchains and money transmission, and into a simpler setting involving productivity applications. Imagine a not-entirely-hypothetical world where providing word processing software to Russia was declared illegal. Note this restriction is phrased very generically; it makes no assumptions about the distribution or business model.

For example the software could be a traditional, locally installed application. LibreOffice is an example of an open-source competitor to the better-known MSFT Office. If it turns out that somebody somewhere in Russia downloaded a copy of that code from one of the mirrors, are LibreOffice developers liable? The answer should be a resounding “no” for several reasons. First the volunteers behind LibreOffice never entered into an agreement with any Russian national/entity for supplying them with software intended for a particular purpose. Second, they had no awareness much less control over who can download their work product once it is released into the wild. Of course these points could also be marshaled in defense of Tornado Cash. Presumably they did not run a marketing campaign courting rogue regimes. Nor for that matter, did North Korean APT check-in with the developers first before using the mixer— at least, based on publicly known information about the case. 

But there is one objection that only holds true for the hypothetical case of stand-alone, locally installed software: that source-code downloaded from GitHub is inert content. It does not accomplish anything, until it is built and executed on some machine under control of the sanctioned entity. The same defense would not hold for a software-as-a-service (SaaS) offering such as Google Docs. If the Russian government switches to Google Docs because MSFT is no longer allowed to sell them copies of Word, Google can not disclaim knowledge of deliberately providing a service to a sanctioned entity. (That would hold true even if use was limited to the “free” version, with no money changing hands and no enterprise contract signed.) Google is not merely providing inert lines of code to customers. It has been animated into a fully functioning service, running on Google-owned hardware inside Google-owned data-centers. There is every expectation that Google can and should take steps to limit access to this service from sanctioned countries.

While the previous cases were cut and dry, gray areas emerge quickly. Suppose someone unaffiliated with the LibreOffice development team takes a copy that software and runs it on AWS as a service. With a little work, it would be possible for anyone in the world with a web browser to remotely connect and use this hosted offering for authoring documents. If it turns out such a hosted service is frequented by sanctioned entities, is that a problem? Provided one accepts that Google bears responsibility for the previous example, the answer here should be identical. But it is less straightforward who that responsible party ought to be. There is full separation of roles between development, hosting and operations. For Google Docs, they are all one and the same. Here code written by one group (open-source developers of LibreOffice) and runs on physical infrastructure provided by a different entity (Amazon.) But ultimately it is the operator who crossed the Rubicon. It was their deliberate decision to execute the code in a manner that would make its functionality publicly-accessible, including to participants who not supposed to have access. Any responsibility from misuse then lies squarely with the operator. The original developers are not involved. Neither is AWS. Amazon is merely the underlying platform provider, a neutral infrastructure that can be used for hosting any type of service, legal or not.

Ethereum as the infrastructure provider

Image for: Ethereum as the infrastructure provider

Returning to Tornado Cash, it is clear that running a mixer on ethereum is closer in spirit to hosting a service at AWS, than it is to publishing open-source software. Billed as the “world computer,” Ethereum is a neutral platform for hosting applications— specifically, extremely niche types of distributed application requiring full transparency and decentralization. As with AWS, individuals can pay this platform in ether to host services— even if those services are written in an unusual programming language and have very limited facilities compared to what can be accomplished with Amazon. Just like AWS, those services can be used by other participants with access to the platform. Anyone with access to the blockchain can leverage those services. (There is in some sense a higher bar. Paying transaction fees in ether is necessary to interact with a service on the Ethereum blockchain. Using a website hosted at AWS requires nothing more than a working internet connection.) Those services could be free or have a commercial angle— as in the case of the Tornado mixer, which had an associated TORN token that its operators planned to profit from.

The implication is clear: Tornado team is responsible for their role as operators of a mixing service, not for their part developers writing the code. That would have been protected speech if they had stopped short of deploying a publicly-accessible contract, leaving it in the realm of research. Instead they opted for “breathing life into the code,” by launching a contract, complete with a token model they fully controlled.

One key difference is the immutable nature of blockchains: it may be impossible to stop or modify a service once it has been deployed. It is as if AWS allowed launching services without a kill switch. Once launched, the service becomes an autonomous entity that neither the original deployer or Amazon itself can shut down. But that does not absolve the operator of responsibility for deploying the service in the first place. There is no engineering rule that prevents a smart-contract from having additional safeguards, such as the ability to upgrade its code to address defects or even to temporarily pause it when problems are discovered. Such administrative controls— or backdoors, depending on perspective— are now common practice for critical ethereum contracts, including stablecoins and decentralized exchanges. For that matter, contracts can incorporate additional rules to blacklist specific addresses or seize funds in response to law enforcement requests. Stablecoin operators do this all the time. Even Tether with its checkered history has demonstrated a surprising appetite for promptly seizing funds in response to court orders. The Tornado team may protest they have no way to shut down or tweak the service in response to “shocking” revelations that it is being leveraged by North Korea. From an ethical perspective, the only response to that protest is: they should have known better than to launch a service based on code without adequate safeguards in the first place.

Parallels with vulnerability disclosure

Image for: Parallels with vulnerability disclosure

Arguments over when developers cross a line into legal liability is not new. Information security has been on the frontlines of that debate for close to three decades owing to the question of proper vulnerability disclosure. Unsurprisingly software vendors have been wildly averse to public dissemination of any knowledge regarding defects in their precious products. Nothing offended those sensibilities more than the school of “full disclosure” especially when accompanied by working exploits. But try as they would like to criminalize such activity with colorful yet misguided metaphors (“handing out free guns to criminals!”) the consensus remains that a security researcher purely releasing research is not liable for the downstream actions of other individuals leveraging their work— even when the research includes fully weaponized exploit code ready-made for breaking into a system. (One exception has been the content industry, which managed to fare better, thanks to a draconian anti-circumvention measure in the DMCA. While that legislation certainly had a chilling effect on pure security research on copyright protection measures, in practice most of the litigation has focused on going after those committing infringement rather than researchers who developed code enabling that infringement.) 

Debates still continue around what constitutes “responsible” disclosure, where society can strike the optimal balance between incentivizing vendors to fix security vulnerabilities promptly without making it easier for threat actors to exploit those same vulnerabilities. Absent any pressure, negligent/incompetent vendors will delay patches arguing that risk is low because there is no evidence of public exploitation. (Of course absence of evidence is not evidence of absence and in any case, vendors have little control over when malicious actors will discover exploits independently.) But here we can step back from the question of optimal social outcomes, and focus on the narrow question of liability. It is not the exploit developer writing code but the person executing that exploit against a vulnerable target who ought to be held legally responsible for the consequences. (To paraphrase a bumper-sticker version of second amendment advocacy: “Exploits don’t pop machines; people pop machines.”) In the same vein, Tornado Cash team is fully culpable for their deliberate decision to turn code into a service. Once they launched the contract on chain, they were no longer mere developers. They became operators.

CP

Behavioral economics on Ethereum: stress-testing censorship resistance

Predicated on studying the behavior of real people (distinct from the mythical homo economicus of theory) behavioral economics has the challenge of constructing realistic experiments in a labarotory setting. That calls for signing up a sizable group of volunteers and putting them into an artificial situation with monetary incentives to influence their decision-making process. Could blockchains in general and Ethereum in particular help by making it easier to either recruit those participants or setup the experimental framework? In this blog post we explore that possibility using a series of hypothetical experiments, building up from simple two-person games to an open-ended version of the tragedy of the commons.

1. Simple case: the ultimatum game

Image for: 1. Simple case: the ultimatum game

The ultimatum game is a simple experiment involving two participants that explores the notion of fairness. The participants are randomly assigned to either “proposer” or “responder” role. A pool of funds is made available to the proposer, who has complete discretion on making an offer to allocate those funds between herself and the responder. If the responder accepts, the funds are distributed as agreed. If the responder vetos the offer— presumably for being too skewed towards the proposer— no one receives anything.

This experiment and its variations are notable in showing an unexpected divergence from the theoretical “profit maximization” model of economics 101. Given that the responder has no leverage, one would expect they will begrudgingly settle for any amount, including wildly unfair splits where the proposer decides keeps 99% of the funds. Given that the proposer is also a rational actor aware of that dynamic, the standard model predicts such highly uneven offers being made… and accepted. Yet that is not what experiments show: most offers are close to 50/50 and most responders outright reject offers that are considered too unequal. (This only scratches the surface of the complex dynamics revealed in the experiment. Subtle changes to the framing— such as telling the responders a tall tale about the split being randomly decided by a computer program instead of a sentient being— changes their willingness to accept unfair splits; possibly because one does not take “offense” at the outcome of a random process the same way they might react to the perceived greed of the fellow on the other side.)

Putting aside the surprising nature of the results, it is straightforward to implement this experiment in Ethereum. We assume both the proposer and responder have ethereum wallets with known addresses. In order to run the experiment on chain, researchers can deploy a smart-contract and fund it with the promised reward. This contract would have three functions:

  1. One only callable by the proposer, stating the intended allocation of funds.
  2. One only callable by the responder, accepting/rejecting that proposed allocation. Depending on the answer, the contract would either distribute funds or return the entire amount back to the experiment team. 
  3. For practical reasons, one would also include an escape-hatch in the form of a third function that can be called by anyone after some deadline has elapsed to recover the funds in case one or both subjects fail to complete the expected task. Depending on the which side reneged on their obligation, it would award the entire reward to the other participant.

There are some caveats that could influence the outcome: both participants must hold some ether already at their designated address, in order to make smart-contract calls. Alternatively the experimenters can supply just enough ETH to both volunteers to pay for the expected cost of those calls. But that runs the risk of participants deciding to abscond with funds instead of proceeding with the experiment. (The responder in particular faces an interesting dilemma when confronted with an unfair split they are inclined to reject. On the one hand, registering their displeasure on-chain sends a message to the greedy proposer, at the cost of spending ETH they had been given for the experiment. On the other hand, simply transferring that ETH to a personal wallet allows the proposer to walk away with something but only at the cost of allowing the greedy proposer to keep 100% of the funds due to the assumed default.) This effect is diminished to the extent that the prize money up for grabs is much larger than the transaction fees the participants are required to part with. More generally, transaction fees determine whether running this experiment on-chain would be any more efficient from an experimental stance than enticing volunteers with free food.

More subtle is the effect of perceived privacy— or lack thereof— in influencing participant behavior. Would a proposer be less inclined to reveal their true colors and offer an unfair split when interacting on-chain versus in real life? On the one hand, blockchains are public: anyone can observe that a particular proposal was greedy. Having their actions permanently on the record for the whole world to observe may motivate participants to follow social mores. On the other hand, blockchain addresses are pseudonyms, without any identifying information. “Bravery of the keyboard” may result in fewer inhibitions about diverging from social expectations and making greedy offers when one is not directly interacting with other persons.

2. Open to all: the Ether auction

Image for: 2. Open to all: the Ether auction

The ultimatum game is played between two players. Running that experiment still requires finding suitable volunteers, pairing them up and launching a smart-contracts for each pair. (The contract will only accept inputs from the proposer and responder, and as such must have awareness of their address.) But there are other experiments which can be run without requiring any advance coordination, beyond that of publicizing the existence of a smart-contract that implements the experimental setup. In effect anyone with an ethereum wallet can make an independent decision on whether they want to participate.

Tullock auctions in general and the better-known “dollar auction” in particular are a case in point. As with all auctions, the highest bidder wins by paying their offer. But unlike most auctions, everyone else who loses to that winning bid are still required to part with the amount they offered. Given those unforgiving dynamics— everyone except the winner must pay and still end up with nothing in return— it seems illogical that anyone would play along. Now consider the “dollar auction,” a slight variant that is simple enough to be demonstrated in classroom settings. The professor holds up a $100 bill and offers to give it to the highest bidder, subject to Tullock auction rules with $1 increments for bids. (Side note: in the original version, only the second-highest bidder is forced to pay while all others are spared. That still does not alter the underlying competitive dynamic between the leading two bidders.) Once the students get over their sense of disbelief that their wise teacher— economics professor by training, of all things—  is willing to part with a $100 bill for a pittance, this looks like a great deal. So one student quickly comes up with the minimum $1 bid, spotting an easy $99 profit. Unfortunately the next student sees an equally easy way to score $98 by bidding $2. Slightly less profit than the first bidder imagined achieving if they remained uncontested, but still a decent amount that any rational participant would rightfully chase after. It follows that the same logic could motivate fellow students to bid $3, $4 and higher amounts in an ever increasing cycle of bids. But even before the third student jumps in, there is one person who has suddenly become more motivated to escalate: the initial bidder. Having lost the lead, they are faced with the prospect of losing $1— since everyone must pay their bid, win or lose. That fundamentally alters their expected value calculus, compared to other students currently sitting on the sidelines. A student who has not entered the auction must decide between zero gain (by remaining a spectator in this experiment) or jumping into the fray to chase after the $100 being dangled in front of the class. By contrast a student who has been already out-bid is looking at a choice between a guaranteed loss of their original or escalating the bid to convert that losses into gains.

Informally these auctions distill the notion of “arms race” or “winner-take-all” situations, where multiple participants expend resources chasing after an objective but only one of them can walk away with the prize while everyone else sees their effort wasted. Economists cited examples where such dynamics are inadvertently created, for example in the competition between American cities competing to win HUD grants. (Presumably the same goes for countries making elaborate bids to host the next Olympics or FIFA World Cup, considering that only one of them will be granted the opportunity.)

Shifting this classroom exercise into Ethereum is straightforward: we create a smart-contract and seed it with the initial prize money of 1 ETH. The contract accepts bids from any address, provided it exceeds the current leading bid by some incremental amount. In fact the bid can be automatically deduced from the amount of ether sent. Participants do not need MetaMask or similar noncustodial wallets with contract invocation capabilities. They can simply send ETH to the contract from an online centralized exchange. Contracts can be design with a default payment function that is triggered when any ether is sent, even without an explicit function call. That default fallback function can take care of the book-keeping associated with bids, including distinguishing between initial vs updated bids. If an address was encountered before, any subsequent calls with value attached are considered an increment on top of the original bid. (That is, if you bid 0.5 ETH and now want to raise that offer to 0.7ETH in order to counter someone else bidding 0.6 ETH, your second call only need to attach the delta 0.2 ETH.) At some previously agreed upon block-height or time, the contract stops accepting any more bids. The same fallback function can be invoked by anyone to “finalize” the outcome and send the prize money to the winner, and presumably any left over ETH from incoming bids to a GoFundMe campaign for funding behavioral economics education.

While this seems straightforward, there are some subtle differences from the class-room setting due to the way Ethereum operates. These can distort the auction dynamics and create alternative ways to maximize profit. The most problematic one arises from the interaction of an auction deadline with the block-by-block manner ethereum transactions are added to the blockchain. One way to guarantee not being outbid is to make sure one is submitting the last bid. If the auction is set to conclude at a particular block height or even instance in time (ethereum blocks contain a timestamp) all rational actors would engage in a game of chicken, waiting until that last block to submit their bids.

In fact, since transactions are visible in mempool before they are mined, rational actors would continue to bide there time even during the interval for that block. Optimal strategy calls for waiting out other bidders to send their transactions, and only submit a higher bid after all of those suckers prematurely tipped their hand. Naturally this runs a different risk that the bid may arrive too late, if the block has already been constructed and attested without your bid getting the last laugh. (That also raises a question of what the contract should do with bids arriving after the deadline. It can bounce the ETH as a favor to the slow poke or alternatively, eat up the ETH without taking the bid into consideration as punishment for playing this game of chicken.)

Even this model is too simplistic for not taking MEV into account. Ethereum blocks are not simply constructed out of some “fair” ordering of all public transactions sitting around in the mempool according to gas paid. Miners have been operating a complex ecosystem of revenue optimization by accepting out-of-band bids— that is, payment outside the standard model of fees— to prioritize specific transactions or reorder transactions within a block. (This system became institutionalized with the switch to proof-of-stake.) Why would participants compete over ordering within a block? Because transactions are executed in the order they appear in the block, and there may be a significant arbitrage opportunity for the first mover. Suppose a decentralized exchange has temporarily mispriced a particular asset because the liquidity pools got out of balance. One lucky trader to  to execute a trade on that DEX can monopolize all of the available profit caused by the temporary dislocation. If the competition to get that transaction mined first were conducted out in the open— as the fee marketplace originally operated— the outcome would be a massively wasteful escalation in transaction fees. Those chasing the arbitrage opportunity would send transactions with increasing fees to convince miners to include their TX ahead of everyone else in the block. This is incredibly inefficient: while multiple such transactions can and will be included in the next block, only the earliest one gets to exploit the price discrepancy on the DEX and collect the reward, while everyone else ends up wasting transaction fees and block space for no good reason. If that dynamic sounds familiar, it is effectively a type of Tullock auction conducted in mempool.

MEV solves that problem by having market participants submit individual transactions or even entire blocks to miners/validators through a semi-private interface. Instead of competing with each other out in the open and paying for TX that did not “win” the race to appear first, MEV converts the Tullock auction into a garden-variety sealed bid first price auction where the losers are no longer stuck paying their bid.

By the same logic, MEV provides a way out of the bizarre dynamics created by the Ether auction: instead of publicly duking it out with bids officially registered on-chain or even sitting in mempool, rational participants can use a service such as Flashbots to privately compete for the prize. There is still no guarantee of winning— a block proposer could ignore Flashbots and just choose to claim the prize for themselves by putting their own bid TX first— but MEV removes the downside for the losers.

3. Tragedy of the commons: Luring Lottery

Image for: 3. Tragedy of the commons: Luring Lottery

Here is a different experiment that MEV will not help with. For real life inspiration, consider an experiment Douglas Hofstadter ran in Scientific American in 1983. (Metamagical Themas covers this episode in great detail.) SciAm announced a lottery with a whopping million dollar bounty to be awarded to the lucky winner. The rules were simple enough: any one can participate by sending a postcard with a positive integer written on the card. Their odds of winning are proportional to the submitted number. There is one catch: the prize money is divided by the total of all submission received. In the “best case” scenario— or worst case, depending on the publisher perspective— only one person participates and sends in the number 1. At that point SciAm may be looking at chapter 11, because that lucky reader is guaranteed to collect the promised million dollars. Alternatively in a “worst case” scenario, millions of readers participate or a handful of determined contestants submit astronomically large numbers to win at all costs. SciAm lives to publish another issue, as the prize money is diluted down to a few dollars.

Unlike the dollar auction, there is nothing subtle about the dynamics here. The contest is manifestly designed to discourage selfish behavior: submitting large numbers (or even making any submission at all, since a truly altruistic reader could deliberately opt not to participate) will increase individual chances of winning, but reduce the overall prize. While the magazine editors were rightfully concerned about having to payout a very large sum if most readers did not take the bait, Hofstadter was not flustered.

No one familiar with Garrett Hardin’s fundamental insight in “The tragedy of the commons” will be surprised by what transpired: not only were there plenty of submissions, some creative readers sent entries that were massive— not expressed as ordinary numbers but defined with complex mathematical formulas, to the point where the contest organizers could not even compare these numbers to gauge probabilities. Not that it mattered, as the prize money was correspondingly reduced to an infinitesimal fraction of a penny. No payouts necessary.

Again this experiment can be run as an Ethereum smart-contract and this time MEV will not help participants game the system. As before, we launch a contract and seed it with the prize money ahead of time. It has two public functions:

  • One accepts submissions for a limited time. Unlike the SciAm lottery judged by sentient beings, we have to constrain submissions to actual integers (no mathematical formulas) and restrict their range to something the EVM can comfortably operate on without overflow, such as 2200. Any address can submit a number. They can even submit multiple entries; this was permitted in the original SciAm lottery which serves as the inspiration for the experiment. The contract keeps track of totals submitted for each address, along with a global tally to serve as denominator when calculating winning odds for every address.
  • The second function can be called by anyone once the lottery concludes. It selects a winner using a fair randomness source, with each address weighted according to the ratio of their total submissions to the overall sum. It also adjusts the payout according to the total submissions and sends the reward to the winner, returning the remainder back to the organizers. (Side note: getting fair randomness can be tricky on chain, requiring a trusted randomness beacon. Seemingly “random” properties such as the previous block hash can be influenced by participants trying to steer the lottery outcome to favor their entry.)

Given this setup, consider the incentives facing every Ethereum user when the lottery is initially launched. There is a million dollars sitting at an immutable contract that is guaranteed to pay out the prize money according to predefined rules. (Unlike SciAm, the contract can not file for bankruptcy or renege on the promise.) One call to the contract is all it takes to participate in the lottery. Win or lose, there is no commitment of funds beyond the transactions fees required for that call. This is a crucial difference from the Tullock auction where every bid represents a sunk cost for the bidder. Downsides are capped regardless of what other network participants are doing.

Also unlike the Tullock auction, there is little disincentive to participate early. There is no advantage to be gained by waiting until the last minute and submitting the last entry. Certainly one can wait to submit a number much higher than previous entries to stack the odds, but doing so also reduces the expected payout. Not to mention that since submissions are capped in a finite range, participants can simply submit the maximum number to begin with. Meanwhile, MEV can not help with the one problem every rational actor would like to solve: prevent anyone else from joining the lottery. While it would be great to be the only participant or at least one of a handful of participants with an entry, existing MEV mechanisms can not indefinitely prevent others from participating in the lottery. At best bidders could delay submissions for a few blocks by paying to influence the content of those blocks. It would require a coordinated censorship effort to exclude all transactions destined to the lottery contract for an extended period of time.

If anything MEV encourages early participation. No one can be assured they will have the “last say” in the construction of the final block before the lottery stops accepting submissions. Therefore the rational course of action is to submit an entry earlier to guarantee a chance at winning. In fact there may even be optimal strategies around “spoiling” the payout for everyone else immediately with a large entry, such that the expected reward for future entries is barely enough to offset transaction fees for any future participant. This is an accelerated tragedy of the commons— equivalent to the herdsmen from Hardin’s anecdote burning the grasslands to discourage other herdsmen from grazing their cattle on the same land.

Testing censorship resistance

Alternatively the ether auction and Luring Lottery serve as empirical tests of miner censorship on Ethereum. In both cases, rational actors have a strong incentive to participate themselves while preventing others from participating. This is because participation by others reduces the expected gain. For the ether auction, being outbid results in going from guaranteed profit to guaranteed loss. For the Tullock lottery, competition from other participants is not quite as detrimental but it undermines expected return in two ways: by reducing the probability of winning and slashing the prize money on offer. It follows that rational actors have an incentive to censor transactions from other participants.

If 100% reliable censorship is possible, then both experiments have a trivial winning strategy for the censor. For the Tullock auction, submit the minimum acceptable bid and prevent anyone else from making higher offers. Likewise for the Luring Lottery, send the number “1” and block anyone else from submitting an entry that would dilute the prize money.

On Ethereum, block proposers are in the best position to engage in such censorship at minimal cost. While they would be foregoing fees associated with the censored TX, that loss is dwarfed by the outsized gain expected from monopolizing the full rewards available from the hypothetical contract. Could such censorship be sustained indefinitely? This seems unlikely, even if multiple validators were colluding under an agreement to split the gains. It only takes a defection from a single proposer to get a transaction included. Validators could choose to pursue a risky strategy of ignoring the undesirable block, on the pretense that the proposer failed to produce a block in time as expected. They could then wait for a block from the next “compliant” proposer who follows the censorship plan. This approach will fail and incur additional penalties if other attesters/validators accept the block and continue building on top of it. Short of near universal agreement on the censorship plan— as with OFAC compliance— a coalition with sufficient validator share is unlikely to materialize. On the other hand 100% reliable censorship is not necessary for the Luring lottery: block proposers can not stop other proposers from participating when it is their turn, but they can certainly exclude any competing TX from their own blocks. That effectively limits participation to proposers or at best ethereum users aligned with a friendly proposer willing to share the prize. But such tacit collusion would be highly unstable: even if every proposer correctly diagnosed the situation and limited themselves to a single submission of “1” to avoid unnecessary escalation, there would still be an incentive to rig the lottery with a last-minute submission that dwarfs all previous entries. 

CP

[Edited: May 4th]