Skip to content

Instantly share code, notes, and snippets.

@milanaleksic
Last active March 3, 2023 16:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save milanaleksic/162c99697a08bb36fe514f66399913ec to your computer and use it in GitHub Desktop.
Save milanaleksic/162c99697a08bb36fe514f66399913ec to your computer and use it in GitHub Desktop.
Ngrok free mode always has only one single tunnel allowed. If you use TCP tunnel, this script extracts the value (since ngrok doesn't have server API). This way you can connect to that port from a script (ssh tunnel is my favorite usage of ngrok) instead of having to visit the site manually to get this value.
package main
import (
"bufio"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"regexp"
"strings"
"flag"
)
const loginPage = "https://dashboard.ngrok.com/user/login"
var (
debugMode bool
email string
password string
csrfPattern = regexp.MustCompile(`value="([^"]+)"`)
tunnelPattern = regexp.MustCompile(`tcp://\d+.tcp.ngrok.io:(\d+)`)
)
func init() {
flag.BoolVar(&debugMode, "debugMode", false, "show extra debug information")
flag.StringVar(&email, "email", "", "email used for ngrok")
flag.StringVar(&password, "password", "", "password used for ngrok")
flag.Parse()
}
func main() {
checkLogging()
jarOptions := &cookiejar.Options{}
jar, err := cookiejar.New(jarOptions)
check("cookie jar", err)
client := &http.Client{
Jar: jar,
}
resp, err := client.Get(loginPage)
check("login page fetch", err)
csrf := getCSRF(resp)
resp.Body.Close()
form := url.Values{}
form.Add("email", email)
form.Add("password", password)
form.Add("csrf_token", csrf)
loginRequest, err := http.NewRequest("POST", loginPage, strings.NewReader(form.Encode()))
check("login request creation", err)
loginRequest.Header.Add("Referer", loginPage)
loginRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err = client.Do(loginRequest)
check("login page post", err)
// dumpResponse(resp)
resp.Body.Close()
resp, err = client.Get("https://dashboard.ngrok.com/status")
listTunnels(resp)
resp.Body.Close()
}
func check(reason string, err error) {
if err != nil {
log.Panicf("Can't process part '%v': error %v", reason, err)
}
}
func listTunnels(resp *http.Response) {
bufReader := bufio.NewReader(resp.Body)
for {
line, err := bufReader.ReadString(10)
if err == io.EOF {
break
}
check("bytes read", err)
if strings.Contains(line, "tcp") {
match := tunnelPattern.FindAllStringSubmatch(line, 1)[0][1]
log.Printf("Found port: %v", match)
fmt.Fprint(os.Stdout, match)
}
}
}
func getCSRF(resp *http.Response) string {
bufReader := bufio.NewReader(resp.Body)
for {
line, err := bufReader.ReadString(10)
if err == io.EOF {
break
}
check("bytes read", err)
if strings.Contains(line, "csrf") {
match := csrfPattern.FindAllStringSubmatch(line, 1)[0][1]
log.Printf("Found CSRF: %T, %v", match, match)
return match
}
}
panic("CSRF not found")
}
func dumpResponse(resp *http.Response) {
bufReader := bufio.NewReader(resp.Body)
for {
line, err := bufReader.ReadString(10)
if err == io.EOF {
break
}
check("response read", err)
log.Printf("RESPONSE: %v", line)
}
}
type blackHoleWriter struct {
}
func (w *blackHoleWriter) Write(p []byte) (n int, err error) {
err = errors.New("black hole writer")
return
}
func checkLogging() {
if !debugMode {
log.SetOutput(&blackHoleWriter{})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment