There's another way to do that: require client certificates. The MITM proxy cannot present the client certificate to the server, since it doesn't have the corresponding private key.
Unfortunately, the user interface for client certificates is a complete pain, so they are rarely used. But they're the only true way for a server to make sure it's talking directly to a client, in the same way server certificates can allow a client to make sure it's talking directly to a server.
Yeah, I've considered client certificates for this very reason, but as you mention, the UI is truly awful.
I'd like to see TLS Channel ID to become available in browsers for this very reason, but it still doesn't seem to have much buy-in.
Someone once proposed that browsers add a JS API for getting information about the certificate with which the page was loaded. The discussion is on a mailing list somewhere, you can find it if you try. It was shot down on the grounds that a page may contain resources from many origins and made over many different TLS connections, or come from cache, etc. So the idea of "what connection" a page was loaded over isn't necessarily very clear cut. If this API were added it would enable JS-based detection, which obviously wouldn't be foolproof against a determined attacker but would enable some monitoring of this issue to be done.
(Another hare-brained scheme I conceived of to detect this sort of thing is to have a special domain with a HPKP header set that doesn't match the certificates served, and a report-uri. If the browser makes a report, it means it's a normal browser acting correctly. If it doesn't, it means the page was served under a custom CA and HPKP has been disabled. Of course this is incredibly erratic as a means: it doesn't work if a browser doesn't support HPKP, you have to wait for the report to be made, or decide how long you wait before you conclude it's not coming, etc.)
>The MITM proxy cannot present the client certificate to the server, since it doesn't have the corresponding private key.
The MITM proxy is operated by the same department that has root on all the endpoints it's intercepting. If necessary, the "endpoint protection" product will grab the private key, or just scrape the details of the browser session from the browser's memory rather than at network level.
Grabbing the client certificate private key is not always possible; it can be on a smart card (and even when on a file, it could be password-protected). Also, the operators of the MITM proxy do not necessarily have root on the endpoints (they can require the users to add the CA certificate themselves), and even if they have, scraping the session keys from the often-updated browser is not trivial.
Unfortunately, the user interface for client certificates is a complete pain, so they are rarely used. But they're the only true way for a server to make sure it's talking directly to a client, in the same way server certificates can allow a client to make sure it's talking directly to a server.