Port Forwarding With DD-WRT

While configuring my Asus RT-AC66U router to allow remote access to my home LAN, I struggled to get port forwarding working. This turned into an interesting puzzle.

I have a static public IP address assigned to my ISP connection. Having a static public IP address to my ISP connection helps with this type of access.

I live rurally. Normaly I do not have a way to explicitly test remote access except by traveling into town and using another access point. That said, I saved some gasoline. I have two VLANs configured in the router as well as a wireless guest network. These different subnets and isolation from the LAN provide me a way to test remote access without leaving home.

During this continual testing I grew tired of manually typing the public IP address. I added an entry in /etc/hosts to avoid the longer typing.

When trying to SSH into the LAN server using port forwarding I continually saw connection time out errors.

I was able to SSH directly into the router. That let me know there were no problems with NAT or the public IP address. For that access I configured the router remote SSH using a port higher than port 1000 rather than the standard port 22. This is nominal security by obscurity at most, but does reduce port scanning noise.

While connecting directly to the router provides a slick way to use SOCKS5 for secure web browsing through a public access point, using SSH in this manner did not help me get into my LAN server and data files. While SSH is used as a portal to manage a router, DD-WRT is not designed to work from within the router like a workstation. For example, there is no ssh-keygen command. Possibly a key pair could be generated outside the router and copied into the DD-WRT root profile. Yet even if doable, I did not want to SSH into the router and then SSH into a LAN computer. Port forwarding seemed to be the correct approach.

The DD-WRT iptables rules showed the following:

    Chain FORWARD (policy ACCEPT)
    target  prot opt source    destination
    ACCEPT  tcp  —  anywhere  lan.server.ip   tcp dpt:ssh 
    ACCEPT  udp  —  anywhere  lan.server.ip   udp dpt:ssh

That output indicated the router should be forwarding the port.

Similar to my remote port 22 obfuscation, I selected a port higher than 1000 to port forward to my LAN server port 22. All computers on the LAN use port 22 for SSH.

My ~/.ssh/config file looks like this:

    Host remote_router
      HostName static.public.ip.address
      Port 2387
      DynamicForward 4321
      User root
      IdentityFile ~/.ssh/remote_router
    Host remote_server
      HostName static.public.ip.address
      Port 3498
      DynamicForward 4321
      User root
      IdentityFile ~/.ssh/remote_server

Where static.public.ip.address is the actual public address.

As a side note, the DynamicForward 4321 option is the same as the -D option when running SSH from the command line. This option allows for using a SOCKS5 proxy — in my case, on local port 4321.

My LAN server /var/log/secure log showed no evidence of any remote SSH attempts. Running nmap -Pn -p 3498 static.public.ip.address showed the port filtered rather than open. That means the actual status of the port is unknown.

Long ago I used an online firewall rules generator to provide me a foundation for iptables rules. Sadly Slackware provides no default rc.firewall script and this online generator helps fill the void.

Long ago I modified the Slackware /etc/rc.d/rc.inet1.conf file to include additional variables to toggle various firewall features. With those variables I consolidated several firewall configurations into a single rc.firewall script. Long forgotten by me is the online generator supports creating rules for external inbound services, such as SSH. Long forgotten by me is I had prepared for allowing remote SSH access. I had such a variable defined in rc.inet1.conf.

I enabled the variable in rc.inet1.conf (ALLOW_OUTSIDE_SSH="yes") and restarted the rc.firewall script.

The firewall rule looks like this:

/sbin/iptables -A tcp_inbound -p TCP -s 0/0 --destination-port 22 -j ACCEPT

With that additional rule I no longer saw a connection time out error. Yet I still could not SSH into the LAN server.

After some head scratching and simply having been around Linux systems a long time, I looked at /etc/hosts.allow. While I had a firewall rule allowing these external packets, I needed to allow the same with hosts.allow.

For years the file looked like this:

    ALL: localhost
    ALL: 127.0.0.1
    ALL: 192.168.1.0/255.255.255.128

I modified the file to this:

    ALL: localhost
    ALL: 127.0.0.1
    ALL: 192.168.1.0/255.255.255.128
    sshd: ALL

Comically, I already had a stdout statement in my rc.firewall script to remind me about verifying hosts.allow:

Please verify /etc/hosts.allow allows SSH access.

How the cobwebs grow when something sits unused.

Next I thought I would get fancy. I modified the firewall rule and hosts.allow. Rather than accept any source IP address I only allowed the public IP address:

/sbin/iptables -A tcp_inbound -p TCP -s static.public.ip.address --destination-port 22 -j ACCEPT

    ALL: localhost
    ALL: 127.0.0.1
    ALL: 192.168.1.0/255.255.255.128
    sshd: 192.168.1.0/255.255.255.128 static.public.ip.address

I thought that would limit external SSH access only from the public IP address. I thought that accessing port 22 on the LAN server from the Internet was possible only through the port forwarding of the router. With that case the only external IP address I should ever see in my logs is the public IP address.

