Page MenuHome GnuPG

gpg-agent --default-cache-ttl option doesn't expire cache in specific situations
Closed, ResolvedPublic

Description

--default-cache-ttl set the time a cache entry is valid.

This time is checked inside housekeeping() function.

This function is executed only when agent_put_cache() or agent_get_cache() functions are called.

In normal GPG usage like:

  1. User sign file using command line
  2. User back to normal work

no more commands are send to gpg-agent so housekeeping() function is not executed.

Basically this means that in most cases passphrase is stored in gpg-agent memory longer than default 600 seconds until new command is send to gpg-agent.

This can be used in specific situations by attacker, who has access to current user session, for stealing private key without knowing passphrase.

Working POC for this attack under Windows is available here:

https://github.com/kacperszurek/gpg_reaper

Steps to reproduce:

  1. Start new gpg-agent instance with small cache time:
cd c:\Program Files (x86)\GnuPG\bin
taskkill /im gpg-agent.exe /F
gpg-agent.exe --daemon --default-cache-ttl 2
  1. Sign some test file
gpg --sign test.txt
  1. You will be asked for passphrase
  1. Repeat step 2. Each time pinentry will ask you for passphrase because our 2 seconds cache expired
  1. Run GPG Reaper
powershell -ExecutionPolicy Bypass -File Gpg-Reaper.ps1 -OutputFile out.txt

As you can see we dumped private key.

Conclusions

housekeeping() function should be called every few seconds so passphrase will be removed from memory as soon as it expired.

Probably handle_tick() function might be good for this.

Revisions and Commits

Event Timeline

werner triaged this task as High priority.
werner added a subscriber: werner.

Well, if you have access to the user's memory you are lost anyway. Should be fixed, though.

Fixed. But you need to wait at least 4 seconds even with a 2 seconds ttl. Will go in 2.2.6 in about 3 weeks. Thanks for reporting.

I wonder if this also works similar in a multi user system:

UserA signs something. -> Agent caches.
UserA logs out -> agent is killed (no cleanup?)
UserB runs your reaper or something similar to extract UserA's key.

I don't know if there are windows mechanisms which would prevent that.

Probably you are right but I don't know Windows internals that much.

Even if this memory is not zeroed attacker needs to:

  1. Read whole RAM
  2. Somehow find valid structure there, probably using some kind of patterns

And this will work only if Windows doesn't allocate this memory to other process in the meantime. So it's nondeterministic.

My method is much simpler and can be used for example during penetration testing because you only need to read specific process memory and you known where structure is stored.