SSH port forwarding: can do, but but cannot undo

Hi,

I am running 23.05.2.

I have created a firewall rule that allows me to ssh to the router from the wan interface (not open to internet). I may also want, rarely, access to LuCI. To reduce the attack surface, my idea was this:

  1. SSH to wan. Do my thing.
  2. If I also need web interface access, enable port forwarding support for dropbear from the SSH session:
uci set dropbear.@dropbear[0].GatewayPorts='on'
uci commit dropbear
service dropbear restart
  1. Open an shh tunnel to 443
ssh -L 8443:127.0.0.1:443 root@openwrt.wan -N
  1. Connect to LuCI on https://127.0.0.1:8443/cgi-bin/luci/. Do my thing
  2. Disconnect from LuCI. Stop the SSH tunnel. SSH again and disable port forwarding support for dropbear:
uci set dropbear.@dropbear[0].GatewayPorts='off'
uci commit dropbear
service dropbear restart

Everything works as expected up to step 4. I couldn't start the tunnel until I enabled GatewayPorts, and once I do it, I can and web interface is available.

But step 5 doesn't seem to be working. It is probably a problem with service dropbear restart because I can see the configuration change both in UCI and LuCI. But running the service restart generates this in logread

Fri Jan 12 02:49:27 2024 authpriv.info dropbear[8991]: Early exit: Terminated by signal
Fri Jan 12 02:49:28 2024 authpriv.info dropbear[9588]: Not backgrounding

Any ideas on how to make it work?

  • What part of Step 5 isn't working?
  • How do you perform the tasks in Step No. 5?
  • What's the purpose of logging in a subsequent time to edit SSH settings (i.e. if an attacker gained access, they could just re-enable it like you)?
  • Do you use SSH keys (password-less) or password login?

It's not clear, because you have 3 tasks listed under No. 5.

The GatewayPorts option shouldn't be needed. I do what you suggest on a regular basis (on port 80, not 443, but that doesn't make a difference) and I never had to configure anything for DropBear.
The GatewayPorts option allows the connection back to the client, check e.g. this link for an explanation of the option.

1 Like

Yes, the -L functionality is default enabled in the dropbear service. No need to change dropbear settings to do what the OP is doing.

That's interesting... that doesn't match the results I was getting.
I first assumed tunneling would be on, and I fired an ssh -L right after opening the firewall for wan on tcp/22, and gave me an error that I didn't keep but it clearly lead me to believe tunneling wasn't enabled for sshd.

It didn't take much to find gatewayports option. I enabled it, service restarted dropbear, and the same ssh -L command started working for me.

One firewall rule (open tcp/22 on wan) gave me access to ssh and https. That's better than 2 firewall rules. And if I disable this port forwarding capability for sshd, it would be even better. I know it is a very marginal improvement, but why not? I am going to automate the whole thing anyway.

I then reverted the dropbear configuration but I haven't been able to go back to what seemed to be the original dropbear behaviour.

I have tried everything I could think of:

  • I have used uci set dropbear.@dropbear[0].GatewayPorts='off'
  • I have used uci -q delete dropbear.@dropbear[0].GatewayPorts
  • I have unchecked the correspondant box on LuCI.

I can see UCI changes on LuCI and viceversa and I don't get errors when restarting dropbear apart from the Not backgrounding log that I do not really understand. But reality is that ssh -L has never stopped working.

At this stage I am using passwords, but I will be switching to keys.

Second comment in this line... I guess my first tests were wrong or somehow different to what I am doing now...
The good thing is that I have a second identical router where I plan to apply the same configuration. I will try to compare configurations and behaviours, see if I find something to report.

Regards,

All daemons are handled by procd on OpenWrt. This only works if daemons do not fork to the background, hence dropbear is started with -F to keep running in the foreground. As a result, it outputs this message to the log file.

If you want to disable port forwarding, dropbear needs to be started with -j and/or -k. I do not see a uci configuration for this at the Wiki page.

For reference, these are the command line options to dropbear on OpenWrt:

Dropbear server v2022.82 https://matt.ucc.asn.au/dropbear/dropbear.html
Usage: dropbear [options]
-b bannerfile	Display the contents of bannerfile before user login
		(default: none)
-r keyfile      Specify hostkeys (repeatable)
		defaults: 
		- rsa /etc/dropbear/dropbear_rsa_host_key
		- ed25519 /etc/dropbear/dropbear_ed25519_host_key
