Skip to content
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

mitmproxy Certificate Issue #120

Closed
aclindsa opened this issue Sep 7, 2018 · 7 comments
Closed

mitmproxy Certificate Issue #120

aclindsa opened this issue Sep 7, 2018 · 7 comments

Comments

@aclindsa
Copy link

aclindsa commented Sep 7, 2018

I'm attempting to use openconnect with GlobalProtect and Okta and am having some issues. I've got mitmproxy setup to attempt to see what's going on, but GlobalProtect on Windows says "The server certificate is invalid. Please contact your IT administrator" when I attempt to use it over the proxy.

I saw you mentioned writing a python shim in #78 (comment) to work around this certificate issue - do you have any more detailed pointers on that, or even an example script you could share?

I've dug around a little, and my initial guess is that I need to figure out how to script mitmproxy to replace the "-----BEGIN CERTIFICATE-----" block with my mitmproxy cert?

@aclindsa aclindsa changed the title mitmproxy mitmproxy Certificate Issue Sep 7, 2018
@dlenski
Copy link
Owner

dlenski commented Sep 7, 2018

I've dug around a little, and my initial guess is that I need to figure out how to script mitmproxy to replace the "-----BEGIN CERTIFICATE-----" block with my mitmproxy cert?

Yep, sounds like you've figured out the right answer here 👍. I did have a script that did this specifically at some point, but it was a couple years ago, and it appears that I've lost it…

The simple mitmproxy examples should be a pretty good place to start.

@aclindsa
Copy link
Author

aclindsa commented Sep 7, 2018

I was able to get the certificate replaced in the response using the following (cert_replacer.py):

from bs4 import BeautifulSoup
from mitmproxy import http

class CertReplacer:
    def response(self, flow: http.HTTPFlow) -> None:
        xml = BeautifulSoup(flow.response.content, "lxml-xml")

        if xml.policy and xml.policy.cert:
            xml.policy.cert.string = "\n" + flow.server_conn.cert.to_pem().decode("utf-8")
            flow.response.content = str(xml).encode("utf8")

addons = [CertReplacer()]

and:

# mitmproxy -s cert_replacer.py