My fanciness foiled me. I could not connect to the LAN server. After some thought the cause was simple. Although the phrase port forwarding is used, the phrase should actually be IP address and port forwarding. That is, there is no network address translation. The original source IP address is the actual address trying to connect to the LAN server.

I restored the LAN server iptables rule to allow SSH from any source:

/sbin/iptables -A tcp_inbound -p TCP -s 0/0 --destination-port 22 -j ACCEPT

I modified hosts.allow as well:

    ALL: localhost
    ALL: 127.0.0.1
    ALL: 192.168.1.0/255.255.255.128
    sshd: ALL

Running nmap -Pn -p 3498 static.public.ip.address showed the port as open.

Allowing access by IP address is a decent idea. I foresee in my own habits and schedule that I probably could limit access to two or three IP address ranges. I will keep this idea in mind for a later day.

With my next attempt to SSH into the LAN server I was prompted to accept the host finger print and asked for my pass phrase. Oddly, I was not connected to the LAN server. The LAN server /var/log/secure log showed a connection from the public IP address. More oddly, I repeated the same command and immediately I was logged into the LAN server. Several attempts again successfully connected.

I was in. Yippee yay a Cow Patty.

I encountered the pass phrase dialog request when trying to SSH into other LAN systems from the LAN server. I canceled the dialog and to my surprise, was nonetheless logged into the target system.

Suddenly there was more work to do.

Having remote access is a new feeling for me. A feeling of potential vulnerability.

Investigating the login quirks led me to a configuration feature called IdentitiesOnly. This option is used in the global /etc/ssh_config file or the user's ~/.ssh/config. Acceptable parameters are no and yes.

With this option set to no, SSH tries additional key pairs if a key pair or pass phrase fails. Not a great way to use SSH. On my systems, canceling the GUI pass phrase dialog did not return me to the terminal and again ask for the pass phrase. Instead the system used my LAN SSH keys and proceeded to login. Not cool. Changing to IdentitiesOnly yes stopped the silliness. Thereafter, failing to provide a correct pass phrase resulted in a failed SSH login attempt. That is what I want. Checking the LAN server SSH log showed the failed attempt. This is what I wanted in case somebody tried to brute force into the LAN server through the port forwarding. A failed attempt is easily grepped in a cron job script.

I was logging into the LAN server as root. While logging into DD-WRT as root is normal practice, and I am using key pairs and a pass phrase to log into the LAN server, logging into my LAN server as non-root would be prudent. I copied the public key to my non-root account and updated my ~/.ssh/config file.

I wanted to block brute force attempts. Port forwarding is just that. Any malicious hacker who proceeded to explore an open port would be knocking on the door of the server. Theoretically the port forwarding limits access to the LAN server only at port 22.

DD-WRT supports brute force blockage through a single check box, which translates into enabling some iptables rules.

I wanted to mimic that behavior on my LAN server.

At the LAN server I added the following firewall rules:

    /sbin/iptables -A INPUT -p tcp --dport $SSH_PORT -i $INET_IFACE -m state --state NEW -m recent --set
    /sbin/iptables -A INPUT -p tcp --dport $SSH_PORT -i $INET_IFACE -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP

While DD-WRT is designed to block brute force attacks on the remote SSH port, I do not believe there is any such protection for port forwarding.

I added the following DD-WRT rules:

    iptables -t nat -I PREROUTING -p tcp -d $(nvram get wan_ipaddr) --dport 3498 -j DNAT --to 192.168.1.1:22
    iptables -I FORWARD -p tcp -d 192.168.1.1 --dport 22 -m state --state RELATED,ESTABLISHED -j ACCEPT
    iptables -I FORWARD 2 -p tcp -d 192.168.1.1 --dport 22 -m state --state NEW -m limit --limit 3/min --limit-burst 3 -j ACCEPT
    iptables -I FORWARD 3 -p tcp -d 192.168.1.1 --dport 22 -j logreject

With remote access I wanted logging. I never had remote access to my LAN. I always had my router configured with no open WAN ports or access. Thus my internal firewall rules were configured for almost no logging at all because all computers on the LAN are trusted and nothing outside the LAN had access. There was no need to clutter logs.

I might want email alerts whenever anybody tries to attack my LAN server through my public ip address and port forwarding. That way I could learn more about how intrusions occur.

As a security precaution I can disable the port forwarding until needed. Yet after all of this effort, how do I test my security precautions? I do not know.

With some reflection I wondered whether I would need or want direct remote access to the router. Disabling remote SSH to the router and allowing only the port forwarding to the LAN server would reduce WAN side access to a single port. Configuring conversely would provide me a secure web browsing portal with unsecured open wireless access points without opening the LAN server to the Internet.

As I conducted these tests from inside my home and LAN, I wanted to truly test remote access by using a trusted remote access point. I traveled into town and tested my remote access.

All went well. Yippee yay a Cow Patty.

Now to configure a VPN.

Posted: Category: Tutorial, Usability Tagged: DD-WRT

Next: Configuring a PPTP VPN Server in DD-WRT

Previous: Remote Access With DD-WRT