-R		Create hostkeys as required
-F		Don't fork into background
-e		Pass on server process environment to child process
-E		Log to stderr rather than syslog
-w		Disallow root logins
-G		Restrict logins to members of specified group
-s		Disable password logins
-g		Disable password logins for root
-B		Allow blank password logins
-T		Maximum authentication tries (default 10)
-j		Disable local port forwarding
-k		Disable remote port forwarding
-a		Allow connections to forwarded ports from any host
-c command	Force executed command
-p [address:]port
		Listen on specified tcp port (and optionally address),
		up to 10 can be specified
		(default port is 22 if none specified)
-P PidFile	Create pid file PidFile
		(default /var/run/dropbear.pid)
-i		Start for inetd
-W <receive_window_buffer> (default 24576, larger may be faster, max 10MB)
-K <keepalive>  (0 is never, default 0, in seconds)
-I <idle_timeout>  (0 is never, default 0, in seconds)
-V    Version
1 Like
  • Why would it stop working?
  • I noticed you didn't specifically say ssh foo -L xxxxx:127.0.0.x:xxxxx
  • Which leads me to my last question - what port did you forward to on the OpenWrt once logged in?

That is indeed something I can answer now.

On my fist tests I used this command:

[felichas@desktop ~]$ ssh -L 0.0.0.0:8443:127.0.0.1:443 root@openwrt.wan -N

and then from a different computer (felichas@laptop) used a browser and tried to connect to: https://desktop:8443/cgi-bin/luci/

This is the tunnel that you guys have helped me understand that doesn't work in dropbear.

This issue helped me too: https://github.com/mkj/dropbear/issues/165

Thank you all for your support. I have learnt many things today. And luckily I had a second router to confirm all this:

On my fist test I used my laptop a middle server and the router, and this command

[felichas@desktop ~]$ ssh -L 0.0.0.0:8443:127.0.0.1:443 root@openwrt.wan -N

As this didn't work, I changed configuration in dropbear and tested again, but this time I tested skipping the desktop and using

[felichas@laptop ~]$ ssh -L 8443:127.0.0.1:443 root@openwrt.wan -N

And it worked...

Repeat with me: when you are investigating you NEVER do 2 changes at the same time, as you never know which one affected.

All is clear for me now, and I understand how dropbear better now.

Thanks for clarifying. If you did indeed run -L xxxxx:127.0.0.x:xxxxx on your desktop, then please show the ssh command you ran on the OpenWrt to forward a local port

This doesn't seem valid.

In case someone benefits from it, this is what I ended up doing:
DISCLAIMER: do not do this unless you understand what you are doing. Different routers have different buttons and LEDs, and this may not work on yours (although it should be very easy to adapt). This is working in a ZyXEL WSM20 (Multy M1) running OpenWrt 23.05.2, r23630-842932a63d

No more tunnels, let's KISS:

  • 2 firewall rules: open ssh on wan + open https on wan.
  • reprogrammed the wps button: if you press it for more than 5s, it will open/close the firewall rules.
  • a monitor will give visual feedback all the time to make sure the firewall rules are not left open more than strictly needed.

The firewall rules "Allow-SSH-wan" and "Allow-HTTPS-wan"

humanname="Allow-SSH-wan"
sectionname="$(uci show firewall |grep "${humanname}" |awk -F. '{print $2}')"
uci -q delete "firewall.${sectionname}"
uci batch << EOI
add firewall rule
set firewall.@rule[-1].name="${humanname}"
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].src='wan'
set firewall.@rule[-1].dest_port='22'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].enabled='0'
EOI
humanname="Allow-HTTPS-wan"
sectionname="$(uci show firewall |grep "${humanname}" |awk -F. '{print $2}')"
uci -q delete "firewall.${sectionname}"
uci batch << EOI
add firewall rule
set firewall.@rule[-1].name="${humanname}"
set firewall.@rule[-1].proto='tcp'
set firewall.@rule[-1].src='wan'
set firewall.@rule[-1].dest_port='443'
set firewall.@rule[-1].target='ACCEPT'
set firewall.@rule[-1].enabled='0'
EOI
uci commit firewall
service firewall restart

A script that flashes a led if any of the previous firwall rules are enabled. Different routers will have to choose different leds for this.

mkdir -p /root/myscripts/
cat << "EOF" > /root/myscripts/led_monitor.sh
#!/bin/sh