I've verified in the mitmproxy response output that the certificate is indeed being replaced in the XML response for the POST to https://vpn.example.com/global-protect/getconfig.esp, but I am still getting the same "The server certificate is invalid. Please contact your IT administrator" message on the client. I've verified that I added the mitmproxy root certificates to Windows correctly by visiting https://vpn.example.com/ in Microsoft Edge and do not observe any certificate errors/warnings and rebooted Windows entirely for good measure.

  1. Does my approach look like it's doing the same thing you previously did?
  2. Do you see anything obviously wrong here or have any other ideas (I'm attaching the sanitized response from getconfig.esp below in case anything looks out of the ordinary to you)?
<?xml version="1.0" encoding="utf-8"?>
<policy>
<portal-name>EXAMPLE-GP-PORTAL</portal-name>
<portal-config-version>4100</portal-config-version>
<version>4.1.4-13                                                        </version>
<client-role>global-protect-full</client-role>
<agent-user-override-key>****</agent-user-override-key>
<root-ca>
<entry name="EXAMPLE-LINUX-VPN">
<cert>
-----BEGIN CERTIFICATE-----
MIIGZDCCBUygAwIBAgIIM35AgIy/tuYwDQYJKoZIhvcNAQELBQAwgbQxCzAJBgNV
[snip]
G2RzopMtgJg=
-----END CERTIFICATE-----
</cert>
<install-in-cert-store>yes</install-in-cert-store>
</entry>
</root-ca>
<connect-method>on-demand</connect-method>
<on-demand>yes</on-demand>
<refresh-config>yes</refresh-config>
<refresh-config-interval>24</refresh-config-interval>
<authentication-modifier>
<none/>
</authentication-modifier>
<authentication-override>
<accept-cookie>no</accept-cookie>
<generate-cookie>no</generate-cookie>
<cookie-encrypt-decrypt-cert/>
</authentication-override>
<use-sso>yes</use-sso>
<ip-address/>
<host/>
<gateways>
<cutoff-time>5</cutoff-time>
<external>
<list>
<entry name="vpn.example.com">
<priority-rule>
<entry name="Any">
<priority>1</priority>
</entry>
</priority-rule>
<priority>1</priority>
<description>vpn.example.com</description>
</entry>
</list>
</external>
</gateways>
<gateways-v6>
<cutoff-time>5</cutoff-time>
<external>
<list>
<entry name="vpn.example.com">
<fqdn>vpn.example.com</fqdn>
<priority-rule>
<entry name="Any">
<priority>1</priority>
</entry>
</priority-rule>
<priority>1</priority>
</entry>
</list>
</external>
</gateways-v6>
<agent-ui>
<can-save-password>yes</can-save-password>
<passcode/>
<agent-user-override-timeout>0</agent-user-override-timeout>
<max-agent-user-overrides>0</max-agent-user-overrides>
<help-page/>
<welcome-page>
<display>no</display>
<page/>
</welcome-page>
<agent-user-override>allowed</agent-user-override>
<enable-advanced-view>yes</enable-advanced-view>
<enable-do-not-display-this-welcome-page-again>yes</enable-do-not-display-this-welcome-page-again>
<can-change-portal>yes</can-change-portal>
<show-agent-icon>yes</show-agent-icon>
<password-expiry-message/>
</agent-ui>
<hip-collection>
<hip-report-interval>3600</hip-report-interval>
<max-wait-time>20</max-wait-time>
<collect-hip-data>yes</collect-hip-data>
<default>
<category>
<member>host-info</member>
<member>data-loss-prevention</member>
<member>patch-management</member>
<member>firewall</member>
<member>antivirus</member>
<member>anti-spyware</member>
<member>disk-backup</member>
<member>disk-encryption</member>
</category>
</default>
</hip-collection>
<agent-config>
<save-user-credentials>1</save-user-credentials>
<portal-2fa>no</portal-2fa>
<internal-gateway-2fa>no</internal-gateway-2fa>
<auto-discovery-external-gateway-2fa>no</auto-discovery-external-gateway-2fa>
<manual-only-gateway-2fa>no</manual-only-gateway-2fa>
<client-upgrade>prompt</client-upgrade>
<logout-remove-sso>yes</logout-remove-sso>
<krb-auth-fail-fallback>yes</krb-auth-fail-fallback>
<retry-tunnel>30</retry-tunnel>
<retry-timeout>5</retry-timeout>
<enforce-globalprotect>no</enforce-globalprotect>
<captive-portal-exception-timeout>0</captive-portal-exception-timeout>
<traffic-blocking-notification-delay>15</traffic-blocking-notification-delay>
<display-traffic-blocking-notification-msg>yes</display-traffic-blocking-notification-msg>
<traffic-blocking-notification-msg>&lt;div style="font-family:'Helvetica Neue';"&gt;&lt;h1 style="color:red;text-align:center; margin: 0; font-size: 30px;"&gt;Notice&lt;/h1&gt;&lt;p style="margin: 0;font-size: 15px; line-height: 1.2em;"&gt;To access the network, you must first connect to GlobalProtect.&lt;/p&gt;&lt;/div&gt;</traffic-blocking-notification-msg>
<allow-traffic-blocking-notification-dismissal>yes</allow-traffic-blocking-notification-dismissal>
<display-captive-portal-detection-msg>no</display-captive-portal-detection-msg>
<captive-portal-detection-msg>&lt;div style="font-family:'Helvetica Neue';"&gt;&lt;h1 style="color:red;text-align:center; margin: 0; font-size: 30px;"&gt;Captive Portal Detected&lt;/h1&gt;&lt;p style="margin: 0; font-size: 15px; line-height: 1.2em;"&gt;GlobalProtect has temporarily permitted network access for you to connect to the Internet. Follow instructions from your internet provider.&lt;/p&gt;&lt;p style="margin: 0; font-size: 15px; line-height: 1.2em;"&gt;If you let the connection time out, open GlobalProtect and click Connect to try again.&lt;/p&gt;&lt;/div&gt;</captive-portal-detection-msg>
<certificate-store-lookup>user-and-machine</certificate-store-lookup>
<scep-certificate-renewal-period>7</scep-certificate-renewal-period>
<ext-key-usage-oid-for-client-cert/>
<retain-connection-smartcard-removal>yes</retain-connection-smartcard-removal>
<rediscover-network>yes</rediscover-network>
<resubmit-host-info>yes</resubmit-host-info>
<can-continue-if-portal-cert-invalid>yes</can-continue-if-portal-cert-invalid>
<user-switch-tunnel-rename-timeout>0</user-switch-tunnel-rename-timeout>
<pre-logon-tunnel-rename-timeout>-1</pre-logon-tunnel-rename-timeout>
<show-system-tray-notifications>no</show-system-tray-notifications>
<max-internal-gateway-connection-attempts>0</max-internal-gateway-connection-attempts>
<portal-timeout>30</portal-timeout>
<connect-timeout>60</connect-timeout>
<receive-timeout>30</receive-timeout>
<enforce-dns>yes</enforce-dns>
<flush-dns>no</flush-dns>
<proxy-multiple-autodetect>no</proxy-multiple-autodetect>
<wsc-autodetect>yes</wsc-autodetect>
<mfa-enabled>no</mfa-enabled>
<mfa-listening-port>4501</mfa-listening-port>
<mfa-trusted-host-list/>
<mfa-notification-msg>You have attempted to access a protected resource that requires additional authentication. Proceed to authenticate at</mfa-notification-msg>
<ipv6-preferred>yes</ipv6-preferred>
</agent-config>
<portal-userauthcookie>empty</portal-userauthcookie>
<portal-prelogonuserauthcookie>empty</portal-prelogonuserauthcookie>
<scep-cert-auth-cookie>-AQ==P[snip]jExsgmHA==</scep-cert-auth-cookie>
</policy>

@aclindsa
Copy link
Author

aclindsa commented Sep 7, 2018

In looking through that XML, the only thing I see that might be problematic is the pair of:
<cookie-encrypt-decrypt-cert/>
<scep-cert-auth-cookie>-AQ==P[snip]jExsgmHA==</scep-cert-auth-cookie>

Does this mean that this cookie is somehow entangled with the certificate I'm replacing?

@dlenski
Copy link
Owner

dlenski commented Sep 7, 2018

I was able to get the certificate replaced in the response using the following (cert_replacer.py):

Nice 👍. My old version was 💯× uglier.

I think I see the problem.

  1. You're injecting the real server certificate into the portal config (flow.server_conn.cert).
    2 … but what you might really need to inject is the faux server certificate that MITMproxy is going to present to the client.
  2. But that might be wrong too… this section is called <root-ca>, so I think what the official client expects it to contain is not the portal or gateway server's certificate, but the certificate used to sign those certificate
    • … in which case you want to inject your ~/.mitmproxy/mitmproxy-ca-cert.pem).

