oss-sec mailing list archives
croc: multiple issues in file sharing utility
From: Matthias Gerstner <mgerstner () suse de>
Date: Fri, 8 Sep 2023 15:37:55 +0200
Hello list, this report is about the Croc [1] file sharing utility. I have found a couple of security relevant issues in it that are not currently fixed. The upstream author doesn't have enough resources to address them on its own and wants to develop fixes in the open. Therefore I have created GitHub issues in the upstream project and publish the full report today. Only after finishing the review I found an older report [2] about Croc that states some of the same issues as in this report. These have been discovered independently of each other. 1) Review Scope and Motivation ============================== This report is based on Croc release 9.6.5 (v9.6.5 Git repository tag). Croc is a cross platform tool. I reviewed it with a focus on the Linux operating system. Croc was added to openSUSE Tumbleweed a while ago and the package's description caught my interest due to its promise to "easily and securely send files from one computer to another". I found issues in similar tools before like KDE Connect [3] or Warpinator [4]. 2) Introduction =============== Croc is a command line program that is supposed to allow secure and easy file transfer between computers. It connects two parties, the sender and the receiver of files, over an untrusted network. Sender and receiver establish a shared secret that needs to be exchanged over a second channel, which is not covered by Croc. Croc offers to automatically generate the shared secret in the style of a human readable phrase like: 1355-sunday-yoga-africa Alternatively the sender can also specify a custom free form shared secret that needs to have a minimum length of six characters. 2.1) Design Overview -------------------- Croc is a surprisingly complex utility, therefore this section offers a broader overview of how it works, to make clearer some of the later sections of the report. The communication between sender and receiver is always routed through what is called a relay instance of Croc. There exists a default public relay reachable on the Internet, which is used if nothing else is configured. Users can also host their own relay. If no explicit IP addresses are configured then the sender also runs a relay implicitly and communicates its presence via a zeroconf protocol. This implicit relay on the sender side is prefered by the receiver, if possible (i.e. if firewalls are not preventing this). While a relay can have its own password protection (`--pass` command line switch), there is a password "pass123" used by default i.e. relays will typically be accessible by anybody without authentication. The initial connection to a relay happens via TCP on the default port 9009. The protocol used here allows some unencrypted operations, like sending a "ping" message for testing the availablity of the service. For regular operation a PAKE (password authenticated) key exchange is performed based on a custom implementation for Croc. When this succeeds then a relay encryption context is available and encrypted messages can be exchanged between sender and relay, or receiver and relay, respectively. No authenticity checks are present at this stage i.e. the relay could also be a man-in-the-middle or otherwise harmful. The relay maintains "rooms" which are identifiers used for connecting pairs of sender and receiver that are interested to transfer files. Rooms are identified by a free form string. Sender and receiver communicate their desired room ID using their relay encryption context, i.e. the room IDs cannot be eavesdropped by other parties. On the sender/receiver side the room names are selected from the three character prefix of the shared secret. Once sender and receiver enter the same room at the relay, the relay switches into a "pipe" mode where it simply forwards all data between sender and receiver (full duplex) and no longer interprets the message contents. At this stage sender and receiver can communicate with each other and will perform another PAKE key exchange. If this succeeds then a second sender/receiver encryption context has been established. The encrypted communication can only take place if the shared secret matches. So on this level a sort of authenticity check between sender and receiver can be assumed, provided that the secret has been shared in a safe manner. Also the relay should not be able to access the cleartext of the data exchanged between sender and receiver. Thus the authenticity of the relay should not be relevant at this point regarding safety of integrity of the transferred data. Using the second level end to end encryption, the sender now tells the receiver which kind of files are about to be transferred (metadata). The receiver will ask the interactive user whether to accept the files. Then the files (which can also be directories or symlinks) specified by the sender will be created locally and the data received will be written to them. There is also support for transferring multiple files in ZIP files which will be decompressed transparently by the receiver. There is a file overwrite check in place for overwrite situations. The interactive user will be asked whether overwriting should take place or not. For the actual file paylaod transmission a file chunk algorithm is employed and the transfer is potentially carried out over multiple TCP connections by connecting to a sequence of ports available at the relay (ports 9010 to 9013 by default). For these additional TCP connections further PAKE encryption contexts will be setup, based on the shared secret. 3) Security Relevant Findings ============================= 3.1) Possible (Concealed) Creation of Files in Dangerous Path Locations ----------------------------------------------------------------------- If more than one file is transferred via Croc then, before the transfer starts, the receiver only sees a summary line about the files about to be received, like: Accept 2 files (159 B)? (Y/n) Only after confirming this dialog the full file reception list will be printed, like: Receiving (<-[ip]:[port]) file1 100% |████████████████████| (140/140 B, 610 B/s) 1/2 file2 100% |████████████████████| ( 0/ 1 B) 2/2 The Croc protocol allows the sender to specify arbitrary paths to be transferred. Via social engineering an attacker could attempt to transfer one or more malicious files in a larger file list, that otherwise looks unsuspicious. There is a file overwrite check in Croc that prevents existing files from being overwritten without user consent (at least by default, there's the `--overwrite` switch to disable the prompt). For not yet existing files there are no security boundaries though. So if e.g. `$HOME/.ssh/authorized_keys` is not existing yet on the receiver side, then the sender can transfer this, maybe unnoticed by the receiver. Even if the receiver notices it after the fact, it might be too late, and the attacker already had the chance to compromise the receiver's system. Two relevant pieces of information might be missing for an attacker in this scenario: the receiver's home directory location and its current working directory. Guessing or determining the receiver's home directory path via social engineering might be well within reach though. Simply implying that the CWD is the home directory might otherwise be a good guess. Also relative paths like `../.ssh/authorized_keys` can be transferred. An attacker has to be careful about this, though, because if the path reaches above the home directory, then "permission denied" errors will become visible on the receiver end, which are more likely to raise alarm. Fixing this kind of attack scenario is difficult, when trying to parse incoming file paths and to detect dangerous situations in userspace. Especially since there is also the possibility of symlinks and Croc even explicitly supports the creation of symlinks in its protocol. In Warpinator [4] the developers ended up using isolation techniques to lock the receiver process side in a specific download directory. Using mount namespaces (ideally an existing tool for creating a namespace jail) or Linux features like Landlock is probably the best solution for this problem. Since Croc is cross platform, implementing isolation can become a large effort though, since there are no shared APIs available for this. 3.2) Circumventing the Interactive File Overwrite Prompt -------------------------------------------------------- As mentioned in issue 3.1) there is a check on the receiver side of Croc whether an incoming file path will overwrite an existing file, and Croc will ask the user interactively whether it should be overwritten, if it already exists. This check works reasonably well, even if symlinks are involved. I did not look that deep into this though - due to the following finding I stopped spending time on finding further possible ways around the restriction. There might still linger attack surface in the parallelism of the chunk transfer (exploiting race conditions) or by aptly crafting the file metadata. There is a loophole in conjunction with the transparent ZIP transfer option though (`croc send --zip`). The sender alone decides if something will be zipped or not (somewhat confusingly flagged by the `FileInfo.TempFile` flag). The receiver will take whatever data has been transferred and will try to unzip it. This way even the overwrite check can be overcome, by placing creative paths into the ZIP archive. Even relative paths like `../../.ssh/authorized_keys` can be placed in the ZIP archive. The unzip operation will silently overwrite existing files. When combined with issue 3.1) then a potential attacker has a lot of freedom to attempt to trick the receiving party into harming its system. If an isolation technique as outlined in issue 3.1) is implemented, then the consequences of overwriting files should be less problematic - although it could still be surprising if previously downloaded files are overwritten with something else. As an additional protection measure only the receiver should decide whether ZIP files are handled, or not. Better controlling the unzip process to prevent overwrites would also be helpful. The safest approach would be to use a pristine empty directory for each new file transfer where nothing can be overwritten in the first place. 3.3) Escape Sequences in Filenames are not Filtered --------------------------------------------------- Filenames on Linux can contain arbitrary characters except for the path separator '/'. Thus filenames can also contain possibly dangerous characters like ASCII control codes (newline, linefeed, etc.) or even complete ANSI/CSI terminal escape sequences. On the Croc receiver side the filenames communicated by the sender side are accepted unfiltered and are also output on stdout during transmission. When the latter happens, the escape sequences are interpreted by the receiver's terminal and can lead to colored text, moving the cursor around or - if an insecure terminal emulator setup [5] is used - even arbitrary code execution can be achieved. In particular this issue is a nice addition to issues 3.1) and 3.2), since it allows to hide filenames of previously transmitted files on stdout, therefore making the attack less conspicuous. This is an example of how this can be done: # this moves the cursor up one line and performs a carriage return, thus # overwriting the previous line on the terminal sender $ touch "`echo -e '\033[1A\rharmless'`" sender $ touch "evil" sender $ croc send evil *harmless [...] receiver $ croc <shared-secret> receiver $ Accept 2 files (0 B)? (Y/n) Y harmless 100% |████████████████████| ( 0/ 1 B) 2/2 An interactive user will only see the "harmless" file, probably not noticing that a file seems to be "missing" in the output. To fix this Croc should filter filenames on the receiver side and either reject or replace any unsafe non-printable characters. 3.4) Use of Parts of the Shared Secret as Room Name --------------------------------------------------- The leading three characters of the shared secret are implicitly used to select a common "room name" at the relay so that sender and receiver can find each other (croc.go:827, croc.go:769, croc.go:572, croc.go:483). When using shared secrets generated by Croc this is fine, because they are formatted just so that the leading part of the secret will make up the room name, like in "1355-sunday-yoga-africa". The leading number is completely unrelated to the rest of the shared secret. For some reason only the leading three digits will be used for the room name, in this case "135", while the final "5" will remain unused. If a sender is selecting a custom shared secret then things can look different, though. Imagine selecting a secret like "MySecretPass". Now the relay will get "MyS" as room name. The relay is a possibly untrusted party that is not verified in the Croc protocol scheme (except if a relay password (`--pass`) is explicitly used). The room name is visible in cleartext to the relay, though. In the example of this custom secret, the room name reveals information about the rest of the shared secret used by sender and receiver. A malicious relay might thus be able to guess the shared secret or the cryptography may be otherwise weakened, allowing the relay to eavesdrop on the ongoing communication, or to impersonate the receiver. For the parallel file chunk transfer further room names of the form `<digest>-<port>` (croc.go:1174) are used for setting up connections on the relay transfer ports (`--ports` option). Here `<digest>` is the SHA256 digest of the five leading characters of the shared secret. So it would be the SHA256 digest of "1355-" or "MySec" in the examples above. Of the resulting digest only the leading six characters will be used for the room name. The relay might be able to make deductions about the relatively short five character input for the hash e.g. by building rainbow tables. Although there will be a lot of collisions that likely make a practical attack difficult, this feels risky overall. To fix this sender and receiver should always use a (sufficiently cryptographically secure) digest of the shared secret as room name. Generally it should be avoided to use the shared secret for anything else than setting up the cryptography - any use beyond that should be carefully considered and a safely derived value should be used. 3.5) Unencrypted "ips?" Message ------------------------------- As a typical part of the Croc protocol (if no explicit `--ip` is passed), the receiver will ask the sender about its locally assigned IP addresses via the `ips?` message (croc.go:792). This message and its reply are sent unencrypted. I assume there is no encryption, because the receiver might still switch the connection to a direct one, without going through a public relay, and setting up the encryption context twice might add additional latency, or additional code complexity. The message being unencrypted means, however, that the sender will send out cleartext information over the Internet, containing all locally assigned IP addresses. This might be an unexpected information leak for a range of users. It can reveal information about the structure of internal networks or otherwise offer information about the identity of the sender. To fix this, the encryption layer should be established before any other data is transferred between sender and receiver. 3.6) Explicit Evaluation of Wildcard Characters on the Sender Side CLI ---------------------------------------------------------------------- The Croc command line tool explicitly (re)evaluates wildcard glob characters in filename arguments (croc.go:262). This seems highly unusual to me, since normally the user's shell will expand wildcards, not the programs that the filenames are passed to. This means even if special characters are escaped on shell level, that Croc will still attempt to expand them. This only happens if a filename contains at least one `*` character. For a sender side user this could be surprising, if a filename actually contains an `*` character, that this will suddenly be expanded nevertheless. Although a bit far fetched it might still pose a social engineering attack vector, by tricking somebody into forwarding a strangely named file and make them unwittingly send more files than intended. The principle of least surprise is violated here and I would drop this logic, or execute it only in whatever use case this is helpful with. 3.7) Well known "pingRoom" in Relays ------------------------------------ This is not actually a security issue, since I couldn't find anything harmful to do with it. Still this aspect allows a relay to operate in a confusing state that should better not occur in the first place. For processing "ping" messages the relay uses a well known "pingRoom". However, a malicious sender can specify "pingRoom" also as an actual room name for transmitting files. First I thought this could be suitable for causing the relay to crash, by tricking it into deleting the "pingRoom". The relay actually attempts to do this, but the "pingRoom" is a global variable and not stored in the dynamic room map, thus nothing bad happens. This "pingRoom" should be excluded from being used for actual file transfers. On a side topic I would generally restrict the length of room name to a short string. And maybe introduce a special namespace like rooms starting with a "." that cannot be used for file transfers, to generalize this concept of special rooms. 3.8) Shared Secret Passed on Command Line ----------------------------------------- On the sender side a custom shared secret can be specified via the `croc send --code <SECRET>` command line. On the receiver side the shared secret, custom or not, is typically passed on the command line using the `croc <SECRET>` command line . The latter invocation variant is actively advocated by the output displayed on the sender side like: On the other computer run croc <SECRET> By passing the shared secret on the command line it will become visible on the host's process list for all local users (on Linux and most UNIX like systems). On a multi user system this might allow other local users to get knowledge of the shared secret and to receive the files instead of the intended recipient. To fix this, the shared secret should be read from stdin, a local file or an environment variable, even though it will be less intuitive than passing it on the command line. 4) Cryptography =============== I am not a cryptography expert, therefore I did not even start to analyze the formal security of the PAKE protocol implementation in Croc. Still I have made some higher level observations regarding the use of cryptography in Croc that are collected in this section. 4.1) Custom PAKE Protocol ------------------------- The implementation of a custom PAKE protocol for this utility generally seems risky to me. The finding in [2] shows that problematic issues can linger here. The use of the fixed `weakKey` (tcp.go:176) and why this is cryptographically secure should be well documented I believe. 4.2) Mixing of Encrypted and Unencrypted Messages ------------------------------------------------- One aspect that I found problematic about the communication protocol in Croc is that encrypted and unencrypted messages are mixed on the same channel and there is no clear transition point as to when the channel is secured. I would find it better if the channel would be secured right away, no matter what, and no unencrypted messages would be transmitted after that. There is one point after which unencrypted communication is rejected in croc.go:1233. This is at a relatively late stage of the protocol though. 4.3) The same Channel is used for Various Different Kinds of Contexts --------------------------------------------------------------------- Similarly I found it difficult to follow that the same TCP connection is potentially used for many different kind of contexts: unencrypted communication (towards the relay and later towards the peer), the encryption context setup with the relay and the encryption context setup with the peer. The shared secret is then also potentially reused on additional TCP ports on the port range used to transfer file chunks. This mix of contexts is sometimes difficult to follow in the code, like the same functions are used for multiple contexts and the actual context is determined only by boolean flags. I don't know how to improve on this without major changes to the current protocol scheme, though. Maybe the code could at least be refactored to make the different contexts clearer. 4.4) Authenticity Completely Relies on the Shared Secret -------------------------------------------------------- An aspect that always makes we worry in programs like Croc, that don't rely on a trusted party, is that the authenticity verification of the communication partner completely relies on a shared secret that somehow needs to be exchanged over a second channel. I am missing some heads up in the Croc documentation that makes users aware how important a sufficiently safe exchange of the shared secret is for the safety of the application. Maybe also adding an explicit peer authenticity verification step would make this clearer than just relying on the shared secret and making it work "like magic". A prompt in the spirit of what openSSH does for host authenticity verification. 4.5) Minimum Shared Secret Length can Lead to very short PAKE Password Input ---------------------------------------------------------------------------- For custom shared secrets the minimum length is currently set to six characters. When using such a short shared secret then, due to the use of the prefix of the shared secret as room names, `pake.InitCurve()` will only receive a single byte as password input e.g. in `croc.go:1112` and `croc.go:224`. A single byte of input seems critically low. Requiring longer shared secrets could improve upon this. But also avoiding to use cleartext portions of the shared secret for other things would make it possible to use the full shared secret for the PAKE setup. 5) CVE Assignments ================== I have requested CVEs from Mitre for the more tangible issues 3.1 through 3.5 and issue 3.8. I will publish them here once they are available. 6) Timeline =========== 2023-08-08: I started the review of the Croc codebase. 2023-08-31: I reported my findings to the upstream author and offered coorindated disclosure. He replied quickly and stated that he wants to address the issues publicly without a formal embargo. 2023-09-04: I replied and agreed to open issues in the GitHub upstream project. I asked about his wishes about CVE assignments, but did not get a reply anymore (yet). 2023-09-08: I created public GitHub issues corresponding to the findings in my report. I requested CVEs from Mitre for the more tangible issues found. I published the full report. 7) References ============= [1]: https://github.com/schollz/croc.git [2]: https://redrocket.club/posts/croc [3]: https://www.openwall.com/lists/oss-security/2020/10/13/4 [4]: https://www.openwall.com/lists/oss-security/2023/04/26/1 [5]: https://www.openwall.com/lists/oss-security/2017/05/01/13 -- Matthias Gerstner <matthias.gerstner () suse de> Security Engineer https://www.suse.com/security GPG Key ID: 0x14C405C971923553 SUSE Software Solutions Germany GmbH HRB 36809, AG Nürnberg Geschäftsführer: Ivo Totev, Andrew McDonald, Werner Knoblich
Attachment:
signature.asc
Description:
Current thread:
- croc: multiple issues in file sharing utility Matthias Gerstner (Sep 08)
- Re: croc: multiple issues in file sharing utility Salvatore Bonaccorso (Sep 21)