# /root/myscripts/led_monitor.sh
# This script is meant to start with the router. It should be registered in /etc/rc.local
# It updates LED states based on router status (a tunnel stablished, a firwall rule enabled, etc.)

fwruleSSH="Allow-SSH-wan"
fwruleHTTPS="Allow-HTTPS-wan"

while true; do

  # red:system is in the lowest possition, allowing it to "send a message" without getting in the way of other leds.
  # Heartbeat on red:system = Firewall rule open on wan

  statusfwruleSSH=$(uci get firewall."$(uci show firewall |grep "${fwruleSSH}" |awk -F. '{print $2}')".enabled)
  statusfwruleHTTPS=$(uci get firewall."$(uci show firewall |grep "${fwruleHTTPS}" |awk -F. '{print $2}')".enabled)

  if [ "${statusfwruleSSH}" = "1" -o "${statusfwruleHTTPS}" = "1" ]; then
    echo "heartbeat" > /sys/class/leds/red:system/trigger
  else
    echo "none" > /sys/class/leds/red:system/trigger
  fi

  sleep 10

done
EOF
chmod 755 /root/myscripts/led_monitor.sh

To have the script running with the router (it is basically a loop, so it will run forever) all we have to do is run it from the startup script:
Just edit /etc/rc.local and add /root/myscripts/led_monitor.sh & before the final exit 0

And finally the script that will enable/disable the firewall rules when you press the wps button.
Again, different routers may have to choose different buttons for this.

cat << "EOF" > /etc/rc.button/wps
#!/bin/sh

# /etc/rc.button/wps
# We want wps button to open/close firewall rules on wan interface for temporary remote access.
# To trigger the action, you have to press the wps button for more than 5s.
# Opening firewall rules will generate a pulsing red warning that will continue as long as the firewall rules are open.

logger "BUTTON=${BUTTON} registered ACTION=${ACTION} after SEEN=${SEEN} seconds"

if [ "${ACTION}" = "released" -a "${SEEN}" -gt 4 ] ;then
  # Let's log the detection of the event
  logger "Request for action on wan firewall rules detected!"
  # and warn the user with a flash of white leds
  echo 1 > /sys/class/leds/white:led2/brightness
  echo 1 > /sys/class/leds/white:led5/brightness
  sleep 2
  echo 0 > /sys/class/leds/white:led2/brightness
  echo 0 > /sys/class/leds/white:led5/brightness

  fwruleSSH="Allow-SSH-wan"
  fwruleHTTPS="Allow-HTTPS-wan"

  statusfwruleSSH=$(uci get firewall."$(uci show firewall |grep "${fwruleSSH}" |awk -F. '{print $2}')".enabled)
  statusfwruleHTTPS=$(uci get firewall."$(uci show firewall |grep "${fwruleHTTPS}" |awk -F. '{print $2}')".enabled)

  if [ "${statusfwruleSSH}" = "1" -o "${statusfwruleHTTPS}" = "1" ]; then
    uci set firewall."$(uci show firewall |grep "${fwruleSSH}" |awk -F. '{print $2}')".enabled="0"
    uci set firewall."$(uci show firewall |grep "${fwruleHTTPS}" |awk -F. '{print $2}')".enabled="0"
    logger "${fwruleSSH} has been disabled. Actual status is enabled=$(uci get firewall."$(uci show firewall |grep "${fwruleSSH}" |awk -F. '{print $2}')".enabled)"
    logger "${fwruleHTTPS} has been disabled. Actual status is enabled=$(uci get firewall."$(uci show firewall |grep "${fwruleHTTPS}" |awk -F. '{print $2}')".enabled)"
  else
    uci set firewall."$(uci show firewall |grep "${fwruleSSH}" |awk -F. '{print $2}')".enabled="1"
    uci set firewall."$(uci show firewall |grep "${fwruleHTTPS}" |awk -F. '{print $2}')".enabled="1"
    logger "${fwruleSSH} has been enabled. Actual status is enabled=$(uci get firewall."$(uci show firewall |grep "${fwruleSSH}" |awk -F. '{print $2}')".enabled)"
    logger "${fwruleHTTPS} has been enabled. Actual status is enabled=$(uci get firewall."$(uci show firewall |grep "${fwruleHTTPS}" |awk -F. '{print $2}')".enabled)"
  fi
fi
EOF
chmod 755 /etc/rc.button/wps

Restart the router and you are good to go. You can monitor the button events with logread -f

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.