However, with the client version that I tested, you can also just empty this whole section (replace with <root-ca/>) and that will make the client rely on system trust. YMMV.

In looking through that XML, the only thing I see that might be problematic is the pair of:
<cookie-encrypt-decrypt-cert/>
<scep-cert-auth-cookie>-AQ==P[snip]jExsgmHA==</scep-cert-auth-cookie>

Does this mean that this cookie is somehow entangled with the certificate I'm replacing?

I don't think so.

One other thing you may run into: some old versions of the official client get confused trying to do DNS when connected through a proxy. In that case, you'll have to replace the gateway hostnames (<entry name="vpn.example.com">) with their corresponding IP addresses. This might be fixed in newer versions of the client.

@aclindsa
Copy link
Author

aclindsa commented Sep 8, 2018

I got really excited because of course it's called <root-ca> and should be the root certificate! But alas, I got the same behavior after trying all three of your suggested methods (client_conn.mitmcert, ~/.mitmproxy/mitmproxy-ca-cert.pem, and emptying <root-ca>).

I think you're on to something with the DNS suggestion, but I think it may be more complex than just switching the name= inside <entry> tags. When I do that, I get "Gateway 11.22.33.44: The server certificate is invalid" (same as before, but with an IP in the message instead of a domain). If I replace all occurrences of the domain with the IP in the getconfig.esp response, I then get "Could not connect to gateway. Please contact your IT administrator".

After striking out, I dug into the logs on Windows and see (in PanGPS.log):

Unable to verify server cert. Result is unable to get local issuer certificate 
Open_SSL_connection: subject '/CN=11.22.33.44'
Open_SSL_connection: issuer '/CN=mitmproxy/O=mitmproxy'
StandardizeIpv6Format host=vpn.example.com
vpn.example.com is not ipv6
standardized name is vpn.example.com
StandardizeIpv6Format host=11.22.33.44
11.22.33.44 is not ipv6
standardized common name is 11.22.33.44
Check domain name vpn.example.com versus CN anme 11.22.33.44
Hostname vpn.example.com doesn't match CN 11.22.33.44, check subalt name
count = 1
alt current_name_type=7
sub alt name=11.22.33.44
Check domain name vpn.example.com versus CN anme 11.22.33.44
Hostname vpn.example.com doesn't matche sub alt name 11.22.33.44
check subalt returns 0
Disconnect SSL 
SSL3 alert write:warning:close notify
Disconnect tcp socket 
CheckServerCert() returns 0x1000
vpn.example.com is not ipv4
vpn.example.com is not ipv6
vpn.example.com is fqdn
Failed to verify server certificate of gateway vpn.example.com.
Set registry LastErrorString as Gateway vpn.example.com: The server certificate is invalid. Please contact your IT administrator.
Show Gateway vpn.example.com: The server certificate is invalid. Please contact your IT administrator.

From the log messages, it almost looks like the client is resolving the domain name to an IP address, then making a request with the IP directly, causing mitmproxy to generate a cert with the IP instead of the domain name in the CN. But if I look at flow.client_conn.mitmcert.subject when the request is being made, the cert appears to have the domain name for the CN...

I'll work on this more when I have time, but figured I'd post my progress (or lack thereof) in case you had further insight. Thanks for your help so far!

@dlenski
Copy link
Owner

dlenski commented Sep 8, 2018

Just double-checking… are you running mitmproxy with this option? (It's required to get mitmproxy to connect to servers which it itself doesn't consider secure.) Another user reminded me of this at #118 (comment)

  --ssl-insecure, -k    Do not verify upstream server SSL/TLS certificates.

@aclindsa
Copy link
Author

Doh! I assumed that if I was seeing traffic to the gateway that I didn't need the --ssl-insecure option. I was wrong. Using it fixed my problem entirely (no need for replacing the certificate in the response body at all).

Thanks!

Repository owner locked and limited conversation to collaborators Oct 10, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants