puma / puma-dev

A tool to manage rack apps in development with puma
BSD 3-Clause "New" or "Revised" License
1.73k stars 106 forks source link

`puma-dev` installs a Root CA that creates an attack vector for MITM attacks #215

Open itay-grudev opened 4 years ago

itay-grudev commented 4 years ago

puma-dev has a feature that allows developers to simulate https on arbitrary domains for testing purposes. The way this feature works is by installing a Root CA in your system that allows puma-dev to issue SSL certificates for arbitrary domain names on the fly. This allows developers to test as if their system was running on production environment with production URLs but still being served by localhost.

The security issues comes from the fact that the Root CA can be used to issue certificates for ANY domain name for a wide array of purposes. Additionally the private key used for issuing certificates is accessible with user level permissions which means that any program ran by the user can get hold of that private key and send it to a remote location.

The attack vector that can exploit this feature is the following:

A piece of compromised software, whether that is a proprietary program or an open source dependency of a dependancy (of a dependency) could copy the file:

/Users/$USER/Library/Application Support/io.puma.dev/key.pem

and send it to an attacker.

An attacker then can use this private key to issue certificates for any arbitrary domain and can potentially fake real services, software updates, etc. This means that the exploit provides an attacker with the ability to execute code on compromised machines.

In reality the users using puma-dev will be software developers, who often have access to staging and/or production systems. Therefore this attack can be used to obtain access to real-world applications and the personal data of enormous amount of users.

While this vulnerability is not trivial to exploit it presents an attack vector that significantly weakens the security of a system and can be used to obtain access to remote systems and execute code on them.

To fix affected systems one needs to delete the puma-dev key, cert and delete the certificate from their system. On MacOS this can be achieved with:

security find-certificate -c "Puma-dev CA" -a -Z | \    
  sudo awk '/SHA-1/{system("security delete-certificate -Z "$NF)}'
rm -rf ~/Library/Application\ Support/io.puma.dev/

Related: #192

Possible fixes include storing the puma-dev keys with root only access and asking for permission elevation every time a certificate needs to be issued.

rst0git commented 4 years ago

Hi @itay-grudev, thank you opening this issue!

Is this specific to Mac users?

dev/ssl_linux.go:

func TrustCert(cert string) error {
    fmt.Printf("! Add %s to your browser to trust CA\n", cert)
    return nil
}

dev/ssl_darwin.go:

func TrustCert(cert string) error {
    fmt.Printf("* Adding certification to login keychain as trusted\n")
    fmt.Printf("! There is probably a dialog open that requires you to authenticate\n")

    login, keychainError := LoginKeyChain()

    if keychainError != nil {
        return keychainError
    }

    err := exec.Command("sh", "-c",
        fmt.Sprintf(`security add-trusted-cert -d -r trustRoot -k '%s' '%s'`,
            login, cert)).Run()

    if err != nil {
        return err
    }

    fmt.Printf("* Certificates setup, ready for https operations!\n")

    return nil
}
itay-grudev commented 4 years ago

@rst0git It would seem that you are correct, the issue does indeed only affect OS X users.

evanphx commented 4 years ago

Hello, yes this is true. This emulates how other such programs allow for TLS access. Do you have an alternate way to make it work?

itay-grudev commented 4 years ago

Possible fixes include storing the puma-dev keys with root only access and asking for permission elevation every time a certificate needs to be issued.

evanphx commented 4 years ago

I'm unsure how restricting access to the cert would change the security issue.

itay-grudev commented 4 years ago

@evanphx Currently any program ran by the user has access to read the contents of the root CA private key. This means that any software using with user level permisssions can issue certificates. Any app/game or malicious package can access the private key.

By restricting the private key to only be readible by root would mean that only the root user or sudo users (after providing password) will have access to the certificate. This way malicious software won't be able to access it without privilage escalation.

nonrational commented 4 years ago

Not sure to call this a bug or an enhancement. puma-dev is acting as intended, but I see how this behavior is non-ideal. AFAIK you'd need to be able to fake DNS as well to really take advantage of this (i.e. sign google.com pointing to a non-google IP with the trusted puma-dev CA).

By restricting the private key to only be readible by root

We can't just chown the key to root, as puma-dev still needs to read it into memory when it starts up. So, we'd need to start puma-dev as root, which seems like a far worse idea than the status quo.

In order to address this with file permissions, we'd want to set up a new unpriv'd user (e.g. puma-dev-runner), run the process under that user and have them own /usr/local/share/puma-dev/certs.{key,pem}. But, then, that user would need to have read/write access to all the folders symlinked into ~/.puma-dev in order to be able to keep symlinking the way we like it. And that seems, again, worse.

However, the somewhat novel X509 name constraints might be useful here. We might be able to create a CA that's ONLY able to sign TLDs / domains that you specify (e.g. .test) There's some anecdotal evidence that they're now supported on macOS and would go a long way to mitigating this issue.

I'm gonna do a little digging and report back.

nonrational commented 4 years ago

FWIW Negative constraints seem to work on macOS:

--- FAIL: TestMakeCert (1.00s)
    ssl_test.go:70:
            Error Trace:    ssl_test.go:70
            Error:          failed to verify certificate
            Test:           TestMakeCert
            Messages:       err: x509: a root or intermediate certificate is not authorized to sign for this name: DNS name "rack-hi-puma.localhost" is excluded by constraint "rack-hi-puma.localhost"
FAIL

edit: well, this works perfectly well in tests, it doesn't stop a browser (chrome, safari, etc) on macOS from trusting signed certs, even with name constraints. 😭

nonrational commented 3 years ago

Note: As of v0.15, puma-dev -uninstall will untrust-and-delete all "Puma-dev CA" certificates in your keychain and delete them from the filesystem.

I think a possible solution here is to store the keypair in the macOS keychain and borrow the solution that https://github.com/docker/docker-credential-helpers implements. That would give puma-dev persistent access to the keypair (without requiring elevated access to the entire keychain) and get the private key off the filesystem.

ThisIsMissEm commented 1 month ago

Would changing the Root CA to enforce name constrains be an option?

    PermittedDNSDomainsCritical [bool](https://pkg.go.dev/builtin#bool) // if true then the name constraints are marked critical.
    PermittedDNSDomains         [][string](https://pkg.go.dev/builtin#string)
    ExcludedDNSDomains          [][string](https://pkg.go.dev/builtin#string)
    PermittedIPRanges           []*[net](https://pkg.go.dev/net).[IPNet](https://pkg.go.dev/net#IPNet)
    ExcludedIPRanges            []*[net](https://pkg.go.dev/net).[IPNet](https://pkg.go.dev/net#IPNet)
    PermittedEmailAddresses     [][string](https://pkg.go.dev/builtin#string)
    ExcludedEmailAddresses      [][string](https://pkg.go.dev/builtin#string)
    PermittedURIDomains         [][string](https://pkg.go.dev/builtin#string)
    ExcludedURIDomains          [][string](https://pkg.go.dev/builtin#string)

Since that could ensure that only certificates are issued for the domain extensions like .test and .local, such that a certificate can't be issued for .com or something?

Although, browser support may vary: https://serverfault.com/questions/1012847/does-chrome-support-x509v3-permitted-name-constraints