You’ve found a website that lets you input remote templates for rendering. Your task is to exploit this system’s vulnerabilities to access and retrieve a hidden flag. Good luck!
We had the program code written in Golang.
package main
import (
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
)
const WEB_PORT = "1337"
const TEMPLATE_DIR = "./templates"
type LocationInfo struct {
IpVersion int `json:"ipVersion"`
IpAddress string `json:"ipAddress"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
CountryName string `json:"countryName"`
CountryCode string `json:"countryCode"`
TimeZone string `json:"timeZone"`
ZipCode string `json:"zipCode"`
CityName string `json:"cityName"`
RegionName string `json:"regionName"`
Continent string `json:"continent"`
ContinentCode string `json:"continentCode"`
}
type MachineInfo struct {
Hostname string
OS string
KernelVersion string
Memory string
}
type RequestData struct {
ClientIP string
ClientUA string
ServerInfo MachineInfo
ClientIpInfo LocationInfo `json:"location"`
}
func (p RequestData ) FetchServerInfo (command string ) string {
out, err := exec. Command ( "sh" , "-c" , command). Output ()
if err != nil {
return ""
}
return string (out)
}
func (p RequestData ) GetLocationInfo (endpointURL string ) ( * LocationInfo , error ) {
resp, err := http. Get (endpointURL)
if err != nil {
return nil , err
}
defer resp.Body. Close ()
if resp.StatusCode != http.StatusOK {
return nil , fmt. Errorf ( "HTTP request failed with status code: %d " , resp.StatusCode)
}
body, err := io. ReadAll (resp.Body)
if err != nil {
return nil , err
}
var locationInfo LocationInfo
if err := json. Unmarshal (body, & locationInfo); err != nil {
return nil , err
}
return & locationInfo, nil
}
func isSubdirectory (basePath, path string ) bool {
rel, err := filepath. Rel (basePath, path)
if err != nil {
return false
}
return ! strings. HasPrefix (rel, ".." + string (filepath.Separator))
}
func readFile (filepath string , basePath string ) ( string , error ) {
if ! isSubdirectory (basePath, filepath) {
return "" , fmt. Errorf ( "Invalid filepath" )
}
data, err := os. ReadFile (filepath)
if err != nil {
return "" , err
}
return string (data), nil
}
func readRemoteFile (url string ) ( string , error ) {
response, err := http. Get (url)
if err != nil {
return "" , err
}
defer response.Body. Close ()
if response.StatusCode != http.StatusOK {
return "" , fmt. Errorf ( "HTTP request failed with status code: %d " , response.StatusCode)
}
content, err := io. ReadAll (response.Body)
if err != nil {
return "" , err
}
return string (content), nil
}
func getIndex (w http . ResponseWriter , r * http . Request ) {
http. Redirect (w, r, "/render?page=index.tpl" , http.StatusMovedPermanently)
}
func getTpl (w http . ResponseWriter , r * http . Request ) {
var page string = r.URL. Query (). Get ( "page" )
var remote string = r.URL. Query (). Get ( "use_remote" )
if page == "" {
http. Error (w, "Missing required parameters" , http.StatusBadRequest)
return
}
reqData := & RequestData {}
userIPCookie, err := r. Cookie ( "user_ip" )
clientIP := ""
if err == nil {
clientIP = userIPCookie.Value
} else {
clientIP = strings. Split (r.RemoteAddr, ":" )[ 0 ]
}
userAgent := r.Header. Get ( "User-Agent" )
locationInfo, err := reqData. GetLocationInfo ( "https://freeipapi.com/api/json/" + clientIP)
if err != nil {
fmt. Println (err)
http. Error (w, "Could not fetch IP location info" , http.StatusInternalServerError)
return
}
reqData.ClientIP = clientIP
reqData.ClientUA = userAgent
reqData.ClientIpInfo = * locationInfo
reqData.ServerInfo.Hostname = reqData. FetchServerInfo ( "hostname" )
reqData.ServerInfo.OS = reqData. FetchServerInfo ( "cat /etc/os-release | grep PRETTY_NAME | cut -d ' \" ' -f 2" )
reqData.ServerInfo.KernelVersion = reqData. FetchServerInfo ( "uname -r" )
reqData.ServerInfo.Memory = reqData. FetchServerInfo ( "free -h | awk '/^Mem/{print $2}'" )
var tmplFile string
if remote == "true" {
tmplFile, err = readRemoteFile (page)
if err != nil {
http. Error (w, "Internal Server Error" , http.StatusInternalServerError)
return
}
} else {
tmplFile, err = readFile (TEMPLATE_DIR + "/" + page, "./" )
if err != nil {
http. Error (w, "Internal Server Error" , http.StatusInternalServerError)
return
}
}
tmpl, err := template. New ( "page" ). Parse (tmplFile)
if err != nil {
http. Error (w, "Internal Server Error" , http.StatusInternalServerError)
return
}
err = tmpl. Execute (w, reqData)
if err != nil {
http. Error (w, "Internal Server Error" , http.StatusInternalServerError)
return
}
}
func main () {
mux := http. NewServeMux ()
mux. HandleFunc ( "/" , getIndex)
mux. HandleFunc ( "/render" , getTpl)
mux. Handle ( "/static/" , http. StripPrefix ( "/static/" , http. FileServer (http. Dir ( "static" ))))
fmt. Println ( "Server started at port " + WEB_PORT)
http. ListenAndServe ( ":" + WEB_PORT, mux)
}
Pay closer look to the highlighted code snippet.
This function allows us to execute shell commands. The program also has a function to read files from the local filesystem and from remote URLs.
We can create a template that executes commands on the server by using go templating code.
But we cannot type directly into the input field. We need to host the template on a remote server. So I decided to use Webhook.site to host the template.
This challenge was a good exercise in exploiting SSRF vulnerabilities. I learned how to use go templating code to execute commands on the server. I also learned how to host templates on remote servers and use them to exploit SSRF vulnerabilities. I hope you enjoyed this write-up. Thanks for reading!