
Preface
Why am I writing this post? Back when I was reproducing the CVE-2017-0199 vulnerability, I needed NAT traversal. I searched for tunneling services and found that many of them were based on ngrok. During the process I found it quite painful to configure. The version I used was 1.7, which reportedly has a memory leak bug. The latest 2.2 is closed-source, and the official documentation no longer works. I read a few posts written by others and decided to record my own setup process as well. I’ve also heard that FRP is stronger than ngrok—maybe I’ll try it next time.
Installation
git clone https://github.com/inconshreveable/ngrok
cd ngrok
make
# 服务端
mv ./ngrokd /usr/bin/ngrokd
# 客户端
mv ./ngrok /usr/bin/ngrok
echo "" > $HOME/.ngrok
Usage
Server
ngrokd roughly means “ngrok daemon”—as the name suggests, it’s the server side. Check the help:
root@gorgiaxx:~/tmp/ngrok/bin# ./ngrokd --help
Usage of ./ngrokd:
-domain string
Domain where the tunnels are hosted (default "ngrok.com")
-httpAddr string
Public address for HTTP connections, empty string to disable (default ":80")
-httpsAddr string
Public address listening for HTTPS connections, emptry string to disable (default ":443")
-log string
Write log messages to this file. 'stdout' and 'none' have special meanings (default "stdout")
-log-level string
The level of messages to log. One of: DEBUG, INFO, WARNING, ERROR (default "DEBUG")
-tlsCrt string
Path to a TLS certificate file
-tlsKey string
Path to a TLS key file
-tunnelAddr string
Public address listening for ngrok client (default ":4443")
On my server, ports 80 and 443 were already occupied by Nginx, so I changed them to 800 and 801. I also didn’t need to configure tlsCrt and tlsKey because I wasn’t going to use them. I set the log level to WARNING. tunnelAddr is the tunnel address used by the internal machine to connect to the server.
./ngrokd -domain gorgiaxx.me -httpAddr :800 -httpsAddr :801 -tunnelAddr :802 -log-level WARNING
Client
Check the help:
gorgias@3vil:~/Tools/port_map/ngrok/bin$ ./ngrok --help
Usage: ./ngrok [OPTIONS] <local port or address>
Options:
-authtoken string
Authentication token for identifying an ngrok.com account
-config string
Path to ngrok configuration file. (default: $HOME/.ngrok)
-hostname string
Request a custom hostname from the ngrok server. (HTTP only) (requires CNAME of your DNS)
-httpauth string
username:password HTTP basic auth creds protecting the public tunnel endpoint
-log string
Write log messages to this file. 'stdout' and 'none' have special meanings (default "none")
-log-level string
The level of messages to log. One of: DEBUG, INFO, WARNING, ERROR (default "DEBUG")
-proto string
The protocol of the traffic over the tunnel {'http', 'https', 'tcp'} (default: 'http+https') (default "http+https")
-subdomain string
Request a custom subdomain from the ngrok server. (HTTP only)
Examples:
ngrok 80
ngrok -subdomain=example 8080
ngrok -proto=tcp 22
ngrok -hostname="example.com" -httpauth="user:password" 10.0.0.1
Advanced usage: ngrok [OPTIONS] <command> [command args] [...]
Commands:
ngrok start [tunnel] [...] Start tunnels by name from config file
ngork start-all Start all tunnels defined in config file
ngrok list List tunnel names from config file
ngrok help Print help
ngrok version Print ngrok version
Examples:
ngrok start www api blog pubsub
ngrok -log=stdout -config=ngrok.yml start ssh
ngrok start-all
ngrok version
If you want easier configuration and plan to share it with more people, it’s best to set up a wildcard DNS record. You can also host your own DNS server. If you’re just using it for yourself, then configure a specific subdomain.
ngrok -hostname="ngrok.gorgoaxx.me" -subdomain=ngrok 802 -proto=tcp 80
I’m not going to configure everything via CLI flags every time—using a config file is the way to go. Refer to this code:
ngrok/src/ngrok/client/config.go
type Configuration struct {
HttpProxy string `yaml:"http_proxy,omitempty"`
ServerAddr string `yaml:"server_addr,omitempty"`
InspectAddr string `yaml:"inspect_addr,omitempty"`
TrustHostRootCerts bool `yaml:"trust_host_root_certs,omitempty"`
AuthToken string `yaml:"auth_token,omitempty"`
Tunnels map[string]*TunnelConfiguration `yaml:"tunnels,omitempty"`
LogTo string `yaml:"-"`
Path string `yaml:"-"`
}
type TunnelConfiguration struct {
Subdomain string `yaml:"subdomain,omitempty"`
Hostname string `yaml:"hostname,omitempty"`
Protocols map[string]string `yaml:"proto,omitempty"`
HttpAuth string `yaml:"auth,omitempty"`
RemotePort uint16 `yaml:"remote_port,omitempty"`
}
When you use ngrok start, it reads the default config file under your home directory:
$HOME/.ngrok
Using Metasploit as an example: the ngrok.gorgiaxx.me subdomain is used to deliver the payload.
Usually you don’t need hostname (which is a fully qualified name). It’s only used in some special cases. If your ngrok server can be resolved via CNAME, you can also manually set your own domain. But since newer versions are closed-source now, this is no longer that relevant.
server_addr: gorgiaxx.me:802
trust_host_root_certs: false
tunnels:
dav:
proto:
http: 8080
subdomain: ngrok
rev:
proto:
tcp: 4444
Then start the tunnels:
./ngrok -config ./ngrok.yml start dav rev
Or start everything at once:
./ngrok -config ./ngrok.yml start-all

Summary
You can see that the open-source version of ngrok has many areas that could be improved. From a usability standpoint it isn’t very convenient, and the tunnels don’t have user authentication—so if someone discovers it, they can use it directly. Still, it’s a pretty decent tool.